perf: istemci istek cache'i (dedup+TTL) ve profil cache (Faz 5)
- lib/istek-cache: modül seviyesi in-flight dedup + 5dk TTL cache (SWR bağımlılığı olmadan); caller iptali paylaşılan fetch'i düşürmez - /api/facetler'in 5 çağrı yeri (hero, cta, kapı, sihirbaz adımları, demo) ve /api/katalog-ara cache'e bağlandı — sihirbazda 2↔3 adım geçişi ve tekrarlanan aramalar artık ağa çıkmıyor; /api/soru GET dedup-only (ttl 0) - katalog-arama: elle yukleniyor state'i kalktı, "cevaplanmamış sorgu" türetmesi + startTransition; rehber puanlaması Schwartzian; ⌘K listener'ı useEffectEvent ile tek sefer bağlanıyor - sihirbaz-adimlar: il sort/filter useMemo (çip başına 81 eleman kopyalanıp sıralanıyordu, harita dizisi referansı sabitlendi) - sohbet-client: stream chunk güncellemeleri startTransition'da — cevap akarken input yazımı bloklanmıyor - sihirbaz-profil: modül cache + event/storage geçersizlemesi (tek "+" tıklamasında 3 localStorage okuma + JSON.parse vardı) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,7 @@ import {
|
||||
} from "./sihirbaz-adimlar-lazy";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { olay, siraKovasi } from "@/lib/analitik";
|
||||
import { onbellekliJsonGetir } from "@/lib/istek-cache";
|
||||
import { useTercihProfili } from "@/features/liste/hooks/use-tercih-profili";
|
||||
import type { SihirbazFacetleri } from "@/types/yokatlas";
|
||||
import { sonucHref, type SihirbazSecimleri } from "@/features/sihirbaz/sihirbaz-sabitler";
|
||||
@@ -91,12 +92,12 @@ export function CtaSiraForm({ baslik }: { baslik?: string }) {
|
||||
try {
|
||||
// Oturum bilgisi facet isteğiyle PARALEL çözülür; sayfa statik kalır
|
||||
// (hero formundaki desenle aynı — bkz. hero-form.tsx).
|
||||
const [res, oturum] = await Promise.all([
|
||||
fetch(`/api/facetler?sira=${value}&tur=${tur}`),
|
||||
const [veri, oturum] = await Promise.all([
|
||||
onbellekliJsonGetir<{ facetler: SihirbazFacetleri }>(
|
||||
`/api/facetler?sira=${value}&tur=${tur}`,
|
||||
),
|
||||
authClient.getSession().catch(() => null),
|
||||
]);
|
||||
if (!res.ok) throw new Error();
|
||||
const veri = (await res.json()) as { facetler: SihirbazFacetleri };
|
||||
setGirisli(Boolean(oturum?.data?.user));
|
||||
setSira(value);
|
||||
setFacetler(veri.facetler);
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
} from "./sihirbaz-adimlar-lazy";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { olay, siraKovasi } from "@/lib/analitik";
|
||||
import { onbellekliJsonGetir } from "@/lib/istek-cache";
|
||||
import {
|
||||
profilDuzenlemeyiAc,
|
||||
useTercihProfili,
|
||||
@@ -67,12 +68,13 @@ export function HeroForm() {
|
||||
try {
|
||||
// Oturum bilgisi mount'ta değil burada, facet isteğiyle PARALEL çözülür:
|
||||
// landing ziyareti başına gereksiz auth turu atılmaz (sayfa statik kalır).
|
||||
const [res, oturum] = await Promise.all([
|
||||
fetch(`/api/facetler?sira=${value}&tur=${tur}`),
|
||||
// Facetler dedup+TTL cache'li: aynı sıra/tür tekrar girilirse ağa çıkmaz.
|
||||
const [veri, oturum] = await Promise.all([
|
||||
onbellekliJsonGetir<{ facetler: SihirbazFacetleri }>(
|
||||
`/api/facetler?sira=${value}&tur=${tur}`,
|
||||
),
|
||||
authClient.getSession().catch(() => null),
|
||||
]);
|
||||
if (!res.ok) throw new Error();
|
||||
const veri = (await res.json()) as { facetler: SihirbazFacetleri };
|
||||
setGirisli(Boolean(oturum?.data?.user));
|
||||
setSira(value);
|
||||
setFacetler(veri.facetler);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
// Adımlar kademeli süzülür: 2. adımın illeri seçili kategorilere, 3. adımın
|
||||
// üniversite tipi sayıları kategori+il'e göre /api/facetler'den tazelenir.
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { ArrowLeft, ArrowRight, X } from "lucide-react";
|
||||
import { useReducedMotion } from "motion/react";
|
||||
import useMeasure from "react-use-measure";
|
||||
@@ -17,6 +17,7 @@ import { TransitionPanel } from "@/components/core/transition-panel";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { IlSecimHaritasi } from "./il-secim-haritasi";
|
||||
import { normalizeIlAdi } from "@/lib/harita";
|
||||
import { onbellekliJsonGetir } from "@/lib/istek-cache";
|
||||
import { olay } from "@/lib/analitik";
|
||||
import { trBaslikDuzeni } from "@/lib/slug";
|
||||
import type { SihirbazFacetleri } from "@/types/yokatlas";
|
||||
@@ -85,6 +86,17 @@ export function SihirbazAdimlar({
|
||||
// tipi sayıları adıma girerken o anki seçimlerle tazelenir. Fetch başarısız
|
||||
// olursa eldeki (daha geniş) liste kalır — sihirbaz asla kilitlenmez.
|
||||
const [canliFacetler, setCanliFacetler] = useState(facetler);
|
||||
// 81 elemanlı listeyi her çip tıklamasında yeniden sıralamamak/filtrelememek
|
||||
// için memo; haritaya giden dizinin referansı da sabit kalır (gereksiz
|
||||
// IlSecimHaritasi render'ı biter).
|
||||
const siraliIller = useMemo(
|
||||
() => [...canliFacetler.iller].sort((a, b) => b.adet - a.adet),
|
||||
[canliFacetler.iller],
|
||||
);
|
||||
const haritaIlleri = useMemo(
|
||||
() => canliFacetler.iller.filter((i) => HARITA_ILLERI.has(i.il)),
|
||||
[canliFacetler.iller],
|
||||
);
|
||||
const [facetYukleniyor, setFacetYukleniyor] = useState(false);
|
||||
useEffect(() => {
|
||||
if (adim === 0) return;
|
||||
@@ -94,10 +106,12 @@ export function SihirbazAdimlar({
|
||||
if (adim === 2) for (const i of iller) params.append("il", i);
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect -- fetch başlangıç işareti
|
||||
setFacetYukleniyor(true);
|
||||
fetch(`/api/facetler?${params}`, { signal: ctrl.signal })
|
||||
.then((res) => (res.ok ? res.json() : null))
|
||||
.then((veri: { facetler: SihirbazFacetleri } | null) => {
|
||||
if (!veri) return;
|
||||
// Cache'li: 2↔3 adımları arasında gidip gelmek aynı URL'i yeniden çekmez
|
||||
onbellekliJsonGetir<{ facetler: SihirbazFacetleri }>(
|
||||
`/api/facetler?${params}`,
|
||||
{ signal: ctrl.signal },
|
||||
)
|
||||
.then((veri) => {
|
||||
setCanliFacetler((mevcut) => ({
|
||||
...mevcut,
|
||||
iller: veri.facetler.iller,
|
||||
@@ -250,9 +264,7 @@ export function SihirbazAdimlar({
|
||||
facetYukleniyor ? "animate-pulse opacity-60" : ""
|
||||
}`}
|
||||
>
|
||||
{[...canliFacetler.iller]
|
||||
.sort((a, b) => b.adet - a.adet)
|
||||
.map((i) => (
|
||||
{siraliIller.map((i) => (
|
||||
<Cip
|
||||
key={i.il}
|
||||
secili={iller.includes(i.il)}
|
||||
@@ -286,7 +298,7 @@ export function SihirbazAdimlar({
|
||||
{/* Harita geniş ekranda görsel destek olarak kalır */}
|
||||
<div className="mt-4 hidden sm:block">
|
||||
<IlSecimHaritasi
|
||||
iller={canliFacetler.iller.filter((i) => HARITA_ILLERI.has(i.il))}
|
||||
iller={haritaIlleri}
|
||||
secili={iller}
|
||||
onToggle={(il) => listeDegistir(iller, setIller, il, 5)}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user