Files
egecankomur d49c9aa225 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
2026-06-04 07:15:18 +03:00

116 lines
3.8 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Image from "next/image";
import Link from "next/link";
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { ArrowLeft, Calendar } from "lucide-react";
import { renderContent } from "@/lib/content-render";
import { getPostBySlug } from "@/lib/data";
import { buildMetadata } from "@/lib/seo";
import { BlogPostingLd } from "@/components/json-ld";
import { ContentSidebar } from "@/components/content-sidebar";
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}): Promise<Metadata> {
const { slug } = await params;
const post = await getPostBySlug(slug);
if (!post) return { title: "Yazı bulunamadı" };
return buildMetadata(`/blog/${slug}`, {
title: post.seo_title || post.title,
description: post.seo_description || post.excerpt || undefined,
keywords: post.tags ?? undefined,
openGraph: {
title: post.seo_title || post.title,
description: post.seo_description || post.excerpt || undefined,
images:
post.seo_image || post.cover_image
? [{ url: (post.seo_image || post.cover_image) as string }]
: undefined,
type: "article",
},
});
}
export default async function BlogPostPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const post = await getPostBySlug(slug);
if (!post || post.status !== "published") notFound();
const html = renderContent(post.content);
return (
<div className="mx-auto max-w-7xl px-6 py-16">
<BlogPostingLd post={post} />
<Link
href="/blog"
className="inline-flex items-center gap-1 text-sm text-[var(--muted)] hover:text-[var(--navy)]"
>
<ArrowLeft className="size-3.5" /> Tüm yazılar
</Link>
<div className="mt-6 grid gap-12 lg:grid-cols-[1fr_320px]">
<article>
<header className="border-b border-[var(--border)] pb-8">
{post.tags && post.tags.length > 0 && (
<div className="flex flex-wrap gap-2">
{post.tags.map((t) => (
<span
key={t}
className="rounded-full bg-[var(--sky-50)] px-2.5 py-1 text-xs text-[var(--sky-600)]"
>
{t}
</span>
))}
</div>
)}
<h1 className="mt-4 text-3xl font-bold leading-tight tracking-tight text-[var(--navy)] sm:text-4xl">
{post.title}
</h1>
{post.excerpt && (
<p className="mt-4 text-lg leading-relaxed text-[var(--muted)]">
{post.excerpt}
</p>
)}
<div className="mt-6 flex items-center gap-3 text-xs text-[var(--muted)]">
{post.author && <span>{post.author}</span>}
{post.author && post.published_at && <span></span>}
{post.published_at && (
<span className="inline-flex items-center gap-1">
<Calendar className="size-3" />
{new Date(post.published_at).toLocaleDateString("tr-TR")}
</span>
)}
</div>
</header>
{post.cover_image && (
<div className="relative mt-8 aspect-video overflow-hidden rounded-2xl">
<Image
src={post.cover_image}
alt={post.title}
fill
sizes="(min-width: 1024px) 768px, 100vw"
className="object-cover"
priority
/>
</div>
)}
<div
className="prose prose-lg mt-10 max-w-none text-[var(--foreground)]"
dangerouslySetInnerHTML={{ __html: html }}
/>
</article>
<ContentSidebar currentSlug={slug} />
</div>
</div>
);
}