feat: SEO altyapısı + admin editör/favicon/menü düzeltmeleri

Admin & site:
- @tailwindcss/typography ekle → editör ve yayın içeriği prose stilleriyle düzgün render
- Favicon: logo.png'den kare app/icon.png + apple-icon.png, varsayılan favicon.ico kaldırıldı
- SEO keyword: seo_settings.default_keywords + seo_pages.keywords + buildMetadata birleştirme
- Menü düzeni admin'den yönetilebilir (site_settings.nav_items, /admin/menu, header & mobile-menu refactor)

SEO:
- app/sitemap.ts (statik + blog/hizmet/çözüm/proje/sektör dinamik)
- app/robots.ts (sitemap ref + /admin,/api disallow)
- app/llms.txt/route.ts (AI/LLM rehberi)
- BlogPosting/Service/FAQ/Article JSON-LD wire (json-ld bileşenleri bağlandı)
- buildMetadata: blog/proje OG görseli + type article + keywords birleştirme düzeltmesi
- blog tags → keyword
This commit is contained in:
egecankomur
2026-06-04 07:15:18 +03:00
parent a321ac5c9b
commit d49c9aa225
26 changed files with 780 additions and 151 deletions
+2
View File
@@ -16,6 +16,7 @@ import {
Image as ImageIcon,
Users as UsersIcon,
Building2,
ListOrdered,
type LucideIcon,
} from "lucide-react";
@@ -24,6 +25,7 @@ type Item = { href: string; label: string; icon: LucideIcon };
const items: Item[] = [
{ href: "/admin", label: "Pano", icon: LayoutDashboard },
{ href: "/admin/site", label: "Site Ayarları", icon: Settings },
{ href: "/admin/menu", label: "Menü Düzeni", icon: ListOrdered },
{ href: "/admin/blog", label: "Blog", icon: Newspaper },
{ href: "/admin/hizmetler", label: "Hizmetler", icon: Layers },
{ href: "/admin/cozumler", label: "Çözümler", icon: Boxes },
+89 -91
View File
@@ -3,6 +3,8 @@ import Link from "next/link";
import { ChevronDown, Phone } from "lucide-react";
import { getSiteSettings, listServices } from "@/lib/data";
import { siteConfig } from "@/lib/site-config";
import { resolveNavItems } from "@/lib/nav";
import type { ServiceRow } from "@/lib/types";
import { HeaderScrollEffect } from "@/components/header-scroll";
import { MobileMenu } from "@/components/mobile-menu";
@@ -22,6 +24,9 @@ export async function Header() {
["seo-dijital-pazarlama", "sosyal-medya-yonetimi", "dijital-reklam"].includes(s.slug),
);
// Admin'den düzenlenebilir üst menü düzeni
const navItems = resolveNavItems(settings?.nav_items).filter((i) => i.visible);
return (
<>
<HeaderScrollEffect />
@@ -50,98 +55,26 @@ export async function Header() {
</span>
</Link>
{/* Col 2 — Desktop nav */}
{/* Col 2 — Desktop nav (sıra admin'den yönetilir) */}
<div className="hidden items-center gap-0.5 lg:flex">
<Link
href="/"
className="inline-flex h-9 items-center justify-center whitespace-nowrap rounded-lg px-3.5 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900"
>
Anasayfa
</Link>
{/* Hizmetler mega menu */}
<div className="group relative">
<button
type="button"
className="inline-flex h-9 items-center justify-center gap-1 whitespace-nowrap rounded-lg px-3.5 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900"
>
Hizmetler
<ChevronDown className="size-3 transition-transform duration-200 group-hover:rotate-180" />
</button>
<div className="pointer-events-none invisible absolute left-1/2 top-full z-50 w-[480px] -translate-x-1/2 pt-2 opacity-0 transition-all duration-150 ease-out group-hover:pointer-events-auto group-hover:visible group-hover:opacity-100">
<div className="translate-y-1 rounded-2xl border border-gray-100 bg-white p-4 shadow-xl transition-transform duration-150 group-hover:translate-y-0">
<div className="grid grid-cols-2 gap-x-3">
<div>
<p className="px-3 pb-1.5 text-[10px] font-semibold uppercase tracking-wider text-gray-400">
Web & Yazılım
</p>
{webServices.map((s) => (
<Link
key={s.slug}
href={`/hizmetler/${s.slug}`}
className="flex items-center gap-3 rounded-xl px-3 py-2.5 text-sm text-gray-700 transition-colors hover:bg-blue-50 hover:text-[var(--navy)]"
>
{s.title}
</Link>
))}
</div>
<div>
<p className="px-3 pb-1.5 text-[10px] font-semibold uppercase tracking-wider text-gray-400">
Dijital Pazarlama
</p>
{marketingServices.map((s) => (
<Link
key={s.slug}
href={`/hizmetler/${s.slug}`}
className="flex items-center gap-3 rounded-xl px-3 py-2.5 text-sm text-gray-700 transition-colors hover:bg-blue-50 hover:text-[var(--navy)]"
>
{s.title}
</Link>
))}
</div>
</div>
<div className="mt-3 border-t border-gray-100 pt-3">
<Link
href="/hizmetler"
className="block rounded-xl px-3 py-2 text-center text-xs font-semibold text-[var(--navy)] hover:bg-blue-50"
>
Tüm hizmetleri gör
</Link>
</div>
</div>
</div>
</div>
<Link
href="/cozumler"
className="inline-flex h-9 items-center justify-center whitespace-nowrap rounded-lg px-3.5 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900"
>
Çözümler
</Link>
<Link
href="/projeler"
className="inline-flex h-9 items-center justify-center whitespace-nowrap rounded-lg px-3.5 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900"
>
Projeler
</Link>
<Link
href="/blog"
className="inline-flex h-9 items-center justify-center whitespace-nowrap rounded-lg px-3.5 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900"
>
Blog
</Link>
<Link
href="/hakkimizda"
className="inline-flex h-9 items-center justify-center whitespace-nowrap rounded-lg px-3.5 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900"
>
Hakkımızda
</Link>
<Link
href="/iletisim"
className="inline-flex h-9 items-center justify-center whitespace-nowrap rounded-lg px-3.5 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900"
>
İletişim
</Link>
{navItems.map((item) =>
item.mega ? (
<ServicesMegaMenu
key={item.key}
label={item.label}
webServices={webServices}
marketingServices={marketingServices}
/>
) : (
<Link
key={item.key}
href={item.href}
className="inline-flex h-9 items-center justify-center whitespace-nowrap rounded-lg px-3.5 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900"
>
{item.label}
</Link>
),
)}
</div>
{/* Col 3 — CTA */}
@@ -176,6 +109,7 @@ export async function Header() {
{/* Mobil menü (hamburger) — sadece < lg */}
<MobileMenu
navItems={navItems}
services={services.map((s) => ({ slug: s.slug, title: s.title }))}
phone={phone}
phoneRaw={phoneRaw}
@@ -188,3 +122,67 @@ export async function Header() {
</>
);
}
function ServicesMegaMenu({
label,
webServices,
marketingServices,
}: {
label: string;
webServices: ServiceRow[];
marketingServices: ServiceRow[];
}) {
return (
<div className="group relative">
<button
type="button"
className="inline-flex h-9 items-center justify-center gap-1 whitespace-nowrap rounded-lg px-3.5 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900"
>
{label}
<ChevronDown className="size-3 transition-transform duration-200 group-hover:rotate-180" />
</button>
<div className="pointer-events-none invisible absolute left-1/2 top-full z-50 w-[480px] -translate-x-1/2 pt-2 opacity-0 transition-all duration-150 ease-out group-hover:pointer-events-auto group-hover:visible group-hover:opacity-100">
<div className="translate-y-1 rounded-2xl border border-gray-100 bg-white p-4 shadow-xl transition-transform duration-150 group-hover:translate-y-0">
<div className="grid grid-cols-2 gap-x-3">
<div>
<p className="px-3 pb-1.5 text-[10px] font-semibold uppercase tracking-wider text-gray-400">
Web & Yazılım
</p>
{webServices.map((s) => (
<Link
key={s.slug}
href={`/hizmetler/${s.slug}`}
className="flex items-center gap-3 rounded-xl px-3 py-2.5 text-sm text-gray-700 transition-colors hover:bg-blue-50 hover:text-[var(--navy)]"
>
{s.title}
</Link>
))}
</div>
<div>
<p className="px-3 pb-1.5 text-[10px] font-semibold uppercase tracking-wider text-gray-400">
Dijital Pazarlama
</p>
{marketingServices.map((s) => (
<Link
key={s.slug}
href={`/hizmetler/${s.slug}`}
className="flex items-center gap-3 rounded-xl px-3 py-2.5 text-sm text-gray-700 transition-colors hover:bg-blue-50 hover:text-[var(--navy)]"
>
{s.title}
</Link>
))}
</div>
</div>
<div className="mt-3 border-t border-gray-100 pt-3">
<Link
href="/hizmetler"
className="block rounded-xl px-3 py-2 text-center text-xs font-semibold text-[var(--navy)] hover:bg-blue-50"
>
Tüm hizmetleri gör
</Link>
</div>
</div>
</div>
</div>
);
}
+45
View File
@@ -1,4 +1,5 @@
import type {
BlogPostRow,
ProjectRow,
ServiceRow,
SiteSettingsRow,
@@ -6,6 +7,12 @@ import type {
} from "@/lib/types";
import { siteConfig } from "@/lib/site-config";
function absUrl(url?: string | null): string | undefined {
if (!url) return undefined;
if (url.startsWith("http")) return url;
return `${siteConfig.url}${url.startsWith("/") ? "" : "/"}${url}`;
}
export function JsonLd({ data }: { data: object }) {
return (
<script
@@ -137,6 +144,44 @@ export function BreadcrumbLd({
);
}
export function BlogPostingLd({
post,
settings,
}: {
post: BlogPostRow;
settings?: SiteSettingsRow | null;
}) {
const image = absUrl(post.seo_image || post.cover_image) ?? `${siteConfig.url}/logo.png`;
const url = `${siteConfig.url}/blog/${post.slug}`;
const data: Record<string, unknown> = {
"@context": "https://schema.org",
"@type": "BlogPosting",
headline: post.title,
description: post.seo_description || post.excerpt || undefined,
image,
datePublished: post.published_at ?? post.$createdAt,
dateModified: post.$updatedAt,
author: {
"@type": post.author ? "Person" : "Organization",
name: post.author || settings?.site_name || siteConfig.name,
},
publisher: {
"@type": "Organization",
name: settings?.site_name ?? siteConfig.name,
logo: {
"@type": "ImageObject",
url: `${siteConfig.url}/logo.png`,
},
},
mainEntityOfPage: { "@type": "WebPage", "@id": url },
url,
};
if (post.tags && post.tags.length > 0) {
data.keywords = post.tags.join(", ");
}
return <JsonLd data={data} />;
}
export function ArticleLd({ post }: { post: ProjectRow }) {
return (
<JsonLd
+50 -56
View File
@@ -5,22 +5,17 @@ import { createPortal } from "react-dom";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { Menu, X, ChevronDown, Phone, ArrowRight } from "lucide-react";
import type { NavItem } from "@/lib/nav";
type NavService = { slug: string; title: string };
const LINKS = [
{ href: "/cozumler", label: "Çözümler" },
{ href: "/projeler", label: "Projeler" },
{ href: "/blog", label: "Blog" },
{ href: "/hakkimizda", label: "Hakkımızda" },
{ href: "/iletisim", label: "İletişim" },
];
export function MobileMenu({
navItems,
services,
phone,
phoneRaw,
}: {
navItems: NavItem[];
services: NavService[];
phone: string;
phoneRaw: string;
@@ -84,58 +79,57 @@ export function MobileMenu({
</button>
</div>
{/* Linkler */}
{/* Linkler — sıra admin'den yönetilir */}
<nav className="flex-1 overflow-y-auto px-3 py-4">
<Link
href="/"
className="block rounded-xl px-4 py-3 text-base font-medium text-gray-800 transition-colors hover:bg-blue-50 hover:text-[var(--navy)]"
>
Anasayfa
</Link>
{/* Hizmetler — açılır */}
<button
type="button"
onClick={() => setServicesOpen((v) => !v)}
aria-expanded={servicesOpen}
className="flex w-full items-center justify-between rounded-xl px-4 py-3 text-base font-medium text-gray-800 transition-colors hover:bg-blue-50 hover:text-[var(--navy)]"
>
Hizmetler
<ChevronDown
className={`size-4 transition-transform duration-200 ${
servicesOpen ? "rotate-180" : ""
}`}
/>
</button>
{servicesOpen && (
<div className="mb-1 ml-3 border-l border-gray-100 pl-3">
{services.map((s) => (
{navItems
.filter((item) => item.visible)
.map((item) =>
item.mega ? (
<div key={item.key}>
{/* Hizmetler — açılır */}
<button
type="button"
onClick={() => setServicesOpen((v) => !v)}
aria-expanded={servicesOpen}
className="flex w-full items-center justify-between rounded-xl px-4 py-3 text-base font-medium text-gray-800 transition-colors hover:bg-blue-50 hover:text-[var(--navy)]"
>
{item.label}
<ChevronDown
className={`size-4 transition-transform duration-200 ${
servicesOpen ? "rotate-180" : ""
}`}
/>
</button>
{servicesOpen && (
<div className="mb-1 ml-3 border-l border-gray-100 pl-3">
{services.map((s) => (
<Link
key={s.slug}
href={`/hizmetler/${s.slug}`}
className="block rounded-lg px-3 py-2 text-sm text-gray-600 transition-colors hover:bg-blue-50 hover:text-[var(--navy)]"
>
{s.title}
</Link>
))}
<Link
href="/hizmetler"
className="block rounded-lg px-3 py-2 text-sm font-semibold text-[var(--sky-600)] hover:text-[var(--navy)]"
>
Tüm hizmetleri gör
</Link>
</div>
)}
</div>
) : (
<Link
key={s.slug}
href={`/hizmetler/${s.slug}`}
className="block rounded-lg px-3 py-2 text-sm text-gray-600 transition-colors hover:bg-blue-50 hover:text-[var(--navy)]"
key={item.key}
href={item.href}
className="block rounded-xl px-4 py-3 text-base font-medium text-gray-800 transition-colors hover:bg-blue-50 hover:text-[var(--navy)]"
>
{s.title}
{item.label}
</Link>
))}
<Link
href="/hizmetler"
className="block rounded-lg px-3 py-2 text-sm font-semibold text-[var(--sky-600)] hover:text-[var(--navy)]"
>
Tüm hizmetleri gör
</Link>
</div>
)}
{LINKS.map((l) => (
<Link
key={l.href}
href={l.href}
className="block rounded-xl px-4 py-3 text-base font-medium text-gray-800 transition-colors hover:bg-blue-50 hover:text-[var(--navy)]"
>
{l.label}
</Link>
))}
),
)}
</nav>
{/* Alt CTA */}