jskelet 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/AGENTS.md +127 -0
  2. package/CHANGELOG.md +40 -0
  3. package/LICENSE +21 -0
  4. package/README.md +342 -0
  5. package/bin/jskelet.mjs +104 -0
  6. package/docs/01-baslangic.md +285 -0
  7. package/docs/02-mimari.md +287 -0
  8. package/docs/03-routing.md +437 -0
  9. package/docs/04-render-ve-sablonlar.md +490 -0
  10. package/docs/05-islands.md +429 -0
  11. package/docs/06-cache.md +409 -0
  12. package/docs/07-yapilandirma.md +673 -0
  13. package/docs/08-build.md +366 -0
  14. package/docs/09-dev-araclari.md +302 -0
  15. package/docs/10-dagitim.md +329 -0
  16. package/docs/11-tasima.md +352 -0
  17. package/docs/README.md +82 -0
  18. package/package.json +97 -0
  19. package/src/build/build.mjs +138 -0
  20. package/src/build/ensure-build.mjs +15 -0
  21. package/src/build/paths.mjs +118 -0
  22. package/src/build/resolve-peer.mjs +36 -0
  23. package/src/build/tasks/client.mjs +268 -0
  24. package/src/build/tasks/css.mjs +124 -0
  25. package/src/build/tasks/fonts.mjs +146 -0
  26. package/src/build/tasks/icons.mjs +224 -0
  27. package/src/build/tasks/images.mjs +244 -0
  28. package/src/build/tasks/precompress.mjs +78 -0
  29. package/src/client/devtools/overlay.js +1763 -0
  30. package/src/client/devtools/report.html +185 -0
  31. package/src/client/devtools/report.js +712 -0
  32. package/src/client/dom.js +95 -0
  33. package/src/client/index.js +26 -0
  34. package/src/client/registry.js +223 -0
  35. package/src/client/safe-image.js +91 -0
  36. package/src/client/store.js +36 -0
  37. package/src/config/defaults.js +102 -0
  38. package/src/config/index.js +433 -0
  39. package/src/config/pattern.js +107 -0
  40. package/src/dev-server.mjs +383 -0
  41. package/src/http/control-flow.js +56 -0
  42. package/src/http/request-cache.js +46 -0
  43. package/src/index.js +35 -0
  44. package/src/init.mjs +220 -0
  45. package/src/log.mjs +332 -0
  46. package/src/logo.png +0 -0
  47. package/src/runtime/alias-hooks.mjs +119 -0
  48. package/src/runtime/register.mjs +4 -0
  49. package/src/server/assets.js +119 -0
  50. package/src/server/create-app.js +167 -0
  51. package/src/server/dev/devtools.js +383 -0
  52. package/src/server/dev/report.js +351 -0
  53. package/src/server/head-hints.js +132 -0
  54. package/src/server/html-cache.js +166 -0
  55. package/src/server/metadata.js +102 -0
  56. package/src/server/middleware/compression.js +205 -0
  57. package/src/server/middleware/dev-gate.js +62 -0
  58. package/src/server/middleware/headers.js +37 -0
  59. package/src/server/middleware/redirects.js +32 -0
  60. package/src/server/middleware/static-precompressed.js +100 -0
  61. package/src/server/middleware/upstream-proxy.js +141 -0
  62. package/src/server/prewarm.js +283 -0
  63. package/src/server/render.js +356 -0
  64. package/src/server/router.js +121 -0
  65. package/src/server/status-page.js +164 -0
  66. package/src/server/upstream-tracking.js +51 -0
  67. package/src/start.mjs +7 -0
  68. package/src/templates/layout.ejs +44 -0
  69. package/src/version.mjs +17 -0
  70. package/src/views/components/loader.js +85 -0
  71. package/src/views/helpers/html.js +102 -0
  72. package/src/views/helpers/tags.js +193 -0
