import type { Metadata } from "next"; import { URUNLER } from "./credits"; /** * Kanonik site adresi. Ödeme akışının kök adresiyle (`appUrl()`, * src/lib/app-url.ts) karıştırılmamalı: o runtime'da okunur, bu ise metadata * için build anında sabitlenir. .dockerignore .env* dosyalarını dışladığı için * prod build burada deterministik olarak kolaytercih.com'a düşer. */ export const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? (process.env.NODE_ENV === "production" ? "https://kolaytercih.com" : "http://localhost:3000"); export const SITE_NAME = "KolayTercih"; /** * Katalog verisinin (data/yokatlas.db) en son yenilendiği gün. Sitemap lastmod, * katalog sayfalarındaki görünür "son güncelleme" satırı buradan okunur. * Yalnızca veri GERÇEKTEN değiştiğinde (ingest/refresh sonrası) elle güncelle; * build zamanına bağlama — her deploy'da değişen lastmod sinyali değersizleşir. */ export const KATALOG_VERI_TARIHI = "2026-09-21"; type SayfaMetadataGirdi = { title: string; description: string; /** Kanonik yol ("/bolumler"); metadataBase ile mutlak adrese çözülür. */ path: string; /** Varsayılan "website"; rehber yazıları article alanlarını buradan verir. */ openGraph?: NonNullable; }; /** * İndekslenebilir sayfaların metadata'sı için tek kaynak. Next, segmentler * arası metadata'yı SIĞ birleştirir: sayfa `openGraph` vermezse kök layout'unki * (ana sayfanın başlığı + og:url'i) olduğu gibi miras kalır; verirse nesnenin * tamamı ezilir (siteName/locale kaybolur). Bu yüzden openGraph burada eksiksiz * üretilir. twitter:title/description/image, openGraph'tan otomatik dolar. */ export function sayfaMetadata({ title, description, path, openGraph, }: SayfaMetadataGirdi): Metadata { return { title, description, alternates: { canonical: path }, openGraph: { type: "website", locale: "tr_TR", siteName: SITE_NAME, url: path, title, description, ...openGraph, }, }; } /** * Üst segmentin dosya-tabanlı OG görselini (opengraph-image.tsx) alt rotaya * elle bağlar. Gerekçe: alt rota `openGraph` tanımlayınca sığ birleştirme üst * segmentten gelen `images`'ı da düşürür (ör. /bolum/[slug]/sayfa/[no]). * Boyut, src/lib/og-sayfa.tsx içindeki OG_SIZE ile aynı olmalı. */ export function ustSegmentOgGorseli(path: string, alt: string) { return [{ url: `${path}/opengraph-image`, width: 1200, height: 630, alt }]; } type JsonLdData = Record; export function organizationJsonLd(): JsonLdData { return { "@context": "https://schema.org", "@type": "Organization", name: SITE_NAME, url: SITE_URL, logo: `${SITE_URL}/kolay-tercih-mark.svg`, contactPoint: { "@type": "ContactPoint", email: "destek@kolaytercih.com", contactType: "customer support", availableLanguage: "Turkish", }, }; } export function webSiteJsonLd(): JsonLdData { return { "@context": "https://schema.org", "@type": "WebSite", name: SITE_NAME, url: SITE_URL, inLanguage: "tr", }; } export function softwareApplicationJsonLd(): JsonLdData { return { "@context": "https://schema.org", "@type": "SoftwareApplication", name: SITE_NAME, applicationCategory: "EducationalApplication", operatingSystem: "Web", url: SITE_URL, inLanguage: "tr", offers: { "@type": "Offer", price: (URUNLER.paket.amountKurus / 100).toFixed(0), priceCurrency: "TRY", description: URUNLER.paket.label, }, }; } export function faqPageJsonLd( faqs: readonly { question: string; answer: string }[], ): JsonLdData { return { "@context": "https://schema.org", "@type": "FAQPage", mainEntity: faqs.map((f) => ({ "@type": "Question", name: f.question, acceptedAnswer: { "@type": "Answer", text: f.answer }, })), }; } export function breadcrumbJsonLd( items: readonly { name: string; path: string }[], ): JsonLdData { return { "@context": "https://schema.org", "@type": "BreadcrumbList", itemListElement: items.map((item, i) => ({ "@type": "ListItem", position: i + 1, name: item.name, item: `${SITE_URL}${item.path}`, })), }; } export function articleJsonLd(opts: { title: string; description: string; path: string; datePublished: string; /** Verilmezse yayın tarihi kullanılır (yazı hiç güncellenmemiş). */ dateModified?: string; /** Mutlak görsel adresi; verilmezse yolun opengraph-image'ı kullanılır. */ image?: string; }): JsonLdData { const url = `${SITE_URL}${opts.path}`; return { "@context": "https://schema.org", "@type": "Article", headline: opts.title, description: opts.description, url, mainEntityOfPage: url, image: opts.image ?? `${url}/opengraph-image`, datePublished: opts.datePublished, dateModified: opts.dateModified ?? opts.datePublished, inLanguage: "tr", // Yazar kurum olarak verilir: yazılar tek imzayla değil ekipçe yazılıp // resmî kaynakla (ÖSYM/YÖK) teyit ediliyor; görünür yazar satırı // rehber-makale.tsx'te aynı adı kullanır. Kişi adı yayımlanacaksa // burası Person'a çevrilir (docs/arastirma/2026-09-24-geo-seo). author: { "@type": "Organization", name: `${SITE_NAME} Ekibi`, url: SITE_URL, }, publisher: { "@type": "Organization", name: SITE_NAME, url: SITE_URL, logo: { "@type": "ImageObject", url: `${SITE_URL}/kolay-tercih-mark.png`, }, }, }; } /** JSON-LD'yi güvenli gömer: `<` kaçışı script-injection'ı engeller. */ export function JsonLd({ data }: { data: JsonLdData }) { return (