d49c9aa225
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
95 lines
2.9 KiB
TypeScript
95 lines
2.9 KiB
TypeScript
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,
|
||
};
|
||
}
|