@@ -0,0 +1,409 @@
1
+ # 06 — Önbellek ve prewarm
2
+
3
+ Bu belge JSkelet'in ISR ikamesini bütün ayrıntılarıyla anlatır: HTML TTL
4
+ önbelleği ve stale-while-revalidate davranışı, `revalidate`'in nereden geldiği,
5
+ cache anahtarının nasıl kurulduğu, `X-JSkelet-Cache` başlığının değerleri,
6
+ sıkıştırılmış gövdenin neden önbellekte durduğu, istek içi memoizasyon
7
+ (`withRequestCache` / `cache()`), upstream hatalarının önbelleği nasıl
8
+ etkilediği (`reportUpstreamFailure`) ve sunucu açılışındaki ısıtma turu.
9
+ Kararların arkasındaki ölçüm gerekçeleri [02-mimari.md](./02-mimari.md)'de,
10
+ config alanlarının tam referansı [07-yapilandirma.md](./07-yapilandirma.md)'de.
11
+
12
+ ## Genel resim
13
+
14
+ ```
15
+ route(controller, { revalidate })
16
+ └─ withHtmlCache(key, ttl, producer) ← TTL + stale-while-revalidate
17
+ └─ withUpstreamTracking(...) ← eksik veri tespiti
18
+ └─ withRequestCache(...) ← istek içi memoizasyon
19
+ └─ produce() → controller + renderPage
20
+ ```
21
+
22
+ Sıra önemli: **istek içi cache en içte** olmalı ki aynı render'daki iki çağrı
23
+ tek upstream isteğine düşsün; **upstream takibi HTML cache'in içinde** olmalı ki
24
+ eksik veriyle üretilen çıktı önbelleğe yazılmasın.
25
+
26
+ ## `revalidate` — TTL nereden gelir
27
+
28
+ Bir route'un TTL'i iki kaynaktan gelebilir ve **config kazanır**:
29
+
30
+ 1. `route(controller, { revalidate: 60 })` — route'un kendi süresi.
31
+ 2. `jskelet.config.mjs` → `cache().html` içindeki eşleşen desen. Varsa
32
+ route'unkini ezer.
33
+
34
+ ```js
35
+ // jskelet.config.mjs
36
+ export default {
37
+ async cache() {
38
+ return {
39
+ html: {
40
+ "/": 60,
41
+ "/haber/:slug": 300,
42
+ "/etiket/:slug": 120,
43
+ },
44
+ };
45
+ },
46
+ };
47
+ ```
48
+
49
+ Config ile ezme, tek bir dosyadan tüm sitenin tazelik profilini ayarlamayı
50
+ mümkün kılar; route dosyalarını dolaşmak gerekmez.
51
+
52
+ Çözüm sonucu **yol başına hatırlanır**, böylece her istekte desen taraması
53
+ yapılmaz. Hiç `cache().html` kuralı yoksa doğrudan route'un değeri kullanılır.
54
+
55
+ `revalidate` verilmemişse ya da 0 ise sayfa **hiç önbelleklenmez**: her istek
56
+ render edilir ve yanıta `Cache-Control` yazılmaz (yalnızca
57
+ `X-JSkelet-Cache: MISS`).
58
+
59
+ Önbellek ayrıca yalnızca `GET` istekleri için devreye girer.
60
+
61
+ ## Cache anahtarı
62
+
63
+ ```
64
+ `${req.path}?${new URLSearchParams(query).toString()}`
65
+ ```
66
+
67
+ Yani yol **ve tüm query parametreleri** anahtarın parçasıdır. `/liste?sayfa=2`
68
+ ile `/liste?sayfa=3` ayrı girdilerdir.
69
+
70
+ Bunun pratik sonucu: query string'e bağlı olmayan bir sayfa, farklı kampanya
71
+ parametreleriyle (`?utm_source=…`) çağrıldığında her kombinasyon için ayrı bir
72
+ girdi üretir. Bu tür parametreleri ters proxy katmanında temizlemek ya da
73
+ önbelleği kapatmak (`revalidate` vermemek) makul bir önlemdir; store en fazla
74
+ 500 girdi tutar ve LRU ile en eskiyi düşürür.
75
+
76
+ ## Stale-while-revalidate
77
+
78
+ Girdi yapısı:
79
+
80
+ ```
81
+ expiresAt = now + ttl
82
+ staleUntil = now + ttl * 2 (STALE_FACTOR = 1)
83
+ ```
84
+
85
+ Okuma davranışı:
86
+
87
+ | Durum | Yanıt | Arka plan |
88
+ | --- | --- | --- |
89
+ | `now < expiresAt` | Önbellekteki HTML, `HIT` | — |
90
+ | `expiresAt ≤ now < staleUntil` | Önbellekteki HTML **anında**, `STALE` | Tazeleme başlatılır |
91
+ | `now ≥ staleUntil` | Girdi silinir, taze render, `MISS` | — |
92
+
93
+ Stale penceresinde tazelemenin hatası isteği etkilemez: eski HTML pencere
94
+ boyunca geçerli kalır ve hata yalnızca loglanır
95
+ (`[html-cache] arka plan tazelemesi başarısız: …`).
96
+
97
+ Aynı anahtar için eşzamanlı tazelemeler tek bir çalışmada birleştirilir
98
+ (`inflight` haritası): yüz eşzamanlı istek tek render'a düşer.
99
+
100
+ Kazanç: ilk ısıtmadan sonra hiçbir istek render'ı beklemez. Bedel: HTML'deki
101
+ veri en fazla `revalidate + bir tazeleme turu` kadar geride olabilir. Bu bedel
102
+ kabul edilebilir, çünkü fiyat gibi canlı alanlar istemcide WebSocket'ten
103
+ güncelleniyor.
104
+
105
+ Store LRU'dur: erişilen girdi sona taşınır, `MAX_ENTRIES = 500` aşılınca en
106
+ eski düşürülür.
107
+
108
+ ## Ne önbelleğe yazılır
109
+
110
+ Yalnızca şu iki koşulun **ikisini** birlikte sağlayan çıktı saklanır:
111
+
112
+ 1. `status === 200`
113
+ 2. `degraded !== true` — render sırasında geçici bir upstream hatası
114
+ bildirilmemiş.
115
+
116
+ Yani 404 sayfaları, redirect'ler ve eksik veriyle üretilmiş HTML önbelleğe
117
+ girmez.
118
+
119
+ ## Yanıt başlıkları
120
+
121
+ `route()` her yanıta `X-JSkelet-Cache` yazar (başlık adı `brand.cacheHeader`
122
+ ile değiştirilebilir):
123
+
124
+ | Değer | Anlamı |
125
+ | --- | --- |
126
+ | `HIT` | Önbellekten, taze |
127
+ | `STALE` | Önbellekten, süresi geçmiş; arkada tazeleniyor |
128
+ | `MISS` | Bu istekte render edildi (ya da önbellek kapalı) |
129
+
130
+ Önbelleklenebilir yanıtlarda ayrıca:
131
+
132
+ ```
133
+ Cache-Control: public, max-age=0, s-maxage=<revalidate>, stale-while-revalidate=60
134
+ ```
135
+
136
+ `max-age=0` tarayıcıda saklamayı kapatır, `s-maxage` ara katmanlara (CDN, ters
137
+ proxy) süreyi bildirir. Böylece CDN önünde durduğunda aynı tazelik modeli iki
138
+ katmanda birlikte çalışır.
139
+
140
+ ## Sıkıştırılmış gövdenin saklanması
141
+
142
+ Önbelleğe alınan her girdi bir `encoded` haritası taşır ve HTML ile aynı ömrü
143
+ paylaşır. Bir sayfa ilk kez brotli ya da gzip ile istendiğinde çıktı hesaplanıp
144
+ haritaya konur; sonraki isteklerde aynı buffer gönderilir. Aynı sayfa her
145
+ istekte yeniden brotli'lenmez.
146
+
147
+ Bu yolda `Content-Encoding`, `Vary` ve `Content-Length` doğrudan `route()`
148
+ tarafından yazılır; sıkıştırma middleware'i `Content-Encoding` gördüğü için
149
+ devreye girmez.
150
+
151
+ `HEAD` istekleri sıkıştırılmaz (gövde yok). İstemci ne brotli ne gzip kabul
152
+ ediyorsa düz HTML gönderilir.
153
+
154
+ ## İstek içi memoizasyon: `cache()`
155
+
156
+ React'in `cache()` fonksiyonunun karşılığı: aynı istek içinde aynı argümanlarla
157
+ yapılan çağrılar tek kez çalışır.
158
+
159
+ ```js
160
+ // lib/api/articles.js
161
+ import { cache } from "jskelet";
162
+
163
+ export const getArticle = cache(async (slug) => {
164
+ const response = await fetch(`${process.env.API_ORIGIN}/articles/${slug}`);
165
+ return response.json();
166
+ });
167
+ ```
168
+
169
+ Artık aynı render'da hem controller hem `hooks.layoutContext()` aynı yazıyı
170
+ isterse tek upstream isteği yapılır.
171
+
172
+ Ayrıntılar:
173
+
174
+ - Bağlam `AsyncLocalStorage` ile taşınır ve `route()` içinde
175
+ `withRequestCache()` tarafından kurulur.
176
+ - **Bağlam yoksa memoizasyon devre dışı kalır** ve fonksiyon doğrudan çağrılır.
177
+ Bir script'ten ya da başka bir sürecin içinden çağırmak güvenlidir.
178
+ - Anahtar `JSON.stringify(args)`; argümansız çağrılar `""` anahtarını
179
+ paylaşır. Serileştirilemeyen argümanlar (fonksiyon, `Symbol`, döngüsel nesne)
180
+ ile kullanmayın.
181
+ - Saklanan şey fonksiyonun **dönüş değeridir**, yani `async` fonksiyonlarda
182
+ Promise'in kendisi. Aynı Promise paylaşıldığı için eşzamanlı çağrılar da
183
+ birleşir.
184
+ - `withRequestCache(run)` dışa açıktır; `route()` dışında (ör. kendi yazdığınız
185
+ bir Express handler'ında) aynı kapsamı kurmak için kullanılabilir.
186
+
187
+ ## Degraded render: `reportUpstreamFailure`
188
+
189
+ Render sırasında upstream düştüyse çıktı eksik veri içeriyor demektir. Böyle bir
190
+ HTML'i tüm TTL boyunca servis etmek yerine önbelleğe **hiç yazmamak** doğru
191
+ davranış: sonraki istek yeniden dener.
192
+
193
+ Bağımlılık yönü bilinçli olarak tersine çevrilmiştir: framework veri katmanını
194
+ tanımaz, veri katmanı framework'e haber verir. Hiç çağıran olmazsa maliyet boş
195
+ bir dizidir.
196
+
197
+ ```js
198
+ // lib/api/client.js
199
+ import { reportUpstreamFailure } from "jskelet";
200
+
201
+ export async function apiGet(path) {
202
+ try {
203
+ const response = await fetch(`${process.env.API_ORIGIN}${path}`);
204
+
205
+ if (!response.ok) {
206
+ reportUpstreamFailure({ status: response.status, path });
207
+ return null;
208
+ }
209
+
210
+ return response.json();
211
+ } catch (error) {
212
+ // Yanıt hiç gelmedi: status 0 ağ hatası anlamına gelir.
213
+ reportUpstreamFailure({ status: 0, path });
214
+ return null;
215
+ }
216
+ }
217
+ ```
218
+
219
+ ### Geçici ve kalıcı hata ayrımı
220
+
221
+ | Durum | Sayılır | Sonuç |
222
+ | --- | --- | --- |
223
+ | `0` (ağ hatası), `408`, `425`, `429`, `>= 500` | **Geçici** | Sayfa önbelleğe yazılmaz, uyarı: `[render] <yol> eksik veriyle üretildi, önbelleğe alınmıyor (…)` |
224
+ | Diğerleri (`400`, `403`, `404`, …) | **Kalıcı** | Yalnızca uyarı: `[render] <yol> eksik veriyle üretildi, upstream kalıcı hata veriyor (…)`. Önbellek engellenmez. |
225
+
226
+ Kalıcı hataların önbelleği engellememesi bilinçli: deterministik cevaplar tekrar
227
+ denemekle düzelmez. Onlar yüzünden önbelleği kapatmak sayfayı her ziyarette
228
+ baştan render etmek olur — içerik yine aynı eksik hâliyle döner, ziyaretçi
229
+ sadece render süresini öder.
230
+
231
+ ## Önbelleği yönetmek
232
+
233
+ `jskelet` şu fonksiyonları dışa açar:
234
+
235
+ | Fonksiyon | Ne yapar |
236
+ | --- | --- |
237
+ | `withHtmlCache(key, ttlSeconds, producer)` | Önbelleği doğrudan kullanmak için. `ttlSeconds` 0 ise producer her zaman çalışır. |
238
+ | `clearHtmlCache()` | Store'u tamamen boşaltır. |
239
+ | `getHtmlCacheSize()` | Girdi sayısı. |
240
+ | `getHtmlCacheEntries()` | Döküm: `{ key, bytes, status, stale, expiresIn, encodings }`. HTML gövdesi dönmez, yalnızca boyutu. |
241
+
242
+ Bir yönetim ucu yazmak için:
243
+
244
+ ```js
245
+ import { clearHtmlCache, getHtmlCacheEntries } from "jskelet";
246
+
247
+ export default function register(app) {
248
+ app.post("/_admin/cache/temizle", (req, res) => {
249
+ if (req.headers["x-admin-token"] !== process.env.ADMIN_TOKEN) {
250
+ res.status(404).end();
251
+ return;
252
+ }
253
+ clearHtmlCache();
254
+ res.json({ ok: true });
255
+ });
256
+
257
+ app.get("/_admin/cache", (req, res) => {
258
+ res.json(getHtmlCacheEntries());
259
+ });
260
+ }
261
+ ```
262
+
263
+ Dev sunucusu ayrıca manifest her değiştiğinde önbelleği kendiliğinden temizler:
264
+ saklanan HTML eski hash'li varlık URL'lerini taşıyor olur ve temizlenmezse sayfa
265
+ silinmiş dosyayı istemeye devam eder ([09-dev-araclari.md](./09-dev-araclari.md)).
266
+
267
+ Önbellek süreç belleğinde yaşadığı için birden fazla süreç/kopya çalıştırıyorsanız
268
+ her birinin kendi önbelleği olur; `clearHtmlCache()` yalnızca çağrıldığı süreci
269
+ etkiler.
270
+
271
+ ## Prewarm — açılışta ısıtma
272
+
273
+ Next'teki build-time prerender'ın karşılığı, ama çıktı diske yazılmaz: önbellek
274
+ süreç belleğinde yaşadığı için ısıtma da süreç ayağa kalkınca yapılır. Kazanç
275
+ aynı — ilk ziyaretçi soğuk render'ı beklemez — fakat veri dondurulmaz; her girdi
276
+ route'un `revalidate` süresiyle yaşlanır ve stale-while-revalidate ile arkada
277
+ tazelenir.
278
+
279
+ Isıtma **gerçek HTTP istekleriyle** yapılır (`http://127.0.0.1:<port>`), çünkü
280
+ cache anahtarı, sıkıştırma ve middleware zinciri normal trafikle bire bir aynı
281
+ olsun.
282
+
283
+ ### `hooks.prewarmPaths()`
284
+
285
+ Hangi yolların ısıtılacağını uygulama bildirir; genelde sitemap üreten
286
+ fonksiyonun aynısıdır.
287
+
288
+ ```js
289
+ // jskelet.config.mjs
290
+ export default {
291
+ hooks: {
292
+ async prewarmPaths() {
293
+ const slugs = await getAllArticleSlugs();
294
+ return ["/", "/piyasalar", ...slugs.map((slug) => `/haber/${slug}`)];
295
+ },
296
+ },
297
+ };
298
+ ```
299
+
300
+ Kurallar:
301
+
302
+ - Dizi döndürmezse uyarı basılır ve ısıtma yapılmaz.
303
+ - Yalnızca `/` ile başlayan string'ler alınır.
304
+ - `prewarmSkip` öneklerinden biriyle başlayanlar atlanır. Varsayılan liste:
305
+ `/api/`, `/_fragment/`, `/__jskelet/`. Oturuma bağlı sayfalar ısıtılmamalı.
306
+ - Tekilleştirme **sırayı korur**: liste `PREWARM_MAX` ile budandığı için
307
+ uygulamanın verdiği öncelik sırası anlamlıdır — en önemli sayfaları başa
308
+ koyun.
309
+ - Bu hook tanımlı değilse ısıtma hiç kurulmaz; zamanlayıcı bile açılmaz.
310
+
311
+ ### Tur mantığı
312
+
313
+ 1. Liste toplanır, `PREWARM_MAX` (varsayılan 400) ile budanır.
314
+ 2. `PREWARM_CONCURRENCY` işçi paralel olarak istek atar (prod'da 4, dev'de 2).
315
+ Dev'de daha az paralellik: tarama, o an tarayıcıda açtığın sayfanın
316
+ render'ıyla CPU için yarışmasın.
317
+ 3. Başarısız yollar için **tek seri tekrar turu** yapılır (`concurrency: 1`).
318
+ Hatalar çoğunlukla upstream rate limit'i (429): ilk tur yüzlerce sayfayı aynı
319
+ anda çekerken API'yi zorluyor. Tekrar turu bu sayfaların önbelleğe girmesini
320
+ sağlıyor; aksi hâlde ziyaretçi soğuk render'ı öder.
321
+ 4. Özet loglanır:
322
+ `[prewarm] 128/130 sayfa ısıtıldı, 2 hata, 5 sayfa tekrar turunda kurtarıldı (12.4s)`
323
+
324
+ İstekler `user-agent: jskelet-prewarm` (`brand.prewarmUserAgent`) ve
325
+ `accept-encoding: br, gzip` başlıklarıyla gider; ikincisi sıkıştırılmış gövdenin
326
+ de önbelleğe girmesi için.
327
+
328
+ `DEV_TOKEN` ayarlıysa ısıtma token'ı çerez olarak taşır; yoksa dev gate tüm
329
+ sayfalara 404 döner ve önbellek hiç dolmaz.
330
+
331
+ Dev panelindeki istek listesi ve terminal, `prewarmUserAgent` taşıyan istekleri
332
+ filtreler: yüzlerce ısıtma isteği görünümü doldurmasın. İlerleme baloncuğun
333
+ yanındaki rozette görünür.
334
+
335
+ ### Zamanlama
336
+
337
+ - Isıtma açılışta **gecikmeyle** başlar: ilk gerçek isteklerle yarışmasın.
338
+ Varsayılan gecikme prod'da 500 ms, dev'de 3000 ms. Dev'de daha uzun, çünkü
339
+ dosya kaydı süreci yeniden başlattığı için zamanlayıcı da ölür; yalnızca
340
+ sunucu bir süre sakin kalınca ısınır.
341
+ - `PREWARM_INTERVAL_SECONDS` / `cache().prewarm.intervalSeconds` > 0 ise tur
342
+ periyodik tekrarlanır. Girdiler `revalidate` ile yaşlandığı ve
343
+ stale-while-revalidate sayesinde ziyaretçi beklemediği için bu **opsiyoneldir**;
344
+ hiç ziyaret edilmeyen sayfaları da sıcak tutmak isteyen kurulumlar için.
345
+ - Tüm zamanlayıcılar `unref()` edilmiştir: süreç kapanışını geciktirmezler.
346
+ - Hiçbir ısıtma hatası süreci düşürmez.
347
+
348
+ ### Ayarlar
349
+
350
+ Öncelik sırası: **ortam değişkeni → config → kod varsayılanı.** Env önde, çünkü
351
+ tek seferlik deneyler config'i düzenlemeden yapılabilsin.
352
+
353
+ | Ayar | Env | `cache().prewarm` | Varsayılan |
354
+ | --- | --- | --- | --- |
355
+ | Açık/kapalı | `PREWARM=0` kapatır, `PREWARM=1` config'i ezip açar | `enabled` | `true` |
356
+ | En fazla yol | `PREWARM_MAX` | `max` | `400` |
357
+ | Paralellik | `PREWARM_CONCURRENCY` | `concurrency` | prod 4, dev 2 |
358
+ | Başlangıç gecikmesi (ms) | `PREWARM_DELAY_MS` | `delayMs` | prod 500, dev 3000 |
359
+ | Periyot (saniye) | `PREWARM_INTERVAL_SECONDS` | `intervalSeconds` | `0` (kapalı) |
360
+
361
+ Sayısal ayarlar yalnızca **pozitif ve sonlu** değer kabul eder; geçersiz bir
362
+ değer sessizce bir sonraki katmana düşer.
363
+
364
+ ### Elle tetikleme
365
+
366
+ ```js
367
+ import { prewarm, prewarmProgress } from "jskelet";
368
+
369
+ await prewarm({ origin: "http://127.0.0.1:3000" }); // hook'tan yollar
370
+ await prewarm({ origin, paths: ["/", "/piyasalar"] }); // yalnızca bu yollar
371
+ await prewarm({ origin, quiet: true }); // özet basmadan
372
+ ```
373
+
374
+ `paths` verilirse hook hiç çağrılmaz. Dönüş değeri
375
+ `{ ok, failed, total, elapsed }`.
376
+
377
+ `prewarmProgress` canlı durumu tutar ve dev paneli bunu okur:
378
+
379
+ ```js
380
+ {
381
+ active, done, total, ok, failed, startedAt, finishedAt,
382
+ entries: [{ path, status, ms, bytes, cache, error }],
383
+ }
384
+ ```
385
+
386
+ `entries` içindeki `cache` alanı o yolun `X-JSkelet-Cache` yanıtıdır; ısıtma
387
+ turunun gerçekten `MISS` → önbellek doldurup doldurmadığını buradan görürsünüz.
388
+
389
+ ## Teşhis: sık görülen durumlar
390
+
391
+ - **Her istek `MISS` dönüyor.** Route'a `revalidate` verilmemiş ya da
392
+ `cache().html` içindeki desen 0 saniye veriyor. Veya sayfa `status: 200`
393
+ dışında bir kod dönüyor.
394
+ - **Sayfa `MISS` dönüyor ama upstream sağlam.** Geçici bir upstream hatası
395
+ bildirilmiş olabilir; logda `eksik veriyle üretildi, önbelleğe alınmıyor`
396
+ satırını arayın.
397
+ - **Sürekli eski veri.** `revalidate` çok yüksek; unutmayın ki gerçek gecikme en
398
+ fazla `revalidate` + bir tazeleme turudur.
399
+ - **Önbellek şişiyor.** Query parametreleri anahtara girdiği için kampanya
400
+ parametreleri girdi çoğaltıyor olabilir.
401
+ - **Isıtma hiç çalışmıyor.** `hooks.prewarmPaths` tanımlı değil, `PREWARM=0`
402
+ ayarlı ya da `cache().prewarm.enabled === false`.
403
+
404
+ ## Sırada ne var
405
+
406
+ - Config alanlarının tam referansı ve env tablosu:
407
+ [07-yapilandirma.md](./07-yapilandirma.md)
408
+ - Önbelleği dev panelinden izlemek: [09-dev-araclari.md](./09-dev-araclari.md)
409
+ - CDN/ters proxy ile birlikte kullanım: [10-dagitim.md](./10-dagitim.md)