feat(seo): GEO paketi — yapılandırılmış metadata, AI bot erişimi, veri tazelik sinyali

- sayfaMetadata(): indekslenebilir sayfalar için tek kaynaklı canonical +
  eksiksiz openGraph üretimi; sayfalar buna geçirildi
- robots.ts: yalnızca /api/ engellenir; noindex'li sayfalar robots engeli
  yerine meta ile yönetilir, AI botları niyeti belgeleyen açık grupla izinli
- next.config: htmlLimitedBots'a AI botları eklendi (JS çalıştırmayan
  botlara metadata head içinde bloklanarak verilir)
- sitemap lastmod + katalogda görünür 'Kaynak/son güncelleme' satırı
  (VeriKaynagi) KATALOG_VERI_TARIHI tek kaynağından
- docs/geo/geo-raporu.md: çalışmanın raporu

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-09-21 00:51:16 +03:00
parent d2651eadb9
commit 53eb07bb34
18 changed files with 718 additions and 73 deletions

View File

@@ -8,9 +8,15 @@ import {
getBolumBySlug,
uniSlugFromAd,
} from "../katalog-queries";
import { JsonLd, breadcrumbJsonLd } from "@/lib/seo";
import {
JsonLd,
breadcrumbJsonLd,
sayfaMetadata,
ustSegmentOgGorseli,
} from "@/lib/seo";
import { SAYFA_BOYU } from "./sayfalama";
import { BolumProgramListesi } from "./bolum-program-listesi";
import { VeriKaynagi } from "./veri-kaynagi";
import { SiteFooter } from "@/components/site-footer";
import { Skeleton } from "@/components/ui/skeleton";
@@ -41,21 +47,27 @@ export function bolumSayfaMetadata(slug: string, sayfa: number): Metadata {
if (!bolum) return {};
const { stats } = bolum;
if (sayfa > 1) {
return {
return sayfaMetadata({
title: `${bolum.ad} Taban Puanları 2026 (Sayfa ${sayfa})`,
description: `${bolum.ad} taban puanları ve başarı sıralamaları tablosunun ${sayfa}. sayfası — toplam ${stats.toplam} programın 2025 yerleştirme verileri.`,
alternates: { canonical: `/bolum/${slug}/sayfa/${sayfa}` },
};
path: `/bolum/${slug}/sayfa/${sayfa}`,
openGraph: {
images: ustSegmentOgGorseli(
`/bolum/${slug}`,
"Bölüm taban puanları ve sıralamaları — KolayTercih",
),
},
});
}
const enIyi =
stats.minSira != null
? ` En iyi taban sıralaması ${sayi(stats.minSira)}.`
: "";
return {
return sayfaMetadata({
title: `${bolum.ad} Taban Puanları ve Başarı Sıralamaları 2026`,
description: `${bolum.ad} 2025 yerleştirme verileri: ${stats.toplam} programın taban puanı ve sıralaması, ${stats.devlet} devlet / ${stats.vakif} vakıf.${enIyi} 2026 tercih rehberi.`,
alternates: { canonical: `/bolum/${slug}` },
};
path: `/bolum/${slug}`,
});
}
export function BolumIcerik({ slug, sayfa }: { slug: string; sayfa: number }) {
@@ -139,6 +151,7 @@ export function BolumIcerik({ slug, sayfa }: { slug: string; sayfa: number }) {
tabanYol={`/bolum/${slug}`}
sayfa={sayfa}
/>
<VeriKaynagi className="mt-6" />
</section>
</main>
<SiteFooter />

View File

@@ -9,7 +9,13 @@ import {
bolumSlugFromIsim,
} from "../katalog-queries";
import { trBaslikDuzeni } from "@/lib/slug";
import { JsonLd, breadcrumbJsonLd, SITE_URL } from "@/lib/seo";
import {
JsonLd,
breadcrumbJsonLd,
sayfaMetadata,
SITE_URL,
ustSegmentOgGorseli,
} from "@/lib/seo";
import { CtaSiraForm } from "@/features/sihirbaz/components/cta-sira-form";
import { SiteFooter } from "@/components/site-footer";
import { Sayfalama, SAYFA_BOYU } from "./sayfalama";
@@ -17,6 +23,7 @@ import { PagePixelDivider } from "@/components/pixel-decor";
import { UniversiteKonumHaritasi } from "./universite-konum-haritasi";
import { uniUlkeKonum } from "@/lib/harita-ulkeler";
import { UniversiteProgramTablosu } from "./universite-program-tablosu";
import { VeriKaynagi } from "./veri-kaynagi";
import { UniLogo } from "@/components/uni-logo";
import { uniLogoYolu } from "@/lib/uni-logolar";
import { Skeleton } from "@/components/ui/skeleton";
@@ -48,17 +55,23 @@ export function uniSayfaMetadata(slug: string, sayfa: number): Metadata {
const uni = getUniversiteBySlug(slug);
if (!uni) return {};
if (sayfa > 1) {
return {
return sayfaMetadata({
title: `${uni.ad} Taban Puanları 2026 (Sayfa ${sayfa})`,
description: `${uni.ad} taban puanları ve başarı sıralamaları tablosunun ${sayfa}. sayfası — toplam ${uni.programSayisi} programın 2025 yerleştirme verileri.`,
alternates: { canonical: `/universite/${slug}/sayfa/${sayfa}` },
};
path: `/universite/${slug}/sayfa/${sayfa}`,
openGraph: {
images: ustSegmentOgGorseli(
`/universite/${slug}`,
"Üniversite taban puanları ve sıralamaları — KolayTercih",
),
},
});
}
return {
return sayfaMetadata({
title: `${uni.ad} Taban Puanları ve Başarı Sıralamaları 2026`,
description: `${uni.ad} 2025 yerleştirme verileri: ${uni.programSayisi} programın taban puanları, başarı sıralamaları ve kontenjanları${uni.il ? ` (${trBaslikDuzeni(uni.il)})` : ""}. 2026 tercih rehberi.`,
alternates: { canonical: `/universite/${slug}` },
};
path: `/universite/${slug}`,
});
}
export function UniversiteIcerik({
@@ -274,11 +287,10 @@ export function UniversiteIcerik({
<UniversiteProgramTablosu gruplar={gruplar} />
<p className="mt-3 text-xs leading-5 text-slate-500">
<VeriKaynagi>
&quot;—&quot;: o yıl için yerleştirme verisi bulunmuyor (yeni açılan
veya o yıl yerleşen çıkmayan program). Kaynak: YÖK Atlas 2025
yerleştirme sonuçları.
</p>
veya o yıl yerleşen çıkmayan program).{" "}
</VeriKaynagi>
<Sayfalama
tabanYol={`/universite/${slug}`}

View File

@@ -0,0 +1,29 @@
import { KATALOG_VERI_TARIHI } from "@/lib/seo";
import { cn } from "@/lib/utils";
const TARIH_METNI = new Date(
`${KATALOG_VERI_TARIHI}T00:00:00Z`,
).toLocaleDateString("tr-TR", {
day: "numeric",
month: "long",
year: "numeric",
timeZone: "UTC",
});
// Katalog tablolarının altındaki kaynak + tazelik satırı. Görünür metin olması
// bilinçli: arama/AI botları JSON-LD'ye değil sayfadaki metne güvenir.
export function VeriKaynagi({
children,
className,
}: {
children?: React.ReactNode;
className?: string;
}) {
return (
<p className={cn("mt-3 text-xs leading-5 text-slate-500", className)}>
{children}
Kaynak: YÖK Atlas 2025 yerleştirme sonuçları. Son güncelleme:{" "}
<time dateTime={KATALOG_VERI_TARIHI}>{TARIH_METNI}</time>.
</p>
);
}