jskelet 0.6.3 → 0.6.4
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.
- package/AGENTS.md +136 -136
- package/CHANGELOG.md +628 -620
- package/LICENSE +21 -21
- package/README.md +2 -0
- package/bin/jskelet.mjs +130 -130
- package/docs/01-baslangic.md +291 -291
- package/docs/02-mimari.md +310 -310
- package/docs/03-routing.md +515 -515
- package/docs/04-render-ve-sablonlar.md +667 -661
- package/docs/05-islands.md +486 -486
- package/docs/06-cache.md +1467 -1443
- package/docs/07-yapilandirma.md +1208 -1197
- package/docs/08-build.md +429 -429
- package/docs/09-dev-araclari.md +364 -364
- package/docs/10-dagitim.md +348 -338
- package/docs/12-panel-ve-oturum.md +479 -478
- package/docs/README.md +83 -83
- package/docs/en/01-getting-started.md +298 -298
- package/docs/en/02-architecture.md +329 -329
- package/docs/en/03-routing.md +531 -531
- package/docs/en/04-rendering.md +675 -669
- package/docs/en/05-islands.md +497 -497
- package/docs/en/06-caching.md +1476 -1453
- package/docs/en/07-configuration.md +1229 -1219
- package/docs/en/08-build.md +447 -447
- package/docs/en/09-dev-tools.md +373 -373
- package/docs/en/10-deployment.md +351 -340
- package/docs/en/11-migration.md +398 -398
- package/docs/en/12-dashboards-and-sessions.md +489 -488
- package/docs/en/README.md +87 -87
- package/package.json +137 -137
- package/src/build/ensure-build.mjs +19 -19
- package/src/build/paths.mjs +153 -153
- package/src/build/resolve-peer.mjs +36 -36
- package/src/build/tasks/client.mjs +349 -349
- package/src/build/tasks/css.mjs +235 -235
- package/src/build/tasks/fonts.mjs +146 -146
- package/src/build/tasks/icons.mjs +357 -357
- package/src/build/tasks/images.mjs +244 -244
- package/src/build/tasks/precompress.mjs +78 -78
- package/src/build/tasks/templates.mjs +20 -20
- package/src/client/admin/i18n.js +764 -764
- package/src/client/admin/login.html +74 -74
- package/src/client/admin/panel.css +809 -809
- package/src/client/admin/panel.html +495 -495
- package/src/client/admin/panel.js +1251 -1251
- package/src/client/devtools/report.html +185 -185
- package/src/client/devtools/report.js +745 -745
- package/src/client/devtools/seo.js +628 -628
- package/src/client/dom.js +95 -95
- package/src/client/form.js +192 -192
- package/src/client/index.js +45 -45
- package/src/client/registry.js +305 -305
- package/src/client/safe-image.js +91 -91
- package/src/client/shared-cookie.js +225 -225
- package/src/client/store.js +36 -36
- package/src/client/swap.js +188 -188
- package/src/compile/codegen.js +336 -336
- package/src/compile/compile-all.js +149 -149
- package/src/compile/errors.js +66 -66
- package/src/compile/expr.js +409 -409
- package/src/compile/index.js +17 -17
- package/src/compile/parse.js +541 -541
- package/src/compile/resolve.js +211 -211
- package/src/compile/scan-exports.js +51 -51
- package/src/config/defaults.js +541 -534
- package/src/config/index.js +1500 -1469
- package/src/config/pattern.js +107 -107
- package/src/generate.mjs +163 -163
- package/src/http/control-flow.js +71 -71
- package/src/http/cookies-entry.js +21 -21
- package/src/http/cookies.js +277 -277
- package/src/http/request-cache.js +46 -46
- package/src/http/request-context.js +165 -165
- package/src/http/shared-cookie.js +178 -178
- package/src/index.js +101 -101
- package/src/init.mjs +232 -230
- package/src/migrate/apply.mjs +262 -262
- package/src/migrate/babel.mjs +79 -79
- package/src/migrate/classify.mjs +155 -155
- package/src/migrate/config.mjs +126 -126
- package/src/migrate/fs-walk.mjs +191 -191
- package/src/migrate/parse.mjs +26 -26
- package/src/migrate/scan.mjs +177 -177
- package/src/migrate/transform/expr-source.mjs +168 -168
- package/src/migrate/transform/island.mjs +67 -67
- package/src/migrate/transform/jsx-to-component.mjs +302 -302
- package/src/migrate/transform/jsx-to-jsk.mjs +330 -330
- package/src/migrate/transform/page-split.mjs +435 -435
- package/src/migrate/write.mjs +81 -81
- package/src/migrate.mjs +171 -171
- package/src/runtime/alias-hooks.mjs +119 -119
- package/src/runtime/register.mjs +4 -4
- package/src/server/admin/actions.js +229 -229
- package/src/server/admin/auth.js +125 -125
- package/src/server/admin/event-log.js +151 -151
- package/src/server/admin/gate.js +209 -209
- package/src/server/admin/inventory.js +188 -188
- package/src/server/admin/mount.js +56 -56
- package/src/server/admin/router.js +216 -216
- package/src/server/admin/snapshot.js +241 -241
- package/src/server/assets.js +147 -147
- package/src/server/auth/handoff.js +309 -309
- package/src/server/cache-blob.js +70 -70
- package/src/server/cache-control.js +45 -0
- package/src/server/cache-deps.js +42 -42
- package/src/server/cache-vary.js +113 -113
- package/src/server/cloudflare.js +607 -607
- package/src/server/create-app.js +366 -366
- package/src/server/data-cache.js +553 -553
- package/src/server/dev/report.js +485 -485
- package/src/server/dev/socket.js +170 -170
- package/src/server/dev/version-check.mjs +139 -139
- package/src/server/disk-cache.js +233 -233
- package/src/server/ejs-adapter.js +59 -59
- package/src/server/html-cache.js +1196 -1196
- package/src/server/image-optimizer.js +500 -500
- package/src/server/logs/access-middleware.js +66 -66
- package/src/server/logs/file-sink.js +193 -193
- package/src/server/logs/pipeline.js +165 -165
- package/src/server/logs/s3-put.js +214 -214
- package/src/server/logs/s3-sink.js +112 -112
- package/src/server/metadata.js +102 -102
- package/src/server/middleware/compression.js +205 -205
- package/src/server/middleware/csrf.js +134 -134
- package/src/server/middleware/dev-gate.js +75 -75
- package/src/server/middleware/headers.js +37 -37
- package/src/server/middleware/redirects.js +32 -32
- package/src/server/middleware/robots-txt.js +341 -341
- package/src/server/middleware/static-precompressed.js +121 -121
- package/src/server/middleware/trailing-slash.js +53 -53
- package/src/server/middleware/upstream-proxy.js +141 -141
- package/src/server/og-image.js +369 -356
- package/src/server/port-guard.js +255 -255
- package/src/server/prewarm.js +1082 -1082
- package/src/server/redis.js +588 -588
- package/src/server/render.js +910 -910
- package/src/server/router.js +157 -157
- package/src/server/status-page.js +265 -265
- package/src/server/upstream-limiter.js +376 -376
- package/src/server/upstream-tracking.js +166 -166
- package/src/shared/cookie-domain.js +66 -66
- package/src/start.mjs +22 -22
- package/src/templates/layout.ejs +30 -30
- package/src/templates/layout.jsk +30 -30
- package/src/version.mjs +31 -31
- package/src/views/components/loader.js +101 -101
- package/src/views/helpers/html.js +102 -102
- package/src/views/helpers/tags.js +375 -375
- package/types/config/defaults.d.ts +6 -0
- package/types/config/index.d.ts +6 -0
- package/types/server/cache-control.d.ts +28 -0
- package/types/server/og-image.d.ts +5 -0
package/docs/10-dagitim.md
CHANGED
|
@@ -1,338 +1,348 @@
|
|
|
1
|
-
# 10 — Dağıtım
|
|
2
|
-
|
|
3
|
-
Bu belge bir JSkelet uygulamasını yayına almayı anlatır: prod build ve start
|
|
4
|
-
akışı, ayarlanması gereken ortam değişkenleri, çalışan bir Docker kurulumu, ters
|
|
5
|
-
proxy ve `trust proxy` notları, sağlık kontrolü ucunun nasıl eklendiği ve
|
|
6
|
-
ölçekleme sırasında önbelleğin nasıl davrandığı. Build adımlarının içeriği
|
|
7
|
-
[08-build.md](./08-build.md)'de, önbellek davranışı [06-cache.md](./06-cache.md)'de.
|
|
8
|
-
|
|
9
|
-
## Prod akışı
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
npm ci
|
|
13
|
-
npm run build # jskelet build
|
|
14
|
-
npm start # jskelet start
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
`jskelet build` `NODE_ENV` verilmemişse `production` ayarlar ve tüm adımları
|
|
18
|
-
çalıştırır: fontlar, ikon sprite, CSS, client JS, görseller, manifest,
|
|
19
|
-
precompress.
|
|
20
|
-
|
|
21
|
-
`jskelet start` önce `.jskelet/manifest.json` dosyasına bakar; yoksa build'i
|
|
22
|
-
kendisi çalıştırır. Docker imajında build zaten yapıldığı için bu bir no-op;
|
|
23
|
-
amaç `npm start`ı doğrudan çalıştıran birinin stilsiz bir sayfayla
|
|
24
|
-
karşılaşmaması.
|
|
25
|
-
|
|
26
|
-
Port doluysa süreç **başlamaz** (PID + ipucu). `jskelet start --murder` o
|
|
27
|
-
porttaki dinleyiciyi öldürüp bağlar — geliştirmede unutulmuş bir süreç için;
|
|
28
|
-
üretim orkestratöründe genelde gerekmez.
|
|
29
|
-
|
|
30
|
-
Sunucu hazır olduğunda tek satır basar:
|
|
31
|
-
|
|
32
|
-
```
|
|
33
|
-
jskelet → http://localhost:3000 (production)
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Süreç iki güvenlik ağıyla korunur: `unhandledRejection` ve `uncaughtException`
|
|
37
|
-
loglanır ve süreç ayakta kalır. Bir haber sitesinde tek sayfanın hatası tüm
|
|
38
|
-
siteyi indirmemeli. Kendi hata izleme aracınıza (Sentry vb.) bağlanmak
|
|
39
|
-
istiyorsanız aynı olaylara kendi dinleyicinizi de ekleyebilirsiniz.
|
|
40
|
-
|
|
41
|
-
## Ortam değişkenleri
|
|
42
|
-
|
|
43
|
-
Zorunlu hiçbir değişken yok; hepsinin makul bir varsayılanı var. Prod'da
|
|
44
|
-
ayarlamayı düşünmeniz gerekenler:
|
|
45
|
-
|
|
46
|
-
| Değişken | Öneri | Neden |
|
|
47
|
-
| --- | --- | --- |
|
|
48
|
-
| `NODE_ENV` | `production` | Şablon cache'i, manifest'in bir kez okunması, bozuk route modülünde fırlatma |
|
|
49
|
-
| `PORT` | `3000` | Orkestratörünüzün beklediği port |
|
|
50
|
-
| `HOST` | `0.0.0.0` | Yalnızca IPv4 dinlemek gerekiyorsa. Varsayılan `::` zaten çift yığın dinler |
|
|
51
|
-
| `PREWARM_MAX` | Site boyutuna göre | Açılışta ısıtılacak sayfa sayısı |
|
|
52
|
-
| `PREWARM_INTERVAL_SECONDS` | `0` ya da uzun bir değer | Hiç ziyaret edilmeyen sayfaları sıcak tutmak isterseniz |
|
|
53
|
-
| `DEV_GATE` + `DEV_TOKEN` | Yalnızca staging'de | Yayına açılmamış ortamı gizler. Token tek başına siteyi kilitlemez |
|
|
54
|
-
| `JSKELET_S3_*` | Access log'u S3'e yazıyorsanız | Bucket + credential; ayrıntı [07](./07-yapilandirma.md) |
|
|
55
|
-
|
|
56
|
-
Production'da dosya veya S3 sink açıldığında HTTP access log middleware
|
|
57
|
-
otomatik mount edilir (`logs.kinds` içinde `http` varsa). Admin paneli ring'i
|
|
58
|
-
ayrıdır — disk/S3'e yazılan satırlar panele akmaz.
|
|
59
|
-
|
|
60
|
-
Tam liste ve prewarm ayarlarının öncelik sırası:
|
|
61
|
-
[07-yapilandirma.md](./07-yapilandirma.md).
|
|
62
|
-
|
|
63
|
-
CLI `--env-file-if-exists=.env` ile çalıştığı için `.env` dosyası varsa otomatik
|
|
64
|
-
yüklenir; yoksa hata verilmez. Kapsayıcıda genelde bu dosya yerine ortam
|
|
65
|
-
değişkenleri doğrudan enjekte edilir. İki kaynağı birlikte kullanmak hangi
|
|
66
|
-
değerin geçerli olduğunu belirsizleştirir; prod imajında `.env` bulundurmamak en
|
|
67
|
-
temizidir.
|
|
68
|
-
|
|
69
|
-
**Gizli anahtarlar `clientEnv` listesine konmamalıdır:** oradaki değerler client
|
|
70
|
-
bundle'a düz metin olarak gömülür ([08-build.md](./08-build.md)). Secret benzeri
|
|
71
|
-
isimler (`SECRET`, `API_KEY`, …) artık build'i düşürür.
|
|
72
|
-
|
|
73
|
-
## Docker
|
|
74
|
-
|
|
75
|
-
Çok aşamalı bir imaj: build aşaması dev bağımlılıklarıyla derler, çalışma
|
|
76
|
-
aşaması yalnızca üretim bağımlılıklarını ve build çıktısını taşır.
|
|
77
|
-
|
|
78
|
-
```dockerfile
|
|
79
|
-
# syntax=docker/dockerfile:1
|
|
80
|
-
|
|
81
|
-
# ---------- build ----------
|
|
82
|
-
FROM node:22-bookworm-slim AS build
|
|
83
|
-
WORKDIR /app
|
|
84
|
-
|
|
85
|
-
# Bağımlılıklar ayrı katmanda: kaynak değişince yeniden kurulum yapılmasın.
|
|
86
|
-
COPY package.json package-lock.json ./
|
|
87
|
-
RUN npm ci
|
|
88
|
-
|
|
89
|
-
# `public/fonts/` commit edilmiş olmalı: build'in ağa çıkması gerekmesin.
|
|
90
|
-
COPY . .
|
|
91
|
-
|
|
92
|
-
ENV NODE_ENV=production
|
|
93
|
-
RUN npx jskelet build
|
|
94
|
-
|
|
95
|
-
# ---------- runtime ----------
|
|
96
|
-
FROM node:22-bookworm-slim AS runtime
|
|
97
|
-
WORKDIR /app
|
|
98
|
-
|
|
99
|
-
ENV NODE_ENV=production
|
|
100
|
-
ENV PORT=3000
|
|
101
|
-
ENV HOST=0.0.0.0
|
|
102
|
-
|
|
103
|
-
COPY package.json package-lock.json ./
|
|
104
|
-
# sharp ve tailwind yalnızca build zamanı gerekli; çalışma imajına girmesin.
|
|
105
|
-
# images.remote kullanıyorsanız sharp'ı production dependencies'e alın.
|
|
106
|
-
RUN npm ci --omit=dev && npm cache clean --force
|
|
107
|
-
|
|
108
|
-
COPY --from=build /app/jskelet.config.mjs ./jskelet.config.mjs
|
|
109
|
-
COPY --from=build /app/jsconfig.json ./jsconfig.json
|
|
110
|
-
COPY --from=build /app/routes ./routes
|
|
111
|
-
COPY --from=build /app/views ./views
|
|
112
|
-
COPY --from=build /app/lib ./lib
|
|
113
|
-
COPY --from=build /app/public ./public
|
|
114
|
-
COPY --from=build /app/.jskelet ./.jskelet
|
|
115
|
-
|
|
116
|
-
# Root olmayan kullanıcı.
|
|
117
|
-
USER node
|
|
118
|
-
|
|
119
|
-
EXPOSE 3000
|
|
120
|
-
|
|
121
|
-
# Sağlık kontrolü: aşağıdaki route'u eklediğinizi varsayar.
|
|
122
|
-
HEALTHCHECK --interval=30s --timeout=3s --start-period=20s --retries=3 \
|
|
123
|
-
CMD node -e "fetch('http://127.0.0.1:'+(process.env.PORT||3000)+'/api/healthcheck').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
|
124
|
-
|
|
125
|
-
CMD ["npx", "jskelet", "start"]
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
Notlar:
|
|
129
|
-
|
|
130
|
-
- **`client/` ve `styles/` çalışma imajına gerekmez:** çıktıları
|
|
131
|
-
`public/assets/` altında. `views/` ve `routes/` gerekir, çünkü render çalışma
|
|
132
|
-
anında yapılıyor. `lib/` yalnızca projenizde varsa kopyalayın.
|
|
133
|
-
- **`.jskelet/` gerekir:** `manifest.json` olmadan `asset()` hash'li URL'leri
|
|
134
|
-
bulamaz ve `jskelet start` build'i baştan çalıştırmaya kalkar.
|
|
135
|
-
- **`sharp` çalışma imajında genelde gerekmez:** yalnızca build zamanı görsel
|
|
136
|
-
optimizasyonu için. `--omit=dev` ile dışarıda kalır (devDependency olarak
|
|
137
|
-
kurulmuşsa). **`images.remote` açıksa** sharp runtime bağımlılığıdır —
|
|
138
|
-
production `dependencies`'e alın ya da runtime imajında ayrıca kurun; yoksa
|
|
139
|
-
optimizer kaynak URL'ye 302 yönlendirir.
|
|
140
|
-
- `jskelet start`ı `npx` olmadan çağırmak isterseniz
|
|
141
|
-
`CMD ["node", "node_modules/jskelet/bin/jskelet.mjs", "start"]` de çalışır.
|
|
142
|
-
|
|
143
|
-
`.dockerignore`:
|
|
144
|
-
|
|
145
|
-
```
|
|
146
|
-
node_modules
|
|
147
|
-
.git
|
|
148
|
-
.jskelet
|
|
149
|
-
public/assets
|
|
150
|
-
.env
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
Build aşaması `npx jskelet build` ile bunları kendisi üretir.
|
|
154
|
-
|
|
155
|
-
### Depo alt dizininden dağıtım
|
|
156
|
-
|
|
157
|
-
Bu depodaki örnekler jskelet'i npm'den değil `"jskelet": "file:../.."` ile
|
|
158
|
-
alıyor. Coolify, Railway, Render gibi araçlarda "base directory" olarak
|
|
159
|
-
`examples/blog` verilirse build context yalnızca o dizin olur, `../..`
|
|
160
|
-
context'in dışında kalır ve kurulum `npm ci`de düşer. Doğru ayar: **base
|
|
161
|
-
directory `/`** (depo kökü) ve imajı yukarıdaki çok aşamalı Dockerfile ile
|
|
162
|
-
uygulama dizinine göre uyarlamak — ya da jskelet'i npm bağımlılığı olarak
|
|
163
|
-
kurup context'i uygulamanın kendi dizini yapmak.
|
|
164
|
-
|
|
165
|
-
Kendi uygulamanızda jskelet normal bir bağımlılık olacağı için bu kısıt yoktur;
|
|
166
|
-
yukarıdaki çok aşamalı imaj yeterli.
|
|
167
|
-
|
|
168
|
-
## Sağlık kontrolü
|
|
169
|
-
|
|
170
|
-
Framework hazır bir sağlık kontrolü ucu **eklemez**; kendi route'unuza koymanız
|
|
171
|
-
gerekir. Varsayılan `devGateBypass` listesi `/api/healthcheck` yolunu içerdiği
|
|
172
|
-
için bu adı kullanmak en az sürprizli seçenektir: dev gate açıkken bile
|
|
173
|
-
erişilebilir kalır.
|
|
174
|
-
|
|
175
|
-
```js
|
|
176
|
-
// routes/00-health.mjs
|
|
177
|
-
import { getHtmlCacheSize } from "jskelet";
|
|
178
|
-
|
|
179
|
-
export default function register(app) {
|
|
180
|
-
app.get("/api/healthcheck", (req, res) => {
|
|
181
|
-
res.setHeader("Cache-Control", "no-store");
|
|
182
|
-
res.json({
|
|
183
|
-
ok: true,
|
|
184
|
-
uptime: process.uptime(),
|
|
185
|
-
cache: getHtmlCacheSize(),
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
Dosya adındaki `00-` öneki, bu route'un herhangi bir yakalayıcıdan önce
|
|
192
|
-
kaydedilmesini sağlar ([03-routing.md](./03-routing.md)).
|
|
193
|
-
|
|
194
|
-
Farklı bir yol kullanacaksanız `devGateBypass` listesini güncelleyin, aksi hâlde
|
|
195
|
-
staging'de orkestratör 404 görür:
|
|
196
|
-
|
|
197
|
-
```js
|
|
198
|
-
devGateBypass: ["/healthz", "/robots.txt", "/sitemap.xml", "/favicon.ico"]
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
Isıtma turu sağlık kontrolünü etkilemez: prewarm başarısız olsa bile süreç ayakta
|
|
202
|
-
kalır ve sayfalar (soğuk da olsa) servis edilir.
|
|
203
|
-
|
|
204
|
-
Hazırlık (readiness) ile canlılık (liveness) ayrımı gerekiyorsa ısıtmanın
|
|
205
|
-
durumunu de raporlayabilirsiniz:
|
|
206
|
-
|
|
207
|
-
```js
|
|
208
|
-
import { prewarmProgress } from "jskelet";
|
|
209
|
-
|
|
210
|
-
app.get("/api/ready", (req, res) => {
|
|
211
|
-
const warmedUp = !prewarmProgress.active && prewarmProgress.finishedAt !== null;
|
|
212
|
-
res.status(warmedUp ? 200 : 503).json({ warmedUp, ...prewarmProgress });
|
|
213
|
-
});
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
Bu ucun yolunu `prewarmSkip` ile ısıtma dışında bırakmayı unutmayın (varsayılan
|
|
217
|
-
`/api/` öneki zaten kapsıyor).
|
|
218
|
-
|
|
219
|
-
## Ters proxy
|
|
220
|
-
|
|
221
|
-
Express uygulaması `trust proxy`yi **açık** olarak kurar (`app.set("trust proxy", true)`).
|
|
222
|
-
Bunun sonuçları:
|
|
223
|
-
|
|
224
|
-
- `req.protocol` `X-Forwarded-Proto` başlığından okunur, yani proxy TLS'i
|
|
225
|
-
sonlandırıyorsa `https` doğru döner.
|
|
226
|
-
- `req.ip` `X-Forwarded-For` zincirinden çözülür.
|
|
227
|
-
- `res.redirect()` ile üretilen mutlak URL'ler doğru şemayı taşır.
|
|
228
|
-
|
|
229
|
-
Bu ayar **proxy'nin bu başlıkları güvenilir biçimde yazdığını varsayar.**
|
|
230
|
-
Uygulamayı doğrudan internete açacaksanız istemcinin `X-Forwarded-*` başlıklarını
|
|
231
|
-
uydurabileceğini unutmayın; her zaman bir proxy ya da yük dengeleyici arkasında
|
|
232
|
-
çalıştırın ve proxy'nin gelen `X-Forwarded-For` başlığını üzerine yazdığından
|
|
233
|
-
emin olun.
|
|
234
|
-
|
|
235
|
-
Örnek nginx yapılandırması:
|
|
236
|
-
|
|
237
|
-
```nginx
|
|
238
|
-
upstream jskelet {
|
|
239
|
-
server 127.0.0.1:3000;
|
|
240
|
-
keepalive 32;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
server {
|
|
244
|
-
listen 443 ssl http2;
|
|
245
|
-
server_name ornek.com;
|
|
246
|
-
|
|
247
|
-
# Yanıt gövdeleri zaten sıkıştırılmış geliyor; ikinci kez sıkıştırma yapma.
|
|
248
|
-
gzip off;
|
|
249
|
-
|
|
250
|
-
location / {
|
|
251
|
-
proxy_pass http://jskelet;
|
|
252
|
-
proxy_http_version 1.1;
|
|
253
|
-
|
|
254
|
-
proxy_set_header Host $host;
|
|
255
|
-
proxy_set_header X-Real-IP $remote_addr;
|
|
256
|
-
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
257
|
-
proxy_set_header X-Forwarded-Proto $scheme;
|
|
258
|
-
proxy_set_header Connection "";
|
|
259
|
-
|
|
260
|
-
# Sıkıştırılmış yanıt alabilmek için upstream'e ilet.
|
|
261
|
-
proxy_set_header Accept-Encoding $http_accept_encoding;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
Önemli noktalar:
|
|
267
|
-
|
|
268
|
-
- **Sıkıştırmayı iki kez yapmayın.** JSkelet brotli/gzip pazarlığını kendisi
|
|
269
|
-
yapıyor ve önbelleklenmiş sayfalarda sıkıştırılmış gövdeyi saklıyor. nginx'in
|
|
270
|
-
kendi `gzip`ini açık bırakmak brotli'yi çözüp yeniden gzip'lemeye yol açabilir.
|
|
271
|
-
- **`Accept-Encoding`i iletin**, yoksa uygulama sıkıştırma yapmaz ve önbellekteki
|
|
272
|
-
hazır sıkıştırılmış gövdeler kullanılmaz.
|
|
273
|
-
- `Vary: Accept-Encoding` uygulama tarafından yazılır; proxy önbelleği bunu
|
|
274
|
-
dikkate alır.
|
|
275
|
-
|
|
276
|
-
### CDN ile birlikte
|
|
277
|
-
|
|
278
|
-
Önbelleklenebilir sayfalara yazılan
|
|
279
|
-
|
|
280
|
-
```
|
|
281
|
-
Cache-Control: public, max-age=0
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
`
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
kopya
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
- [ ] `
|
|
328
|
-
- [ ] `
|
|
329
|
-
- [ ]
|
|
330
|
-
-
|
|
331
|
-
- [ ]
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
-
|
|
337
|
-
-
|
|
338
|
-
-
|
|
1
|
+
# 10 — Dağıtım
|
|
2
|
+
|
|
3
|
+
Bu belge bir JSkelet uygulamasını yayına almayı anlatır: prod build ve start
|
|
4
|
+
akışı, ayarlanması gereken ortam değişkenleri, çalışan bir Docker kurulumu, ters
|
|
5
|
+
proxy ve `trust proxy` notları, sağlık kontrolü ucunun nasıl eklendiği ve
|
|
6
|
+
ölçekleme sırasında önbelleğin nasıl davrandığı. Build adımlarının içeriği
|
|
7
|
+
[08-build.md](./08-build.md)'de, önbellek davranışı [06-cache.md](./06-cache.md)'de.
|
|
8
|
+
|
|
9
|
+
## Prod akışı
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm ci
|
|
13
|
+
npm run build # jskelet build
|
|
14
|
+
npm start # jskelet start
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`jskelet build` `NODE_ENV` verilmemişse `production` ayarlar ve tüm adımları
|
|
18
|
+
çalıştırır: fontlar, ikon sprite, CSS, client JS, görseller, manifest,
|
|
19
|
+
precompress.
|
|
20
|
+
|
|
21
|
+
`jskelet start` önce `.jskelet/manifest.json` dosyasına bakar; yoksa build'i
|
|
22
|
+
kendisi çalıştırır. Docker imajında build zaten yapıldığı için bu bir no-op;
|
|
23
|
+
amaç `npm start`ı doğrudan çalıştıran birinin stilsiz bir sayfayla
|
|
24
|
+
karşılaşmaması.
|
|
25
|
+
|
|
26
|
+
Port doluysa süreç **başlamaz** (PID + ipucu). `jskelet start --murder` o
|
|
27
|
+
porttaki dinleyiciyi öldürüp bağlar — geliştirmede unutulmuş bir süreç için;
|
|
28
|
+
üretim orkestratöründe genelde gerekmez.
|
|
29
|
+
|
|
30
|
+
Sunucu hazır olduğunda tek satır basar:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
jskelet → http://localhost:3000 (production)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Süreç iki güvenlik ağıyla korunur: `unhandledRejection` ve `uncaughtException`
|
|
37
|
+
loglanır ve süreç ayakta kalır. Bir haber sitesinde tek sayfanın hatası tüm
|
|
38
|
+
siteyi indirmemeli. Kendi hata izleme aracınıza (Sentry vb.) bağlanmak
|
|
39
|
+
istiyorsanız aynı olaylara kendi dinleyicinizi de ekleyebilirsiniz.
|
|
40
|
+
|
|
41
|
+
## Ortam değişkenleri
|
|
42
|
+
|
|
43
|
+
Zorunlu hiçbir değişken yok; hepsinin makul bir varsayılanı var. Prod'da
|
|
44
|
+
ayarlamayı düşünmeniz gerekenler:
|
|
45
|
+
|
|
46
|
+
| Değişken | Öneri | Neden |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
| `NODE_ENV` | `production` | Şablon cache'i, manifest'in bir kez okunması, bozuk route modülünde fırlatma |
|
|
49
|
+
| `PORT` | `3000` | Orkestratörünüzün beklediği port |
|
|
50
|
+
| `HOST` | `0.0.0.0` | Yalnızca IPv4 dinlemek gerekiyorsa. Varsayılan `::` zaten çift yığın dinler |
|
|
51
|
+
| `PREWARM_MAX` | Site boyutuna göre | Açılışta ısıtılacak sayfa sayısı |
|
|
52
|
+
| `PREWARM_INTERVAL_SECONDS` | `0` ya da uzun bir değer | Hiç ziyaret edilmeyen sayfaları sıcak tutmak isterseniz |
|
|
53
|
+
| `DEV_GATE` + `DEV_TOKEN` | Yalnızca staging'de | Yayına açılmamış ortamı gizler. Token tek başına siteyi kilitlemez |
|
|
54
|
+
| `JSKELET_S3_*` | Access log'u S3'e yazıyorsanız | Bucket + credential; ayrıntı [07](./07-yapilandirma.md) |
|
|
55
|
+
|
|
56
|
+
Production'da dosya veya S3 sink açıldığında HTTP access log middleware
|
|
57
|
+
otomatik mount edilir (`logs.kinds` içinde `http` varsa). Admin paneli ring'i
|
|
58
|
+
ayrıdır — disk/S3'e yazılan satırlar panele akmaz.
|
|
59
|
+
|
|
60
|
+
Tam liste ve prewarm ayarlarının öncelik sırası:
|
|
61
|
+
[07-yapilandirma.md](./07-yapilandirma.md).
|
|
62
|
+
|
|
63
|
+
CLI `--env-file-if-exists=.env` ile çalıştığı için `.env` dosyası varsa otomatik
|
|
64
|
+
yüklenir; yoksa hata verilmez. Kapsayıcıda genelde bu dosya yerine ortam
|
|
65
|
+
değişkenleri doğrudan enjekte edilir. İki kaynağı birlikte kullanmak hangi
|
|
66
|
+
değerin geçerli olduğunu belirsizleştirir; prod imajında `.env` bulundurmamak en
|
|
67
|
+
temizidir.
|
|
68
|
+
|
|
69
|
+
**Gizli anahtarlar `clientEnv` listesine konmamalıdır:** oradaki değerler client
|
|
70
|
+
bundle'a düz metin olarak gömülür ([08-build.md](./08-build.md)). Secret benzeri
|
|
71
|
+
isimler (`SECRET`, `API_KEY`, …) artık build'i düşürür.
|
|
72
|
+
|
|
73
|
+
## Docker
|
|
74
|
+
|
|
75
|
+
Çok aşamalı bir imaj: build aşaması dev bağımlılıklarıyla derler, çalışma
|
|
76
|
+
aşaması yalnızca üretim bağımlılıklarını ve build çıktısını taşır.
|
|
77
|
+
|
|
78
|
+
```dockerfile
|
|
79
|
+
# syntax=docker/dockerfile:1
|
|
80
|
+
|
|
81
|
+
# ---------- build ----------
|
|
82
|
+
FROM node:22-bookworm-slim AS build
|
|
83
|
+
WORKDIR /app
|
|
84
|
+
|
|
85
|
+
# Bağımlılıklar ayrı katmanda: kaynak değişince yeniden kurulum yapılmasın.
|
|
86
|
+
COPY package.json package-lock.json ./
|
|
87
|
+
RUN npm ci
|
|
88
|
+
|
|
89
|
+
# `public/fonts/` commit edilmiş olmalı: build'in ağa çıkması gerekmesin.
|
|
90
|
+
COPY . .
|
|
91
|
+
|
|
92
|
+
ENV NODE_ENV=production
|
|
93
|
+
RUN npx jskelet build
|
|
94
|
+
|
|
95
|
+
# ---------- runtime ----------
|
|
96
|
+
FROM node:22-bookworm-slim AS runtime
|
|
97
|
+
WORKDIR /app
|
|
98
|
+
|
|
99
|
+
ENV NODE_ENV=production
|
|
100
|
+
ENV PORT=3000
|
|
101
|
+
ENV HOST=0.0.0.0
|
|
102
|
+
|
|
103
|
+
COPY package.json package-lock.json ./
|
|
104
|
+
# sharp ve tailwind yalnızca build zamanı gerekli; çalışma imajına girmesin.
|
|
105
|
+
# images.remote kullanıyorsanız sharp'ı production dependencies'e alın.
|
|
106
|
+
RUN npm ci --omit=dev && npm cache clean --force
|
|
107
|
+
|
|
108
|
+
COPY --from=build /app/jskelet.config.mjs ./jskelet.config.mjs
|
|
109
|
+
COPY --from=build /app/jsconfig.json ./jsconfig.json
|
|
110
|
+
COPY --from=build /app/routes ./routes
|
|
111
|
+
COPY --from=build /app/views ./views
|
|
112
|
+
COPY --from=build /app/lib ./lib
|
|
113
|
+
COPY --from=build /app/public ./public
|
|
114
|
+
COPY --from=build /app/.jskelet ./.jskelet
|
|
115
|
+
|
|
116
|
+
# Root olmayan kullanıcı.
|
|
117
|
+
USER node
|
|
118
|
+
|
|
119
|
+
EXPOSE 3000
|
|
120
|
+
|
|
121
|
+
# Sağlık kontrolü: aşağıdaki route'u eklediğinizi varsayar.
|
|
122
|
+
HEALTHCHECK --interval=30s --timeout=3s --start-period=20s --retries=3 \
|
|
123
|
+
CMD node -e "fetch('http://127.0.0.1:'+(process.env.PORT||3000)+'/api/healthcheck').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
|
124
|
+
|
|
125
|
+
CMD ["npx", "jskelet", "start"]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Notlar:
|
|
129
|
+
|
|
130
|
+
- **`client/` ve `styles/` çalışma imajına gerekmez:** çıktıları
|
|
131
|
+
`public/assets/` altında. `views/` ve `routes/` gerekir, çünkü render çalışma
|
|
132
|
+
anında yapılıyor. `lib/` yalnızca projenizde varsa kopyalayın.
|
|
133
|
+
- **`.jskelet/` gerekir:** `manifest.json` olmadan `asset()` hash'li URL'leri
|
|
134
|
+
bulamaz ve `jskelet start` build'i baştan çalıştırmaya kalkar.
|
|
135
|
+
- **`sharp` çalışma imajında genelde gerekmez:** yalnızca build zamanı görsel
|
|
136
|
+
optimizasyonu için. `--omit=dev` ile dışarıda kalır (devDependency olarak
|
|
137
|
+
kurulmuşsa). **`images.remote` açıksa** sharp runtime bağımlılığıdır —
|
|
138
|
+
production `dependencies`'e alın ya da runtime imajında ayrıca kurun; yoksa
|
|
139
|
+
optimizer kaynak URL'ye 302 yönlendirir.
|
|
140
|
+
- `jskelet start`ı `npx` olmadan çağırmak isterseniz
|
|
141
|
+
`CMD ["node", "node_modules/jskelet/bin/jskelet.mjs", "start"]` de çalışır.
|
|
142
|
+
|
|
143
|
+
`.dockerignore`:
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
node_modules
|
|
147
|
+
.git
|
|
148
|
+
.jskelet
|
|
149
|
+
public/assets
|
|
150
|
+
.env
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Build aşaması `npx jskelet build` ile bunları kendisi üretir.
|
|
154
|
+
|
|
155
|
+
### Depo alt dizininden dağıtım
|
|
156
|
+
|
|
157
|
+
Bu depodaki örnekler jskelet'i npm'den değil `"jskelet": "file:../.."` ile
|
|
158
|
+
alıyor. Coolify, Railway, Render gibi araçlarda "base directory" olarak
|
|
159
|
+
`examples/blog` verilirse build context yalnızca o dizin olur, `../..`
|
|
160
|
+
context'in dışında kalır ve kurulum `npm ci`de düşer. Doğru ayar: **base
|
|
161
|
+
directory `/`** (depo kökü) ve imajı yukarıdaki çok aşamalı Dockerfile ile
|
|
162
|
+
uygulama dizinine göre uyarlamak — ya da jskelet'i npm bağımlılığı olarak
|
|
163
|
+
kurup context'i uygulamanın kendi dizini yapmak.
|
|
164
|
+
|
|
165
|
+
Kendi uygulamanızda jskelet normal bir bağımlılık olacağı için bu kısıt yoktur;
|
|
166
|
+
yukarıdaki çok aşamalı imaj yeterli.
|
|
167
|
+
|
|
168
|
+
## Sağlık kontrolü
|
|
169
|
+
|
|
170
|
+
Framework hazır bir sağlık kontrolü ucu **eklemez**; kendi route'unuza koymanız
|
|
171
|
+
gerekir. Varsayılan `devGateBypass` listesi `/api/healthcheck` yolunu içerdiği
|
|
172
|
+
için bu adı kullanmak en az sürprizli seçenektir: dev gate açıkken bile
|
|
173
|
+
erişilebilir kalır.
|
|
174
|
+
|
|
175
|
+
```js
|
|
176
|
+
// routes/00-health.mjs
|
|
177
|
+
import { getHtmlCacheSize } from "jskelet";
|
|
178
|
+
|
|
179
|
+
export default function register(app) {
|
|
180
|
+
app.get("/api/healthcheck", (req, res) => {
|
|
181
|
+
res.setHeader("Cache-Control", "no-store");
|
|
182
|
+
res.json({
|
|
183
|
+
ok: true,
|
|
184
|
+
uptime: process.uptime(),
|
|
185
|
+
cache: getHtmlCacheSize(),
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Dosya adındaki `00-` öneki, bu route'un herhangi bir yakalayıcıdan önce
|
|
192
|
+
kaydedilmesini sağlar ([03-routing.md](./03-routing.md)).
|
|
193
|
+
|
|
194
|
+
Farklı bir yol kullanacaksanız `devGateBypass` listesini güncelleyin, aksi hâlde
|
|
195
|
+
staging'de orkestratör 404 görür:
|
|
196
|
+
|
|
197
|
+
```js
|
|
198
|
+
devGateBypass: ["/healthz", "/robots.txt", "/sitemap.xml", "/favicon.ico"]
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Isıtma turu sağlık kontrolünü etkilemez: prewarm başarısız olsa bile süreç ayakta
|
|
202
|
+
kalır ve sayfalar (soğuk da olsa) servis edilir.
|
|
203
|
+
|
|
204
|
+
Hazırlık (readiness) ile canlılık (liveness) ayrımı gerekiyorsa ısıtmanın
|
|
205
|
+
durumunu de raporlayabilirsiniz:
|
|
206
|
+
|
|
207
|
+
```js
|
|
208
|
+
import { prewarmProgress } from "jskelet";
|
|
209
|
+
|
|
210
|
+
app.get("/api/ready", (req, res) => {
|
|
211
|
+
const warmedUp = !prewarmProgress.active && prewarmProgress.finishedAt !== null;
|
|
212
|
+
res.status(warmedUp ? 200 : 503).json({ warmedUp, ...prewarmProgress });
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Bu ucun yolunu `prewarmSkip` ile ısıtma dışında bırakmayı unutmayın (varsayılan
|
|
217
|
+
`/api/` öneki zaten kapsıyor).
|
|
218
|
+
|
|
219
|
+
## Ters proxy
|
|
220
|
+
|
|
221
|
+
Express uygulaması `trust proxy`yi **açık** olarak kurar (`app.set("trust proxy", true)`).
|
|
222
|
+
Bunun sonuçları:
|
|
223
|
+
|
|
224
|
+
- `req.protocol` `X-Forwarded-Proto` başlığından okunur, yani proxy TLS'i
|
|
225
|
+
sonlandırıyorsa `https` doğru döner.
|
|
226
|
+
- `req.ip` `X-Forwarded-For` zincirinden çözülür.
|
|
227
|
+
- `res.redirect()` ile üretilen mutlak URL'ler doğru şemayı taşır.
|
|
228
|
+
|
|
229
|
+
Bu ayar **proxy'nin bu başlıkları güvenilir biçimde yazdığını varsayar.**
|
|
230
|
+
Uygulamayı doğrudan internete açacaksanız istemcinin `X-Forwarded-*` başlıklarını
|
|
231
|
+
uydurabileceğini unutmayın; her zaman bir proxy ya da yük dengeleyici arkasında
|
|
232
|
+
çalıştırın ve proxy'nin gelen `X-Forwarded-For` başlığını üzerine yazdığından
|
|
233
|
+
emin olun.
|
|
234
|
+
|
|
235
|
+
Örnek nginx yapılandırması:
|
|
236
|
+
|
|
237
|
+
```nginx
|
|
238
|
+
upstream jskelet {
|
|
239
|
+
server 127.0.0.1:3000;
|
|
240
|
+
keepalive 32;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
server {
|
|
244
|
+
listen 443 ssl http2;
|
|
245
|
+
server_name ornek.com;
|
|
246
|
+
|
|
247
|
+
# Yanıt gövdeleri zaten sıkıştırılmış geliyor; ikinci kez sıkıştırma yapma.
|
|
248
|
+
gzip off;
|
|
249
|
+
|
|
250
|
+
location / {
|
|
251
|
+
proxy_pass http://jskelet;
|
|
252
|
+
proxy_http_version 1.1;
|
|
253
|
+
|
|
254
|
+
proxy_set_header Host $host;
|
|
255
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
256
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
257
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
258
|
+
proxy_set_header Connection "";
|
|
259
|
+
|
|
260
|
+
# Sıkıştırılmış yanıt alabilmek için upstream'e ilet.
|
|
261
|
+
proxy_set_header Accept-Encoding $http_accept_encoding;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Önemli noktalar:
|
|
267
|
+
|
|
268
|
+
- **Sıkıştırmayı iki kez yapmayın.** JSkelet brotli/gzip pazarlığını kendisi
|
|
269
|
+
yapıyor ve önbelleklenmiş sayfalarda sıkıştırılmış gövdeyi saklıyor. nginx'in
|
|
270
|
+
kendi `gzip`ini açık bırakmak brotli'yi çözüp yeniden gzip'lemeye yol açabilir.
|
|
271
|
+
- **`Accept-Encoding`i iletin**, yoksa uygulama sıkıştırma yapmaz ve önbellekteki
|
|
272
|
+
hazır sıkıştırılmış gövdeler kullanılmaz.
|
|
273
|
+
- `Vary: Accept-Encoding` uygulama tarafından yazılır; proxy önbelleği bunu
|
|
274
|
+
dikkate alır.
|
|
275
|
+
|
|
276
|
+
### CDN ile birlikte
|
|
277
|
+
|
|
278
|
+
Önbelleklenebilir sayfalara yazılan başlıklar:
|
|
279
|
+
|
|
280
|
+
```
|
|
281
|
+
Cache-Control: public, max-age=0
|
|
282
|
+
CDN-Cache-Control: max-age=<html ttl>, stale-while-revalidate=<staleWhileRevalidate>
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
`max-age=0` tarayıcıda saklamayı kapatır. Edge süresi `CDN-Cache-Control`
|
|
286
|
+
üzerindeki `max-age`'dir ve mevcut HTML TTL'dir. `stale-while-revalidate`
|
|
287
|
+
`cache().staleWhileRevalidate` değeridir (varsayılan 60; `0` direktifi basmaz).
|
|
288
|
+
`s-maxage` yazılmaz: Cloudflare `max-age=0` ile birlikte onu `EXPIRED` sayar.
|
|
289
|
+
`must-revalidate`, `proxy-revalidate` ve `no-cache` aynı yanıtta yoktur.
|
|
290
|
+
|
|
291
|
+
CDN `max-age` boyunca kendi kopyasını verir, taze pencere bitince
|
|
292
|
+
`stale-while-revalidate` süresince eski HTML'i sunar ve arkada origin'e sorar.
|
|
293
|
+
Origin da kendi önbelleğinden anında yanıtlar.
|
|
294
|
+
|
|
295
|
+
**Kırılma.** Yalnızca `Cache-Control` / `s-maxage` okuyan bir ara katman
|
|
296
|
+
(nginx `proxy_cache`) bu HTML'i artık önbelleklemez. Cloudflare
|
|
297
|
+
`CDN-Cache-Control` okur. Süreç içi önbellek ve `X-JSkelet-Cache` aynı kalır.
|
|
298
|
+
|
|
299
|
+
`X-JSkelet-Cache` başlığı hangi katmanın yanıtladığını teşhis etmeyi
|
|
300
|
+
kolaylaştırır; CDN'in kendi cache başlığıyla birlikte okuyun
|
|
301
|
+
([06-cache.md](./06-cache.md)).
|
|
302
|
+
|
|
303
|
+
Statik varlıklar (`/assets/`, `/fonts/`) `immutable` işaretli olduğu için CDN'de
|
|
304
|
+
süresiz tutulabilir; hash değiştiğinde URL de değişir.
|
|
305
|
+
|
|
306
|
+
## Ölçekleme
|
|
307
|
+
|
|
308
|
+
HTML önbelleği **süreç belleğinde** yaşar. Birden fazla kopya çalıştırdığınızda:
|
|
309
|
+
|
|
310
|
+
- Her kopyanın kendi önbelleği olur; bellek kullanımı kopya sayısıyla çarpılır
|
|
311
|
+
(en fazla 500 girdi + sıkıştırılmış kopyaları).
|
|
312
|
+
- Her kopya açılışta kendi ısıtma turunu yapar. `PREWARM_MAX` ve
|
|
313
|
+
`PREWARM_CONCURRENCY` değerlerini upstream API'nizin kopya sayısıyla çarpılmış
|
|
314
|
+
yükü kaldırabileceği şekilde ayarlayın.
|
|
315
|
+
- `clearHtmlCache()` yalnızca çağrıldığı süreci etkiler. Tüm kopyaları
|
|
316
|
+
temizlemek gerekiyorsa bunu orkestratör düzeyinde (yeniden başlatma) ya da
|
|
317
|
+
kendi yazacağınız bir yayın mekanizmasıyla çözmeniz gerekir.
|
|
318
|
+
- Önünde bir CDN varsa çoğu istek origin'e hiç gelmez ve kopya başına önbellek
|
|
319
|
+
farkı görünmez hâle gelir.
|
|
320
|
+
|
|
321
|
+
Tek kopyanın kapasitesini artırmak için `revalidate` sürelerini yükseltmek,
|
|
322
|
+
kopya eklemekten genellikle daha etkilidir: önbellek isabet oranı arttıkça
|
|
323
|
+
istek başına iş neredeyse sıfıra iner.
|
|
324
|
+
|
|
325
|
+
## Yayın öncesi kontrol listesi
|
|
326
|
+
|
|
327
|
+
- [ ] `NODE_ENV=production`
|
|
328
|
+
- [ ] `npm run build` çalıştı ve `.jskelet/manifest.json` üretildi
|
|
329
|
+
- [ ] `public/fonts/` içindeki woff2 dosyaları commit edilmiş
|
|
330
|
+
([08-build.md](./08-build.md))
|
|
331
|
+
- [ ] `styles/globals.css` içindeki `@source` direktifleri tüm şablon
|
|
332
|
+
dizinlerini kapsıyor
|
|
333
|
+
- [ ] `hooks.notFound()` tanımlı ve 404 şablonu var
|
|
334
|
+
- [ ] `hooks.metadata()` içinde `siteUrl` var (göreli `canonical`lar
|
|
335
|
+
mutlaklaşsın)
|
|
336
|
+
- [ ] `cache().html` desenleri sitenin tazelik profiline uygun
|
|
337
|
+
- [ ] `hooks.prewarmPaths()` en önemli sayfaları başa koyuyor
|
|
338
|
+
- [ ] `headers()` içinde CSP ve güvenlik başlıkları tanımlı
|
|
339
|
+
- [ ] Sağlık kontrolü ucu var ve `devGateBypass` listesinde
|
|
340
|
+
- [ ] Staging'de `DEV_GATE=1` ve `DEV_TOKEN` ayarlı, prod'da gate **kapalı**
|
|
341
|
+
- [ ] Ters proxy `Accept-Encoding`i iletiyor ve kendi sıkıştırmasını yapmıyor
|
|
342
|
+
- [ ] `clientEnv` listesinde gizli anahtar yok
|
|
343
|
+
|
|
344
|
+
## Sırada ne var
|
|
345
|
+
|
|
346
|
+
- Önbellek ayarları ve prewarm: [06-cache.md](./06-cache.md)
|
|
347
|
+
- Ortam değişkenlerinin tamamı: [07-yapilandirma.md](./07-yapilandirma.md)
|
|
348
|
+
- Next.js'ten taşıma: [11-tasima.md](./11-tasima.md)
|