perf: sunucu waterfall'ları ve blocking analitik düzeltildi (Faz 1)
Vercel React Best Practices denetimi, sunucu tarafı: - odeme: analitik olayı after() ile yanıt sonrasına alındı; ucuz ürün doğrulaması oturum turundan öne çekildi; sonuç kartında oturum+sipariş paralel okunuyor - /api/soru POST: gövde+oturum ve 3 bağımsız sorgu Promise.all'a bağlandı - rehber: prod'da globalThis cache + özetler için frontmatter fast-path (24 dosya okuma + marked.parse her render'da tekrarlanıyordu); arama kutusuna giden payload RehberAramaKaydi'ya kırpıldı (tarih serileşmiyor) - /sonuc hattı: ProgramListeSatiri (fakulte/puan2025 client'a serileşmiyor) - katalog: universite/bolum listelerinde O(n²) filtreler tek geçişe indi - sira-gerekli + katalog-arama: effect'te setState kalktı (lint yeşil) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,13 @@ export type RehberOzet = {
|
||||
tarih: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Header'daki ⌘K aramasına giden asgari alan seti. `tarih` client'ta hiç
|
||||
* okunmadığı için RSC payload'ına taşınmaz (root layout üzerinden her
|
||||
* sayfaya serileşiyordu).
|
||||
*/
|
||||
export type RehberAramaKaydi = Pick<RehberOzet, "slug" | "baslik" | "aciklama">;
|
||||
|
||||
export type RehberSema = {
|
||||
/** Mermaid tanımı (aria/altyazi yönergeleri ayıklanmış hâli) */
|
||||
tanim: string;
|
||||
@@ -40,6 +47,16 @@ export type RehberYazi = RehberOzet & { parcalar: RehberParca[] };
|
||||
|
||||
const REHBER_DIZIN = path.join(process.cwd(), "content", "rehber");
|
||||
|
||||
// SiteHeader root layout'ta olduğu için getAllRehberler her sayfa
|
||||
// render/prerender'ında koşuyor; cache'siz hâli sayfa başına 24 dosya okuma +
|
||||
// 24 marked.parse demekti. Yalnızca prod'da cache'lenir — dev'de içerik
|
||||
// düzenlenirken taze fs okuması korunur (katalog.ts/db.ts globalThis deseni).
|
||||
const globalRehber = globalThis as unknown as {
|
||||
rehberYazilar?: Map<string, RehberYazi | null>;
|
||||
rehberOzetler?: RehberOzet[];
|
||||
};
|
||||
const CACHE_AKTIF = process.env.NODE_ENV === "production";
|
||||
|
||||
function parseFrontmatter(raw: string): {
|
||||
meta: Record<string, string>;
|
||||
body: string;
|
||||
@@ -105,21 +122,62 @@ function parcala(slug: string, body: string): RehberParca[] {
|
||||
return parcalar;
|
||||
}
|
||||
|
||||
function oku(slug: string): RehberYazi | null {
|
||||
const dosya = path.join(REHBER_DIZIN, `${slug}.md`);
|
||||
if (!fs.existsSync(dosya)) return null;
|
||||
const { meta, body } = parseFrontmatter(fs.readFileSync(dosya, "utf8"));
|
||||
function metaDogrula(
|
||||
slug: string,
|
||||
meta: Record<string, string>,
|
||||
): asserts meta is { baslik: string; aciklama: string; tarih: string } {
|
||||
if (!meta.baslik || !meta.aciklama || !meta.tarih) {
|
||||
throw new Error(
|
||||
`rehber: ${slug}.md frontmatter eksik (baslik/aciklama/tarih zorunlu)`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function oku(slug: string): RehberYazi | null {
|
||||
if (CACHE_AKTIF) {
|
||||
const onbellek = globalRehber.rehberYazilar?.get(slug);
|
||||
if (onbellek !== undefined) return onbellek;
|
||||
}
|
||||
const dosya = path.join(REHBER_DIZIN, `${slug}.md`);
|
||||
let yazi: RehberYazi | null = null;
|
||||
if (fs.existsSync(dosya)) {
|
||||
const { meta, body } = parseFrontmatter(fs.readFileSync(dosya, "utf8"));
|
||||
metaDogrula(slug, meta);
|
||||
yazi = {
|
||||
slug,
|
||||
baslik: meta.baslik,
|
||||
aciklama: meta.aciklama,
|
||||
tarih: meta.tarih,
|
||||
parcalar: parcala(slug, body),
|
||||
};
|
||||
}
|
||||
if (CACHE_AKTIF) {
|
||||
(globalRehber.rehberYazilar ??= new Map()).set(slug, yazi);
|
||||
}
|
||||
return yazi;
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste görünümleri için fast-path: yalnızca frontmatter parse edilir,
|
||||
* `parcala()`/`marked.parse` hiç çağrılmaz (özetler `parcalar`ı kullanmıyor).
|
||||
* Tam parse zaten cache'teyse dosyaya inmeden onu kırpar.
|
||||
*/
|
||||
function ozetOku(slug: string): RehberOzet | null {
|
||||
const tam = CACHE_AKTIF ? globalRehber.rehberYazilar?.get(slug) : undefined;
|
||||
if (tam !== undefined) {
|
||||
if (tam === null) return null;
|
||||
const { slug: s, baslik, aciklama, tarih } = tam;
|
||||
return { slug: s, baslik, aciklama, tarih };
|
||||
}
|
||||
const dosya = path.join(REHBER_DIZIN, `${slug}.md`);
|
||||
if (!fs.existsSync(dosya)) return null;
|
||||
const { meta } = parseFrontmatter(fs.readFileSync(dosya, "utf8"));
|
||||
metaDogrula(slug, meta);
|
||||
return {
|
||||
slug,
|
||||
baslik: meta.baslik,
|
||||
aciklama: meta.aciklama,
|
||||
tarih: meta.tarih,
|
||||
parcalar: parcala(slug, body),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -139,14 +197,22 @@ export function getRehber(slug: string): RehberYazi | null {
|
||||
}
|
||||
|
||||
export function getAllRehberler(): RehberOzet[] {
|
||||
return getAllRehberSlugs()
|
||||
.map((slug) => oku(slug))
|
||||
.filter((y): y is RehberYazi => y !== null)
|
||||
.map(({ slug, baslik, aciklama, tarih }) => ({
|
||||
slug,
|
||||
baslik,
|
||||
aciklama,
|
||||
tarih,
|
||||
}))
|
||||
if (CACHE_AKTIF && globalRehber.rehberOzetler) {
|
||||
return globalRehber.rehberOzetler;
|
||||
}
|
||||
const ozetler = getAllRehberSlugs()
|
||||
.map((slug) => ozetOku(slug))
|
||||
.filter((y): y is RehberOzet => y !== null)
|
||||
.sort((a, b) => b.tarih.localeCompare(a.tarih));
|
||||
if (CACHE_AKTIF) globalRehber.rehberOzetler = ozetler;
|
||||
return ozetler;
|
||||
}
|
||||
|
||||
/** Header aramasının RSC payload'ına giden kırpılmış liste. */
|
||||
export function getRehberAramaKayitlari(): RehberAramaKaydi[] {
|
||||
return getAllRehberler().map(({ slug, baslik, aciklama }) => ({
|
||||
slug,
|
||||
baslik,
|
||||
aciklama,
|
||||
}));
|
||||
}
|
||||
Reference in New Issue
Block a user