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

95 lines
2.9 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 "server-only";
import type { Metadata } from "next";
import { getSeoPage, getSeoSettings } from "@/lib/data";
import { siteConfig } from "@/lib/site-config";
export async function buildMetadata(path: string, fallback?: Metadata): Promise<Metadata> {
const [settings, override] = await Promise.all([
getSeoSettings(),
getSeoPage(path),
]);
const siteName = settings?.site_name || siteConfig.name;
const siteDesc = settings?.site_description || siteConfig.tagline;
const ogDefault = settings?.default_og_image || "/logo.png";
const title =
override?.title ?? (fallback?.title as string | undefined) ?? siteName;
const description =
override?.description ??
(fallback?.description as string | undefined) ??
siteDesc;
// Sayfanın kendi OG bilgisi (blog kapağı, type:"article" vb.) — fallback'ten
// oku. Öncelik: sayfa SEO override > sayfanın fallback OG görseli > varsayılan.
const fbOg = fallback?.openGraph as
| { images?: unknown; type?: string }
| undefined;
const fbOgImage = (() => {
const imgs = fbOg?.images;
if (typeof imgs === "string") return imgs;
if (Array.isArray(imgs) && imgs.length) {
const first = imgs[0];
if (typeof first === "string") return first;
if (first && typeof first === "object" && "url" in first)
return String((first as { url: unknown }).url);
}
return undefined;
})();
const ogImage = override?.og_image || fbOgImage || ogDefault;
const ogType = fbOg?.type ?? "website";
// Anahtar kelimeler: sayfa override + site geneli varsayılan + sayfanın kendi
// keyword'leri (örn. blog etiketleri) birleştirilir, tekrarlar ayıklanır.
const fbKeywords = fallback?.keywords;
const fbKeywordsStr = Array.isArray(fbKeywords)
? fbKeywords.join(",")
: typeof fbKeywords === "string"
? fbKeywords
: "";
const keywordsRaw = [override?.keywords, settings?.default_keywords, fbKeywordsStr]
.filter(Boolean)
.join(",");
const keywords = keywordsRaw
? Array.from(
new Set(
keywordsRaw
.split(",")
.map((k) => k.trim())
.filter(Boolean),
),
)
: undefined;
return {
title,
description,
keywords,
metadataBase: new URL(siteConfig.url),
openGraph: {
title,
description,
images: ogImage ? [{ url: ogImage }] : undefined,
type: ogType as "website" | "article",
locale: "tr_TR",
siteName,
},
twitter: {
card: "summary_large_image",
title,
description,
images: ogImage ? [ogImage] : undefined,
site: settings?.twitter_handle ?? undefined,
},
alternates: override?.canonical
? { canonical: override.canonical }
: undefined,
robots: override?.noindex
? { index: false, follow: false }
: undefined,
verification: settings?.google_site_verification
? { google: settings.google_site_verification }
: undefined,
};
}