feat(seo): derin katalog sayfalarında başlık/açıklama/H1'i sayfanın içeriğiyle ayrıştır
/bolum/*/sayfa/N: title + description + H1 o sayfanın taban sırası aralığını taşır (dilimdeki min–max efektif sıra; veri yoksa "taban verisi olmayan programlar"). Görünür breadcrumb üniversite sayfasındaki desenle eşitlendi (üst bölüm linki + "Sayfa N"). /universite/*/sayfa/N: description + giriş paragrafı o sayfadaki birim adlarını sayar (ilk 2 + "ve N birim daha"); dilim tek birimden ibaretse birim içi taban sırası aralığı eklenir. <title> uzunluk nedeniyle değişmedi. Dilim hesabı tek yardımcıda (sayfaDilimi); metadata ve gövde aynı hesabı kullanır. Canonical, robots, sitemap, rota dosyaları ve 1. sayfaların başlık/açıklaması değişmedi. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -18,7 +18,7 @@ import {
|
||||
} from "@/lib/seo";
|
||||
import { CtaSiraForm } from "@/features/sihirbaz/components/cta-sira-form";
|
||||
import { SiteFooter } from "@/components/site-footer";
|
||||
import { Sayfalama, SAYFA_BOYU } from "./sayfalama";
|
||||
import { Sayfalama, SAYFA_BOYU, sayfaDilimi } from "./sayfalama";
|
||||
import { PagePixelDivider } from "@/components/pixel-decor";
|
||||
import { UniversiteKonumHaritasi } from "./universite-konum-haritasi";
|
||||
import { uniUlkeKonum } from "@/lib/harita-ulkeler";
|
||||
@@ -51,13 +51,65 @@ export function uniSayfaStaticParams(): { slug: string; no: string }[] {
|
||||
});
|
||||
}
|
||||
|
||||
type UniDetay = NonNullable<ReturnType<typeof getUniversiteBySlug>>;
|
||||
|
||||
/**
|
||||
* Sayfanın satır dilimi + o dilimde geçen fakülte/birim adları (tablo
|
||||
* sırasıyla, tekrarsız). Liste fakülteye göre dizili olduğundan her derin
|
||||
* sayfa belirli birimleri kapsar. Metadata ve gövde aynı hesabı buradan alır.
|
||||
* Fakültesi boş satırlar tabloda "Diğer" grubuna düşer; ad listesine girmez.
|
||||
*/
|
||||
function uniSayfaDilimi(uni: UniDetay, sayfa: number) {
|
||||
// Sayfalama fakülte sıralı düz liste üzerinden; dilim içi yeniden gruplanır
|
||||
const tumProgramlar = uni.fakulteler.flatMap((f) => f.programlar);
|
||||
const dilim = sayfaDilimi(tumProgramlar, sayfa);
|
||||
const fakulteAdlari: string[] = [];
|
||||
for (const p of dilim) {
|
||||
if (p.fakulte && !fakulteAdlari.includes(p.fakulte)) {
|
||||
fakulteAdlari.push(p.fakulte);
|
||||
}
|
||||
}
|
||||
return { tumProgramlar, dilim, fakulteAdlari };
|
||||
}
|
||||
|
||||
/**
|
||||
* İlk 2 ad + "ve N birim daha"; ad yoksa null (çağıran eski metne düşer).
|
||||
* Dilim tek birimden ibaretse (50'den çok programlı MYO'lar art arda iki
|
||||
* sayfayı doldurabiliyor) sayfalar ayırt edilsin diye birim içi taban sırası
|
||||
* aralığı eklenir — birim içinde satırlar zaten taban sırasına göre dizili.
|
||||
*/
|
||||
function fakulteOzeti({
|
||||
dilim,
|
||||
fakulteAdlari: adlar,
|
||||
}: ReturnType<typeof uniSayfaDilimi>): string | null {
|
||||
if (adlar.length === 0) return null;
|
||||
if (adlar.length === 1) {
|
||||
const siralar = dilim
|
||||
.map((p) => p.sira2025 ?? p.sira2024)
|
||||
.filter((n): n is number => n != null);
|
||||
if (siralar.length === 0) return adlar[0];
|
||||
const min = Math.min(...siralar);
|
||||
const max = Math.max(...siralar);
|
||||
return min === max
|
||||
? `${adlar[0]} (taban sırası ${sayi(min)})`
|
||||
: `${adlar[0]} (taban sırası ${sayi(min)}–${sayi(max)} arası)`;
|
||||
}
|
||||
if (adlar.length === 2) return `${adlar[0]} ve ${adlar[1]}`;
|
||||
return `${adlar[0]}, ${adlar[1]} ve ${adlar.length - 2} birim daha`;
|
||||
}
|
||||
|
||||
export function uniSayfaMetadata(slug: string, sayfa: number): Metadata {
|
||||
const uni = getUniversiteBySlug(slug);
|
||||
if (!uni) return {};
|
||||
if (sayfa > 1) {
|
||||
// Üniversite adları uzun: <title> sayfa numarasıyla kalır (~60 karakter),
|
||||
// farklılaştırıcı (o sayfadaki birimler) açıklamaya ve gövdeye gider.
|
||||
const birimler = fakulteOzeti(uniSayfaDilimi(uni, sayfa));
|
||||
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.`,
|
||||
description: birimler
|
||||
? `${uni.ad} taban puanları, sayfa ${sayfa}: ${birimler}. Toplam ${uni.programSayisi} programın 2025 yerleştirme verileri — taban puanı, başarı sıralaması ve kontenjan.`
|
||||
: `${uni.ad} taban puanları ve başarı sıralamaları tablosunun ${sayfa}. sayfası — toplam ${uni.programSayisi} programın 2025 yerleştirme verileri.`,
|
||||
path: `/universite/${slug}/sayfa/${sayfa}`,
|
||||
openGraph: {
|
||||
images: ustSegmentOgGorseli(
|
||||
@@ -84,14 +136,11 @@ export function UniversiteIcerik({
|
||||
const uni = getUniversiteBySlug(slug);
|
||||
if (!uni) notFound();
|
||||
|
||||
// Sayfalama fakülte sıralı düz liste üzerinden; dilim içi yeniden gruplanır
|
||||
const tumProgramlar = uni.fakulteler.flatMap((f) => f.programlar);
|
||||
const sayfaDilim = uniSayfaDilimi(uni, sayfa);
|
||||
const { tumProgramlar, dilim } = sayfaDilim;
|
||||
const toplamSayfa = uniToplamSayfa(tumProgramlar.length);
|
||||
if (sayfa < 1 || sayfa > toplamSayfa) notFound();
|
||||
const dilim = tumProgramlar.slice(
|
||||
(sayfa - 1) * SAYFA_BOYU,
|
||||
sayfa * SAYFA_BOYU,
|
||||
);
|
||||
const birimler = fakulteOzeti(sayfaDilim);
|
||||
const gruplar: {
|
||||
fakulte: string;
|
||||
programlar: (Program & { bolumSlug: string | null })[];
|
||||
@@ -263,7 +312,9 @@ export function UniversiteIcerik({
|
||||
{sayi(tumProgramlar.length)} programın{" "}
|
||||
{sayi((sayfa - 1) * SAYFA_BOYU + 1)}–
|
||||
{sayi(Math.min(sayfa * SAYFA_BOYU, tumProgramlar.length))}.
|
||||
satırları, fakülte sırasıyla). Üniversite özeti ve istatistikler{" "}
|
||||
satırları, fakülte sırasıyla).
|
||||
{birimler ? ` Bu sayfadaki birimler: ${birimler}.` : ""} Üniversite
|
||||
özeti ve istatistikler{" "}
|
||||
<Link
|
||||
href={`/universite/${slug}`}
|
||||
className="font-medium text-primary hover:underline"
|
||||
|
||||
Reference in New Issue
Block a user