feat(geo): llms.txt, RSS, IndexNow, Article yazar/görsel, bölüm sayfasına veri özeti, vakıf bursu yazısı
All checks were successful
Deploy / deploy (push) Successful in 6m16s
All checks were successful
Deploy / deploy (push) Successful in 6m16s
GEO araştırması (docs/arastirma/2026-09-24-geo-seo) Dalga 1-2 uygulaması: - /llms.txt ve /feed.xml route'ları (getAllRehberler'den otomatik), head'de RSS linki - Article JSON-LD: author, image, mainEntityOfPage, publisher logo; görünür 'Yazan' satırı - robots: ClaudeBot ayrı grupta Crawl-delay 1 - scripts/indexnow.ts (pnpm indexnow) + public anahtar dosyası - bölüm sayfası açılışı veriden üretilen özgü cümleler (il/program/tür/kontenjan, taban aralığı, yıllık yön) - ana sayfa fiyat iddiasına kaynak; iki yazıda 23.000 -> 21.000+; beş yazıya 'Kısa cevap' açılışı - yeni yazı: vakif-universitesi-bursu-kesilir-mi (ÖSYM 2026 kılavuzu + YÖK ücret esasları) - YAYIN-KUYRUGU: IndexNow adımı ve aylık AI görünürlük rutini Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
54
src/app/feed.xml/route.ts
Normal file
54
src/app/feed.xml/route.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { getAllRehberler } from "@/lib/rehber";
|
||||
import { SITE_NAME, SITE_URL } from "@/lib/seo";
|
||||
|
||||
/**
|
||||
* Rehber yazılarının RSS 2.0 beslemesi. Kök layout metadata'sındaki
|
||||
* alternates.types bu adresi <link rel="alternate"> olarak <head>'e yazar.
|
||||
* Cache Components altında istek verisi okunmadığı için prerender edilir.
|
||||
*/
|
||||
function xmlKacis(metin: string): string {
|
||||
return metin
|
||||
.replaceAll("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
.replaceAll('"', """);
|
||||
}
|
||||
|
||||
/** "YYYY-MM-DD" → RFC 822 (RSS pubDate). Gün 09:00 Türkiye saati kabul edilir. */
|
||||
function rfc822(iso: string): string {
|
||||
return new Date(`${iso}T09:00:00+03:00`).toUTCString();
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
const yazilar = getAllRehberler();
|
||||
const enYeni = yazilar[0]?.guncelleme ?? yazilar[0]?.tarih;
|
||||
|
||||
const items = yazilar
|
||||
.map(
|
||||
(y) => ` <item>
|
||||
<title>${xmlKacis(y.baslik)}</title>
|
||||
<link>${SITE_URL}/rehber/${y.slug}</link>
|
||||
<guid isPermaLink="true">${SITE_URL}/rehber/${y.slug}</guid>
|
||||
<description>${xmlKacis(y.aciklama)}</description>
|
||||
<pubDate>${rfc822(y.tarih)}</pubDate>
|
||||
</item>`,
|
||||
)
|
||||
.join("\n");
|
||||
|
||||
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<channel>
|
||||
<title>${SITE_NAME} Tercih Rehberi</title>
|
||||
<link>${SITE_URL}/rehber</link>
|
||||
<description>YKS tercih dönemi rehberi: veriye dayalı, öğrenci diliyle yazılar.</description>
|
||||
<language>tr</language>
|
||||
<atom:link href="${SITE_URL}/feed.xml" rel="self" type="application/rss+xml" />
|
||||
${enYeni ? ` <lastBuildDate>${rfc822(enYeni)}</lastBuildDate>\n` : ""}${items}
|
||||
</channel>
|
||||
</rss>
|
||||
`;
|
||||
|
||||
return new Response(xml, {
|
||||
headers: { "Content-Type": "application/rss+xml; charset=utf-8" },
|
||||
});
|
||||
}
|
||||
@@ -52,6 +52,9 @@ export const metadata: Metadata = {
|
||||
description:
|
||||
"YKS sıralamana göre gerçek YÖK Atlas verisiyle dengeli 24 tercihlik liste. İnsan danışmandan çok daha düşük fiyata, yapay zekâ destekli tercih danışmanlığı.",
|
||||
applicationName: "KolayTercih",
|
||||
alternates: {
|
||||
types: { "application/rss+xml": "/feed.xml" },
|
||||
},
|
||||
openGraph: {
|
||||
type: "website",
|
||||
locale: "tr_TR",
|
||||
|
||||
62
src/app/llms.txt/route.ts
Normal file
62
src/app/llms.txt/route.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { getAllRehberler } from "@/lib/rehber";
|
||||
import { KATALOG_VERI_TARIHI, SITE_URL } from "@/lib/seo";
|
||||
import { SON_YIL, VERI_ARALIGI } from "@/lib/veri-yillari";
|
||||
import { PROGRAM_SAYISI_METNI } from "@/lib/veri-kapsami";
|
||||
import { trTarih } from "@/lib/tarih";
|
||||
|
||||
/**
|
||||
* llmstxt.org biçiminde site özeti: H1, tek paragraf özet, H2 altında
|
||||
* bağlantı listeleri. Yalnızca sitede zaten görünen bilgiyi taşır; içerik
|
||||
* gövdesi (llms-full.txt) bilinçli olarak üretilmez — etkisi kanıtsız,
|
||||
* toplu kopyalamayı kolaylaştırır (docs/arastirma/2026-09-24-geo-seo).
|
||||
*
|
||||
* Rehber listesi getAllRehberler()'den gelir; sitemap ile aynı kaynak,
|
||||
* elle bakım yok. Cache Components altında istek verisi okunmadığı için
|
||||
* build'de prerender edilir.
|
||||
*/
|
||||
export async function GET() {
|
||||
const yazilar = getAllRehberler();
|
||||
const rehberSatirlari = yazilar
|
||||
.map(
|
||||
(y) =>
|
||||
`- [${y.baslik}](${SITE_URL}/rehber/${y.slug}): ${y.aciklama}`,
|
||||
)
|
||||
.join("\n");
|
||||
|
||||
const metin = `# KolayTercih
|
||||
|
||||
> Türkiye'de YKS (üniversite giriş sınavı) tercih danışmanlığı: YÖK Atlas'ın
|
||||
> resmî yerleştirme verisine dayanan, yapay zekâ destekli 24 tercihlik dengeli
|
||||
> liste. Ücretsiz katalog: bölüm, üniversite ve il sayfalarında ${VERI_ARALIGI}
|
||||
> taban sıralamaları.
|
||||
|
||||
Veri kaynağı: YÖK Atlas ${SON_YIL} yerleştirme sonuçları, ${PROGRAM_SAYISI_METNI}
|
||||
program. Katalog en son ${trTarih(KATALOG_VERI_TARIHI)} tarihinde güncellendi.
|
||||
KolayTercih, ÖSYM ya da YÖK ile bağlantılı değildir; yerleşme garantisi vermez.
|
||||
Yapay zekâ önerileri yalnızca kullanıcının sıralamasıyla açılan gerçek programlar
|
||||
arasından seçilir.
|
||||
|
||||
## Ana bölümler
|
||||
|
||||
- [Bölümler](${SITE_URL}/bolumler): bölüm bazında taban puanı ve sıralama tabloları
|
||||
- [Üniversiteler](${SITE_URL}/universiteler): il bazlı üniversite kataloğu
|
||||
- [Üniversite sıralaması](${SITE_URL}/universite-siralamasi): URAP akademik başarı sıralaması
|
||||
- [Tercih Rehberi](${SITE_URL}/rehber): tercih süreciyle ilgili ${yazilar.length} rehber yazısı
|
||||
- [Nasıl çalışır](${SITE_URL}/meraklisina): sihirbaz ve liste mantığının anlatımı
|
||||
- [Paket](${SITE_URL}/paket): ücretli yapay zekâ tercih listesi hizmeti
|
||||
|
||||
## Rehber yazıları
|
||||
|
||||
${rehberSatirlari}
|
||||
|
||||
## Optional
|
||||
|
||||
- [Gizlilik](${SITE_URL}/gizlilik)
|
||||
- [Kullanım koşulları](${SITE_URL}/kosullar)
|
||||
- [İletişim](${SITE_URL}/iletisim)
|
||||
`;
|
||||
|
||||
return new Response(metin, {
|
||||
headers: { "Content-Type": "text/plain; charset=utf-8" },
|
||||
});
|
||||
}
|
||||
@@ -93,7 +93,7 @@ const problems = [
|
||||
icon: Wallet,
|
||||
title: "Danışman lüks haline geldi",
|
||||
description:
|
||||
"İyi bir tercih danışmanı 1.000–3.000 TL istiyor. Ücretsiz robotların çoğu ise yalnızca liste filtreliyor; listeyi kurup gerekçesini yazmıyor.",
|
||||
"Armut'ta tercih danışmanlığı ilanları ortalama 1.000–3.000 TL (Eylül 2026). Ücretsiz robotların çoğu ise yalnızca liste filtreliyor; listeyi kurup gerekçesini yazmıyor.",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -185,7 +185,7 @@ const comparison = [
|
||||
{
|
||||
feature: "Fiyat",
|
||||
robot: "Ücretsiz",
|
||||
danisman: "1.000–3.000 TL",
|
||||
danisman: "1.000–3.000 TL*",
|
||||
tercihai: `${PAKET_FIYATI} TL · tek seferlik`,
|
||||
},
|
||||
];
|
||||
@@ -524,6 +524,9 @@ export default function Home() {
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<p className="mt-3 text-xs text-slate-500">
|
||||
* Danışman fiyat aralığı: Armut ortalama fiyatı, Eylül 2026.
|
||||
</p>
|
||||
</Reveal>
|
||||
</Parallax>
|
||||
</div>
|
||||
|
||||
@@ -25,7 +25,7 @@ const AI_BOTLARI = [
|
||||
"Amzn-SearchBot",
|
||||
// Model eğitimi: aramasız yanıtlarda markanın bilinmesi buradan geçer
|
||||
"GPTBot",
|
||||
"ClaudeBot",
|
||||
// ClaudeBot aşağıda kendi grubunda (Crawl-delay için); buraya yazma.
|
||||
"Google-Extended",
|
||||
"Applebot-Extended",
|
||||
"meta-externalagent",
|
||||
@@ -38,6 +38,9 @@ export default function robots(): MetadataRoute.Robots {
|
||||
rules: [
|
||||
{ userAgent: "*", allow: "/", disallow: DISALLOW },
|
||||
{ userAgent: AI_BOTLARI, allow: "/", disallow: DISALLOW },
|
||||
// Anthropic Crawl-delay'i resmî olarak destekliyor (support.claude.com);
|
||||
// eğitim taraması 1011 URL'lik katalogda saniyede 1 istekle yeter.
|
||||
{ userAgent: "ClaudeBot", allow: "/", disallow: DISALLOW, crawlDelay: 1 },
|
||||
],
|
||||
sitemap: `${SITE_URL}/sitemap.xml`,
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
getBolumBySlug,
|
||||
uniSlugFromAd,
|
||||
} from "../katalog-queries";
|
||||
import type { Program } from "@/types/yokatlas";
|
||||
import {
|
||||
JsonLd,
|
||||
breadcrumbJsonLd,
|
||||
@@ -156,6 +157,63 @@ function siraAraligiMetni({
|
||||
return verisiz > 0 ? `${aralik} ile taban verisi olmayanlar` : aralik;
|
||||
}
|
||||
|
||||
/**
|
||||
* İlk sayfanın açılış cümleleri: kaç ilde kaç program, devlet/vakıf dağılımı,
|
||||
* toplam kontenjan, taban sıralaması aralığı ve bir önceki yıla göre yön.
|
||||
* Yön için yalnızca iki yılda da sırası olan programlar sayılır; sıra sayısı
|
||||
* büyümüşse o programa daha geriden yerleşilmiş demektir (öğrenci dilinde
|
||||
* "gevşedi"). Beş programdan azsa yön cümlesi kurulmaz.
|
||||
*/
|
||||
function bolumVeriOzeti(bolum: BolumDetay, programlar: Program[]): string {
|
||||
const { stats } = bolum;
|
||||
const iller = new Set<string>();
|
||||
let kontenjan = 0;
|
||||
let gevseyen = 0;
|
||||
let sikilasan = 0;
|
||||
let karsilastirilan = 0;
|
||||
for (const p of programlar) {
|
||||
if (p.il) iller.add(p.il);
|
||||
kontenjan += p.kontenjanSon ?? 0;
|
||||
const onceki = p.siraGecmisi[p.siraGecmisi.length - 2];
|
||||
if (p.siraSon != null && onceki != null) {
|
||||
karsilastirilan += 1;
|
||||
if (p.siraSon > onceki) gevseyen += 1;
|
||||
else if (p.siraSon < onceki) sikilasan += 1;
|
||||
}
|
||||
}
|
||||
const digerTur = stats.toplam - stats.devlet - stats.vakif;
|
||||
const dagilim = [
|
||||
stats.devlet > 0 ? `${sayi(stats.devlet)} devlet` : null,
|
||||
stats.vakif > 0 ? `${sayi(stats.vakif)} vakıf` : null,
|
||||
digerTur > 0 ? `${sayi(digerTur)} KKTC/yurt dışı` : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(", ");
|
||||
const ilSayisi = iller.size > 0 ? iller.size : stats.ilSayisi;
|
||||
const cumleler = [
|
||||
`${bolum.ad} bölümü ${SON_YIL} YKS yerleştirmesinde ${sayi(ilSayisi)} ilde ${sayi(stats.toplam)} programda öğrenci aldı (${dagilim}${kontenjan > 0 ? `; toplam ${sayi(kontenjan)} kontenjan` : ""}).`,
|
||||
];
|
||||
if (stats.minSira != null && stats.maxSira != null) {
|
||||
cumleler.push(
|
||||
stats.minSira === stats.maxSira
|
||||
? `Taban başarı sıralaması ${sayi(stats.minSira)}.`
|
||||
: `Taban başarı sıralaması programa göre ${sayi(stats.minSira)} ile ${sayi(stats.maxSira)} arasında değişiyor.`,
|
||||
);
|
||||
}
|
||||
if (karsilastirilan >= 5) {
|
||||
// Yüzdeye iyelik eki gerekmeyen kalıp: "%7'sinde / %9'unda" gibi ek
|
||||
// seçimi sayının okunuşuna bağlı, şablonda güvenle kurulamaz.
|
||||
const kucOran = Math.round((sikilasan / karsilastirilan) * 100);
|
||||
const buyOran = Math.round((gevseyen / karsilastirilan) * 100);
|
||||
cumleler.push(
|
||||
kucOran >= buyOran
|
||||
? `${ONCEKI_YIL}'e göre taban sıralaması sayı olarak küçülen (yerleşmesi zorlaşan) program oranı %${kucOran}, büyüyen (kolaylaşan) %${buyOran}.`
|
||||
: `${ONCEKI_YIL}'e göre taban sıralaması sayı olarak büyüyen (yerleşmesi kolaylaşan) program oranı %${buyOran}, küçülen (zorlaşan) %${kucOran}.`,
|
||||
);
|
||||
}
|
||||
return cumleler.join(" ");
|
||||
}
|
||||
|
||||
export function bolumSayfaMetadata(slug: string, sayfa: number): Metadata {
|
||||
const bolum = getBolumBySlug(slug);
|
||||
if (!bolum) return {};
|
||||
@@ -237,6 +295,11 @@ export function BolumIcerik({ slug, sayfa }: { slug: string; sayfa: number }) {
|
||||
}
|
||||
}
|
||||
const { stats } = bolum;
|
||||
// Sayfaya özgü, veriden türeyen özet cümle (GEO: yapay zekâ cevap motorları
|
||||
// sayfa başına ilk metni saklar; 1011 katalog sayfasının aynı şablon cümleyle
|
||||
// açılması hem alıntıyı hem Google'ın "ölçekli içerik" politikasını zorlar —
|
||||
// docs/arastirma/2026-09-24-geo-seo). Rakamlar tablodakiyle aynı kaynaktan.
|
||||
const veriOzeti = ilkSayfa ? bolumVeriOzeti(bolum, tumProgramlar) : null;
|
||||
const hapSinifi =
|
||||
"inline-flex h-10 items-center gap-2 rounded-full border border-slate-200 bg-white px-4 text-sm font-semibold text-slate-800";
|
||||
|
||||
@@ -325,9 +388,8 @@ export function BolumIcerik({ slug, sayfa }: { slug: string; sayfa: number }) {
|
||||
|
||||
{ilkSayfa ? (
|
||||
<p className="mx-auto mt-6 max-w-3xl text-center text-base leading-relaxed text-slate-600 sm:text-lg lg:mx-0 lg:text-left">
|
||||
{bolum.ad} bölümünü açan {sayi(tumProgramlar.length)} üniversite
|
||||
programının {SON_YIL} taban puanlarını, başarı sıralamalarını ve
|
||||
kontenjanlarını incele.{" "}
|
||||
{veriOzeti} Aşağıdaki tabloda her programın taban puanı, başarı
|
||||
sıralaması ve kontenjanı var.{" "}
|
||||
<em>
|
||||
{TERCIH_YILI} tabanları, yerleştirme sonuçları açıklandığında
|
||||
netleşir.
|
||||
|
||||
@@ -74,7 +74,8 @@ export function RehberMakale({ slug }: { slug: string }) {
|
||||
{yazi.aciklama}
|
||||
</p>
|
||||
<p className="mt-3 text-xs text-slate-500">
|
||||
Yayın: <time dateTime={yazi.tarih}>{trTarih(yazi.tarih)}</time>
|
||||
Yazan: KolayTercih Ekibi · Yayın:{" "}
|
||||
<time dateTime={yazi.tarih}>{trTarih(yazi.tarih)}</time>
|
||||
{yazi.guncelleme ? (
|
||||
<>
|
||||
{" · "}Güncelleme:{" "}
|
||||
|
||||
@@ -153,20 +153,38 @@ export function articleJsonLd(opts: {
|
||||
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: `${SITE_URL}${opts.path}`,
|
||||
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`,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user