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.
- package/AGENTS.md +127 -0
- package/CHANGELOG.md +40 -0
- package/LICENSE +21 -0
- package/README.md +342 -0
- package/bin/jskelet.mjs +104 -0
- package/docs/01-baslangic.md +285 -0
- package/docs/02-mimari.md +287 -0
- package/docs/03-routing.md +437 -0
- package/docs/04-render-ve-sablonlar.md +490 -0
- package/docs/05-islands.md +429 -0
- package/docs/06-cache.md +409 -0
- package/docs/07-yapilandirma.md +673 -0
- package/docs/08-build.md +366 -0
- package/docs/09-dev-araclari.md +302 -0
- package/docs/10-dagitim.md +329 -0
- package/docs/11-tasima.md +352 -0
- package/docs/README.md +82 -0
- package/package.json +97 -0
- package/src/build/build.mjs +138 -0
- package/src/build/ensure-build.mjs +15 -0
- package/src/build/paths.mjs +118 -0
- package/src/build/resolve-peer.mjs +36 -0
- package/src/build/tasks/client.mjs +268 -0
- package/src/build/tasks/css.mjs +124 -0
- package/src/build/tasks/fonts.mjs +146 -0
- package/src/build/tasks/icons.mjs +224 -0
- package/src/build/tasks/images.mjs +244 -0
- package/src/build/tasks/precompress.mjs +78 -0
- package/src/client/devtools/overlay.js +1763 -0
- package/src/client/devtools/report.html +185 -0
- package/src/client/devtools/report.js +712 -0
- package/src/client/dom.js +95 -0
- package/src/client/index.js +26 -0
- package/src/client/registry.js +223 -0
- package/src/client/safe-image.js +91 -0
- package/src/client/store.js +36 -0
- package/src/config/defaults.js +102 -0
- package/src/config/index.js +433 -0
- package/src/config/pattern.js +107 -0
- package/src/dev-server.mjs +383 -0
- package/src/http/control-flow.js +56 -0
- package/src/http/request-cache.js +46 -0
- package/src/index.js +35 -0
- package/src/init.mjs +220 -0
- package/src/log.mjs +332 -0
- package/src/logo.png +0 -0
- package/src/runtime/alias-hooks.mjs +119 -0
- package/src/runtime/register.mjs +4 -0
- package/src/server/assets.js +119 -0
- package/src/server/create-app.js +167 -0
- package/src/server/dev/devtools.js +383 -0
- package/src/server/dev/report.js +351 -0
- package/src/server/head-hints.js +132 -0
- package/src/server/html-cache.js +166 -0
- package/src/server/metadata.js +102 -0
- package/src/server/middleware/compression.js +205 -0
- package/src/server/middleware/dev-gate.js +62 -0
- package/src/server/middleware/headers.js +37 -0
- package/src/server/middleware/redirects.js +32 -0
- package/src/server/middleware/static-precompressed.js +100 -0
- package/src/server/middleware/upstream-proxy.js +141 -0
- package/src/server/prewarm.js +283 -0
- package/src/server/render.js +356 -0
- package/src/server/router.js +121 -0
- package/src/server/status-page.js +164 -0
- package/src/server/upstream-tracking.js +51 -0
- package/src/start.mjs +7 -0
- package/src/templates/layout.ejs +44 -0
- package/src/version.mjs +17 -0
- package/src/views/components/loader.js +85 -0
- package/src/views/helpers/html.js +102 -0
- package/src/views/helpers/tags.js +193 -0
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
# 07 — Yapılandırma referansı
|
|
2
|
+
|
|
3
|
+
Bu belge `jskelet.config.mjs`'in tam referansıdır: her alan, tipi, varsayılanı
|
|
4
|
+
ve örneği. Ardından `source` desen sözdizimi ve framework'ün okuduğu tüm ortam
|
|
5
|
+
değişkenleri tablosu geliyor. Alanların davranışsal ayrıntıları için ilgili
|
|
6
|
+
belgelere bağlantı verildi; buradaki amaç tek bakışta tam liste sunmak.
|
|
7
|
+
|
|
8
|
+
## Dosyanın konumu ve yüklenmesi
|
|
9
|
+
|
|
10
|
+
Config dosyası proje kökünde `jskelet.config.mjs` adıyla aranır ve **zorunlu
|
|
11
|
+
değildir**. Yoksa ya da okunamıyorsa uyarı basılır ve sunucu varsayılanlarla
|
|
12
|
+
ayağa kalkar; bozuk bir düzenleme siteyi açılamaz hâle getirmemeli.
|
|
13
|
+
|
|
14
|
+
```js
|
|
15
|
+
// jskelet.config.mjs
|
|
16
|
+
export default {
|
|
17
|
+
// …
|
|
18
|
+
};
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Default export yoksa modülün kendisi config olarak kullanılır (named export'lar).
|
|
22
|
+
|
|
23
|
+
`headers()`, `redirects()`, `rewrites()` ve `cache()` bölümleri fonksiyon **ya da
|
|
24
|
+
düz değer** olabilir; fonksiyon olmaları hâlinde `async` olabilirler ve `this`
|
|
25
|
+
config nesnesine bağlıdır. Bir bölüm hata verirse yalnızca o bölüm yok sayılır.
|
|
26
|
+
|
|
27
|
+
Config başarıyla yüklendiğinde bir özet basılır:
|
|
28
|
+
`[config] jskelet.config.mjs yüklendi — 3 header, 2 redirect, 1 cache kuralı`
|
|
29
|
+
|
|
30
|
+
## Tam örnek
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
// jskelet.config.mjs
|
|
34
|
+
export default {
|
|
35
|
+
paths: {
|
|
36
|
+
views: "views",
|
|
37
|
+
public: "public",
|
|
38
|
+
client: "client",
|
|
39
|
+
routes: "routes",
|
|
40
|
+
styles: "styles/globals.css",
|
|
41
|
+
generated: ".jskelet",
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
brand: {
|
|
45
|
+
name: "Örnek",
|
|
46
|
+
poweredBy: "Örnek",
|
|
47
|
+
cacheHeader: "X-Ornek-Cache",
|
|
48
|
+
devBasePath: "/__ornek/dev",
|
|
49
|
+
prewarmUserAgent: "ornek-prewarm",
|
|
50
|
+
devTokenCookie: "dev_token",
|
|
51
|
+
lang: "tr",
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
layout: "views/layout.ejs",
|
|
55
|
+
routes: ["./routes/10-pages.mjs", "./routes/99-catch-all.mjs"],
|
|
56
|
+
|
|
57
|
+
static: {
|
|
58
|
+
extensions: [".svg", ".png", ".webp", ".avif", ".ico", ".woff2"],
|
|
59
|
+
prefixes: ["/assets/", "/fonts/"],
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
devGateBypass: ["/api/healthcheck", "/robots.txt"],
|
|
63
|
+
preconnect: ["https://cdn.ornek.com"],
|
|
64
|
+
|
|
65
|
+
navigation: {
|
|
66
|
+
prefetch: "moderate",
|
|
67
|
+
prerender: "conservative",
|
|
68
|
+
viewTransition: true,
|
|
69
|
+
exclude: ["/cikis"],
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
prewarmSkip: ["/api/", "/_fragment/", "/__ornek/"],
|
|
73
|
+
watch: ["data"],
|
|
74
|
+
|
|
75
|
+
fonts: [{ family: "Inter", weights: [400, 600, 700] }],
|
|
76
|
+
icons: { scan: ["views", "client", "routes", "lib"] },
|
|
77
|
+
images: { widths: [400, 800, 1200], quality: 78, skip: ["indirmeler"] },
|
|
78
|
+
clientEnv: ["PUBLIC_WS_URL"],
|
|
79
|
+
|
|
80
|
+
async headers() {
|
|
81
|
+
return [
|
|
82
|
+
{
|
|
83
|
+
source: "/:path*",
|
|
84
|
+
headers: [{ key: "X-Frame-Options", value: "SAMEORIGIN" }],
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
async redirects() {
|
|
90
|
+
return [{ source: "/eski/:slug", destination: "/yeni/:slug", permanent: true }];
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
async rewrites() {
|
|
94
|
+
return {
|
|
95
|
+
afterFiles: [
|
|
96
|
+
{ source: "/api/:path*", destination: "https://api.ornek.com/:path*" },
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
async cache() {
|
|
102
|
+
return {
|
|
103
|
+
html: { "/": 60, "/haber/:slug": 300 },
|
|
104
|
+
prewarm: { enabled: true, max: 400, concurrency: 4, intervalSeconds: 0 },
|
|
105
|
+
};
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
hooks: {
|
|
109
|
+
metadata() { /* … */ },
|
|
110
|
+
layoutContext() { /* … */ },
|
|
111
|
+
notFound() { /* … */ },
|
|
112
|
+
error() { /* … */ },
|
|
113
|
+
prewarmPaths() { /* … */ },
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## `paths`
|
|
119
|
+
|
|
120
|
+
**Tip:** `Record<string, string>` — **Varsayılan:** aşağıdaki tablo
|
|
121
|
+
|
|
122
|
+
Proje kökündeki dizin (ve `styles` için dosya) adları. Değerler proje köküne
|
|
123
|
+
göre çözülür ve içeride mutlak yola çevrilir.
|
|
124
|
+
|
|
125
|
+
| Anahtar | Varsayılan | İçeriği |
|
|
126
|
+
| --- | --- | --- |
|
|
127
|
+
| `views` | `"views"` | EJS layout, sayfalar, bileşenler |
|
|
128
|
+
| `public` | `"public"` | Statik dosyalar; build çıktısı da buraya yazılır |
|
|
129
|
+
| `client` | `"client"` | Island runtime kaynakları ve entry'ler |
|
|
130
|
+
| `routes` | `"routes"` | Route modülleri |
|
|
131
|
+
| `styles` | `"styles/globals.css"` | Tailwind/PostCSS giriş **dosyası** |
|
|
132
|
+
| `generated` | `".jskelet"` | `manifest.json`, `metafile.json`, `images.json` |
|
|
133
|
+
|
|
134
|
+
`styles` bir dosya yolu olduğu hâlde aynı çözümlemeden geçer; ayrı bir alan
|
|
135
|
+
tutmaya değmiyor.
|
|
136
|
+
|
|
137
|
+
İki yol her zaman türetilir ve ezilemez: `public/assets` (hash'li build
|
|
138
|
+
çıktısı) ve `public/fonts` (self-host fontlar).
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
paths: { views: "src/views", routes: "src/routes", styles: "src/styles/main.css" }
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## `brand`
|
|
145
|
+
|
|
146
|
+
**Tip:** `object` — **Varsayılan:** aşağıdaki tablo
|
|
147
|
+
|
|
148
|
+
Markalama ve tek yerden değiştirilebilir isimler. Fork eden ya da beyaz etiket
|
|
149
|
+
kullanan projeler kendi adını verebilir. Verilen alanlar varsayılanlarla sığ
|
|
150
|
+
birleştirilir.
|
|
151
|
+
|
|
152
|
+
| Alan | Tip | Varsayılan | Anlamı |
|
|
153
|
+
| --- | --- | --- | --- |
|
|
154
|
+
| `name` | `string` | `"JSkelet"` | Görüntü adı |
|
|
155
|
+
| `poweredBy` | `string` | `"JSkelet"` | `X-Powered-By` başlığının değeri |
|
|
156
|
+
| `cacheHeader` | `string` | `"X-JSkelet-Cache"` | HTML cache durumu başlığı ([06-cache.md](./06-cache.md)) |
|
|
157
|
+
| `devBasePath` | `string` | `"/__jskelet/dev"` | Dev overlay ve rapor uçlarının kökü |
|
|
158
|
+
| `prewarmUserAgent` | `string` | `"jskelet-prewarm"` | Isıtma isteklerinin UA'sı; dev paneli bunu filtreler |
|
|
159
|
+
| `devTokenCookie` | `string` | `"dev_token"` | Dev gate'in çerez ve query parametresi adı |
|
|
160
|
+
| `lang` | `string` | — | `<html lang>` varsayılanı. Verilmezse layout `"en"` kullanır. |
|
|
161
|
+
|
|
162
|
+
`lang` için öncelik sırası: `hooks.layoutContext()` → `lang` **>** `brand.lang`
|
|
163
|
+
**>** `"en"`.
|
|
164
|
+
|
|
165
|
+
```js
|
|
166
|
+
brand: { lang: "tr", poweredBy: "Örnek", cacheHeader: "X-Ornek-Cache" }
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## `layout`
|
|
170
|
+
|
|
171
|
+
**Tip:** `string` — **Varsayılan:** yok (otomatik çözüm)
|
|
172
|
+
|
|
173
|
+
Layout `.ejs` dosyasının yolu. Verilen değer **views dizininin üst dizinine**
|
|
174
|
+
göre çözülür, yani varsayılan `views` ile `"views/ozel.ejs"` →
|
|
175
|
+
`<root>/views/ozel.ejs`.
|
|
176
|
+
|
|
177
|
+
Verilmezse sırayla: `views/layout.ejs` varsa o, yoksa framework'ün minimal
|
|
178
|
+
layout'u. Ayrıntı: [04-render-ve-sablonlar.md](./04-render-ve-sablonlar.md).
|
|
179
|
+
|
|
180
|
+
## `routes`
|
|
181
|
+
|
|
182
|
+
**Tip:** `string[]` — **Varsayılan:** `null` (dizin taraması)
|
|
183
|
+
|
|
184
|
+
Route modüllerinin açık listesi, proje köküne göre. Verilen sırada yüklenir.
|
|
185
|
+
Verilmezse `paths.routes` dizini alfabetik ve özyinelemeli olarak taranır.
|
|
186
|
+
Ayrıntı: [03-routing.md](./03-routing.md).
|
|
187
|
+
|
|
188
|
+
```js
|
|
189
|
+
routes: ["./routes/api.js", "./routes/pages.js", "./routes/catch-all.js"]
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## `static`
|
|
193
|
+
|
|
194
|
+
**Tip:** `{ extensions?: string[], prefixes?: string[] }` — **Varsayılan:**
|
|
195
|
+
aşağıda
|
|
196
|
+
|
|
197
|
+
Uzantı ve önek bazlı statik dosya tespiti. Bu listeye uyan yollara
|
|
198
|
+
`Cache-Control: public, max-age=31536000, immutable` yazılır.
|
|
199
|
+
|
|
200
|
+
| Alan | Varsayılan |
|
|
201
|
+
| --- | --- |
|
|
202
|
+
| `extensions` | `[".svg", ".png", ".webp", ".avif", ".ico", ".woff2"]` |
|
|
203
|
+
| `prefixes` | `["/assets/", "/fonts/"]` |
|
|
204
|
+
|
|
205
|
+
Verilirse varsayılanın **yerine** geçer (birleştirilmez), yani varsayılana ek
|
|
206
|
+
yapmak isterseniz tam listeyi yazın.
|
|
207
|
+
|
|
208
|
+
```js
|
|
209
|
+
static: {
|
|
210
|
+
extensions: [".svg", ".png", ".webp", ".avif", ".ico", ".woff2", ".mp4"],
|
|
211
|
+
prefixes: ["/assets/", "/fonts/", "/video/"],
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## `devGateBypass`
|
|
216
|
+
|
|
217
|
+
**Tip:** `string[]` — **Varsayılan:**
|
|
218
|
+
`["/api/healthcheck", "/robots.txt", "/sitemap.xml", "/site.webmanifest", "/favicon.ico"]`
|
|
219
|
+
|
|
220
|
+
Dev gate'in hiçbir koşulda kapatmadığı **tam** yollar (önek değil, birebir
|
|
221
|
+
eşleşme). `DEV_TOKEN` ayarlı bir ortamda sağlık kontrolünün ve robots
|
|
222
|
+
dosyalarının erişilebilir kalması için. Verilirse varsayılanın yerine geçer.
|
|
223
|
+
|
|
224
|
+
Ayrıntı: [09-dev-araclari.md](./09-dev-araclari.md).
|
|
225
|
+
|
|
226
|
+
## `preconnect`
|
|
227
|
+
|
|
228
|
+
**Tip:** `string[]` — **Varsayılan:** `[]`
|
|
229
|
+
|
|
230
|
+
Üçüncü taraf origin'ler; her sayfanın `<head>`inde `<link rel="preconnect">`
|
|
231
|
+
olarak basılır. Görsel CDN'i, API origin'i, font host'u buraya yazılır. Değerler
|
|
232
|
+
`new URL(...).origin` ile normalize edilir; geçersiz bir URL atlanır ve uyarı
|
|
233
|
+
basılır.
|
|
234
|
+
|
|
235
|
+
Liste her sayfada aynı olduğu için bir kez hesaplanıp saklanır. Boş liste geçerli
|
|
236
|
+
bir yapılandırmadır.
|
|
237
|
+
|
|
238
|
+
```js
|
|
239
|
+
preconnect: ["https://cdn.ornek.com", "https://api.ornek.com"]
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## `navigation`
|
|
243
|
+
|
|
244
|
+
**Tip:** `object` — **Varsayılan:**
|
|
245
|
+
`{ prefetch: "moderate", prerender: false, viewTransition: false, exclude: [] }`
|
|
246
|
+
|
|
247
|
+
Site içi gezinmeyi hızlandıran `<head>` ipuçları. JSkelet klasik MPA olduğu için
|
|
248
|
+
her tıklama tam sayfa yüklemesidir; bu bölüm o yüklemeyi tarayıcının **önceden**
|
|
249
|
+
yapmasını sağlar. Client runtime'ı eklenmez — Speculation Rules ve view
|
|
250
|
+
transition tarayıcı yetenekleridir, desteklemeyen tarayıcıda sessizce yok
|
|
251
|
+
sayılırlar.
|
|
252
|
+
|
|
253
|
+
| Alan | Tip | Varsayılan | Anlamı |
|
|
254
|
+
| --- | --- | --- | --- |
|
|
255
|
+
| `prefetch` | `false \| "conservative" \| "moderate" \| "eager"` | `"moderate"` | Bağlantı hedefinin **belgesini** önceden indirir |
|
|
256
|
+
| `prerender` | aynı | `false` | Hedefi arka planda **tam render eder**; tıklama anında açılır |
|
|
257
|
+
| `viewTransition` | `boolean` | `false` | `@view-transition { navigation: auto }` basar |
|
|
258
|
+
| `exclude` | `string[]` | `[]` | Spekülasyon dışı bırakılacak href desenleri |
|
|
259
|
+
|
|
260
|
+
`true` verilirse `prefetch`/`prerender` varsayılan eagerness'a düşer; tanınmayan
|
|
261
|
+
bir değer uyarı basıp varsayılana döner.
|
|
262
|
+
|
|
263
|
+
**Eagerness ne demek:** `conservative` bağlantıya basıldığı an, `moderate`
|
|
264
|
+
bağlantı üzerinde bir süre duraksandığında, `eager` bağlantı görünür olur olmaz
|
|
265
|
+
tetikler. Yukarı çıktıkça isabet artar, boşa giden istek de artar.
|
|
266
|
+
|
|
267
|
+
**`prerender` neden kapalı geliyor.** Prerender edilen sayfanın script'leri
|
|
268
|
+
gerçekten çalışır. Ölçüm kodunu `prerenderingchange` olayına bağlamayan bir
|
|
269
|
+
uygulamada ziyaret sayıları şişer. Açmadan önce analytics'i gözden geçirin;
|
|
270
|
+
sunucu tarafındaki maliyeti düşüktür, çünkü spekülatif istek de HTML
|
|
271
|
+
önbelleğinden karşılanır ([06-cache.md](./06-cache.md)).
|
|
272
|
+
|
|
273
|
+
**Her koşulda muaf olanlar.** `/api/*`, `/_fragment/*` ve `brand.devBasePath`
|
|
274
|
+
altındaki yollar otomatik dışlanır; `exclude` bunların üstüne eklenir. Ayrıca
|
|
275
|
+
`rel="nofollow"`, `target="_blank"` ve `data-no-prefetch` taşıyan bağlantılar
|
|
276
|
+
hiçbir kurala girmez. Yan etkisi olan tek bir bağlantıyı dışarıda bırakmanın en
|
|
277
|
+
kolay yolu sonuncusu:
|
|
278
|
+
|
|
279
|
+
```html
|
|
280
|
+
<a href="/cikis" data-no-prefetch>Çıkış</a>
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**`viewTransition` açarken arka planı `<html>`e verin.** Geçiş sırasında tarayıcı
|
|
284
|
+
eski ve yeni sayfanın anlık görüntülerini çapraz geçirir; `<body>`ye verilmiş bir
|
|
285
|
+
arka plan bu görüntünün içinde kalır ve altta kalan canvas görünür. Sonuç, her
|
|
286
|
+
geçişte bir kare beyaz flaştır ve koyu temada gözden kaçmaz. Renk `<html>` (ya da
|
|
287
|
+
`:root`) üzerindeyse böyle bir boşluk oluşmaz:
|
|
288
|
+
|
|
289
|
+
```html
|
|
290
|
+
<html lang="tr" class="bg-white dark:bg-slate-950">
|
|
291
|
+
<body class="text-slate-900 dark:text-slate-100">
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Hareket azaltma tercihi framework tarafından karşılanır: `prefers-reduced-motion:
|
|
295
|
+
reduce` altında geçiş kapatılır, ayrıca bir şey yazmanız gerekmez.
|
|
296
|
+
|
|
297
|
+
**Geçişi içerikle sınırlayın.** Varsayılan davranış tüm belgeyi tek parça olarak
|
|
298
|
+
çapraz geçirir, yani gezinme boyunca hiç değişmeyen header ve footer da titrer.
|
|
299
|
+
Bu bölgelere bir `view-transition-name` vermek onları kendi grubuna alır;
|
|
300
|
+
tarayıcı aynı adı iki belgede de gördüğü için "aynı öğe" sayar. Adlandırılan
|
|
301
|
+
öğenin animasyonunu kapatınca geçiş yalnızca içerikte kalır:
|
|
302
|
+
|
|
303
|
+
```css
|
|
304
|
+
body > header { view-transition-name: site-header; }
|
|
305
|
+
body > footer { view-transition-name: site-footer; }
|
|
306
|
+
|
|
307
|
+
::view-transition-old(site-header),
|
|
308
|
+
::view-transition-old(site-footer) { animation: none; opacity: 0; }
|
|
309
|
+
::view-transition-new(site-header),
|
|
310
|
+
::view-transition-new(site-footer) { animation: none; opacity: 1; }
|
|
311
|
+
|
|
312
|
+
/* Kalan içerik; varsayılan 250ms gezinmeyi yavaş hissettiriyor. */
|
|
313
|
+
::view-transition-old(root),
|
|
314
|
+
::view-transition-new(root) { animation-duration: 180ms; }
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Çalışan hâli `examples/marketing/styles/globals.css` içinde.
|
|
318
|
+
|
|
319
|
+
**CSP kullanıyorsanız** kurallar satır içi bir `<script type="speculationrules">`
|
|
320
|
+
olarak basılır; `script-src` politikanızın buna izin vermesi gerekir.
|
|
321
|
+
|
|
322
|
+
```js
|
|
323
|
+
navigation: {
|
|
324
|
+
prefetch: "moderate",
|
|
325
|
+
prerender: "conservative",
|
|
326
|
+
viewTransition: true,
|
|
327
|
+
exclude: ["/cikis", "/sepet/*"],
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## `prewarmSkip`
|
|
332
|
+
|
|
333
|
+
**Tip:** `string[]` — **Varsayılan:** `["/api/", "/_fragment/", "/__jskelet/"]`
|
|
334
|
+
|
|
335
|
+
Isıtmanın atlayacağı yol **önekleri**. Oturuma bağlı ya da fragment uçları
|
|
336
|
+
ısıtılmamalı. Verilirse varsayılanın yerine geçer — `brand.devBasePath`i
|
|
337
|
+
değiştirdiyseniz bu listeyi de güncellemeyi unutmayın.
|
|
338
|
+
|
|
339
|
+
Ayrıntı: [06-cache.md](./06-cache.md).
|
|
340
|
+
|
|
341
|
+
## `watch`
|
|
342
|
+
|
|
343
|
+
**Tip:** `string[]` — **Varsayılan:** `[]`
|
|
344
|
+
|
|
345
|
+
`jskelet dev`in sunucu yeniden başlatma için izleyeceği **ek** dizinler, proje
|
|
346
|
+
köküne göre. `routes`, `views` ve `lib` zaten izlenir; `client/` ve `styles/`
|
|
347
|
+
esbuild ve CSS watcher'ları tarafından ele alınır, buraya konmamalı.
|
|
348
|
+
|
|
349
|
+
Yalnızca `.js`, `.mjs`, `.json` ve `.ejs` uzantılı dosyalar tetikleyicidir.
|
|
350
|
+
|
|
351
|
+
```js
|
|
352
|
+
watch: ["data", "content"]
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Ayrıntı: [09-dev-araclari.md](./09-dev-araclari.md).
|
|
356
|
+
|
|
357
|
+
## `fonts`
|
|
358
|
+
|
|
359
|
+
**Tip:** `{ family: string, slug?: string, weights?: number[] }[]` —
|
|
360
|
+
**Varsayılan:** `[]`
|
|
361
|
+
|
|
362
|
+
Self-host edilecek Google Fonts aileleri. Boş bırakılırsa font adımı hiç
|
|
363
|
+
çalışmaz.
|
|
364
|
+
|
|
365
|
+
| Alan | Tip | Varsayılan | Anlamı |
|
|
366
|
+
| --- | --- | --- | --- |
|
|
367
|
+
| `family` | `string` | — | Google Fonts aile adı: `"Inter"`, `"Noto Sans"` |
|
|
368
|
+
| `slug` | `string` | `family`den türetilir (küçük harf, boşluk → `-`) | Dosya adı öneki |
|
|
369
|
+
| `weights` | `number[]` | `[400]` | İndirilecek ağırlıklar |
|
|
370
|
+
|
|
371
|
+
Çıktı: `public/fonts/<slug>-<weight>.woff2`, manifest anahtarı aynı dosya adı.
|
|
372
|
+
Dosyalar **sabit isimlidir** (hash yok) ve **commit edilmesi beklenir**.
|
|
373
|
+
Ayrıntı: [08-build.md](./08-build.md).
|
|
374
|
+
|
|
375
|
+
```js
|
|
376
|
+
fonts: [
|
|
377
|
+
{ family: "Inter", weights: [400, 600, 700] },
|
|
378
|
+
{ family: "Noto Serif", slug: "serif", weights: [400] },
|
|
379
|
+
]
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
## `icons`
|
|
383
|
+
|
|
384
|
+
**Tip:** `{ scan?: string[] } | false` — **Varsayılan:** `{}`
|
|
385
|
+
|
|
386
|
+
Phosphor SVG sprite üretimi.
|
|
387
|
+
|
|
388
|
+
| Değer | Sonuç |
|
|
389
|
+
| --- | --- |
|
|
390
|
+
| `{}` (varsayılan) | Sprite üretilir; taranan dizinler `["views", "client", "routes", "lib"]` |
|
|
391
|
+
| `{ scan: [...] }` | Taranan dizinler değiştirilir |
|
|
392
|
+
| `false` | Sprite adımı tamamen atlanır |
|
|
393
|
+
|
|
394
|
+
`@phosphor-icons/core` uygulamanın `node_modules`'ünde yoksa adım sessizce
|
|
395
|
+
atlanır. Ayrıntı: [08-build.md](./08-build.md).
|
|
396
|
+
|
|
397
|
+
```js
|
|
398
|
+
icons: { scan: ["views", "client", "routes", "lib", "content"] }
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
## `images`
|
|
402
|
+
|
|
403
|
+
**Tip:** `{ widths?: number[], quality?: number, skip?: string[] } | false` —
|
|
404
|
+
**Varsayılan:** `{}`
|
|
405
|
+
|
|
406
|
+
`public/` altındaki png/jpg görsellerin webp varyantlarını üretir.
|
|
407
|
+
|
|
408
|
+
| Alan | Tip | Varsayılan | Anlamı |
|
|
409
|
+
| --- | --- | --- | --- |
|
|
410
|
+
| `widths` | `number[]` | `[400, 640, 960, 1280, 1920]` | Üretilecek genişlikler. Kaynaktan büyük olanlar elenir; kaynağın kendi genişliği (en fazla 1920) her zaman eklenir. |
|
|
411
|
+
| `quality` | `number` | `78` | webp kalitesi. Değişince kodlayıcı imzası değişir ve tüm görseller yeniden kodlanır. |
|
|
412
|
+
| `skip` | `string[]` | `[]` | Taranmayacak **dizin adları**. `assets` ve `fonts` her zaman atlanır. |
|
|
413
|
+
|
|
414
|
+
`false` verilirse görsel adımı hiç çalışmaz. Adım `sharp` gerektirir ve watch
|
|
415
|
+
turunda hiç çalışmaz. Ayrıntı: [08-build.md](./08-build.md).
|
|
416
|
+
|
|
417
|
+
```js
|
|
418
|
+
images: { widths: [400, 800, 1200], quality: 82, skip: ["indirmeler"] }
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
## `clientEnv`
|
|
422
|
+
|
|
423
|
+
**Tip:** `string[]` — **Varsayılan:** `[]`
|
|
424
|
+
|
|
425
|
+
Client bundle'a build zamanında gömülecek ortam değişkeni anahtarları. Next'teki
|
|
426
|
+
`NEXT_PUBLIC_*` ile aynı sözleşme, ama hangi anahtarın herkese açık olduğu
|
|
427
|
+
isimden değil config'ten belli. `NODE_ENV` her zaman gömülür.
|
|
428
|
+
|
|
429
|
+
`process.env`in tamamı tek nesne olarak define edildiği için listede olmayan bir
|
|
430
|
+
anahtar okunduğunda çökme yerine `undefined` döner.
|
|
431
|
+
|
|
432
|
+
```js
|
|
433
|
+
clientEnv: ["PUBLIC_WS_URL", "PUBLIC_CDN_ORIGIN"]
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
**Buraya gizli anahtar koymayın** — değerler bundle'da düz metin olarak durur.
|
|
437
|
+
|
|
438
|
+
## `headers()`
|
|
439
|
+
|
|
440
|
+
**Tip:** `() => { source: string, headers: { key: string, value: string }[] }[]`
|
|
441
|
+
— **Varsayılan:** `[]`
|
|
442
|
+
|
|
443
|
+
Yol desenine göre yanıt başlıkları. Framework yalnızca statik dosyalara uzun
|
|
444
|
+
ömürlü cache yazar; bunun dışındaki her başlık (CSP, COOP, HSTS,
|
|
445
|
+
X-Frame-Options…) buradan gelir ve varsayılanların üstüne biner.
|
|
446
|
+
|
|
447
|
+
Eşleşen **tüm** kurallar uygulanır (redirect'lerin aksine ilk eşleşmede
|
|
448
|
+
durulmaz), sırayla; aynı başlığı iki kural yazarsa sonraki kazanır.
|
|
449
|
+
|
|
450
|
+
`key`i olmayan ya da `value`u `undefined` olan girdiler atlanır; hiç geçerli
|
|
451
|
+
başlığı kalmayan bir kural hiç eklenmez.
|
|
452
|
+
|
|
453
|
+
```js
|
|
454
|
+
async headers() {
|
|
455
|
+
return [
|
|
456
|
+
{
|
|
457
|
+
source: "/:path*",
|
|
458
|
+
headers: [
|
|
459
|
+
{ key: "X-Frame-Options", value: "SAMEORIGIN" },
|
|
460
|
+
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
|
|
461
|
+
{
|
|
462
|
+
key: "Content-Security-Policy",
|
|
463
|
+
value: "default-src 'self'; img-src 'self' https://cdn.ornek.com data:",
|
|
464
|
+
},
|
|
465
|
+
],
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
source: "/indirme/:path*",
|
|
469
|
+
headers: [{ key: "Cache-Control", value: "no-store" }],
|
|
470
|
+
},
|
|
471
|
+
];
|
|
472
|
+
}
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
## `redirects()`
|
|
476
|
+
|
|
477
|
+
**Tip:**
|
|
478
|
+
`() => { source: string, destination: string, permanent?: boolean, statusCode?: number }[]`
|
|
479
|
+
— **Varsayılan:** `[]`
|
|
480
|
+
|
|
481
|
+
| Alan | Tip | Anlamı |
|
|
482
|
+
| --- | --- | --- |
|
|
483
|
+
| `source` | `string` | Desen (aşağıdaki sözdizimi) |
|
|
484
|
+
| `destination` | `string` | Hedef; `:param` yer tutucuları doldurulur |
|
|
485
|
+
| `permanent` | `boolean` | `true` → 308, aksi hâlde 307 |
|
|
486
|
+
| `statusCode` | `number` | Açık durum kodu; `permanent`i ezer |
|
|
487
|
+
|
|
488
|
+
İlk eşleşen kural kazanır ve query string korunur. Ayrıntı:
|
|
489
|
+
[03-routing.md](./03-routing.md).
|
|
490
|
+
|
|
491
|
+
## `rewrites()`
|
|
492
|
+
|
|
493
|
+
**Tip:** `() => Rule[] | { beforeFiles?: Rule[], afterFiles?: Rule[] }`
|
|
494
|
+
burada `Rule = { source: string, destination: string }` — **Varsayılan:** `[]`
|
|
495
|
+
|
|
496
|
+
Dizi döndürülürse tamamı `afterFiles` sayılır.
|
|
497
|
+
|
|
498
|
+
- `beforeFiles` statik dosyalardan da önce çalışır.
|
|
499
|
+
- `afterFiles` statik denendikten sonra, route'lardan önce çalışır.
|
|
500
|
+
- Mutlak hedef (`http://`/`https://`) → gömülü ters proxy.
|
|
501
|
+
- Göreli hedef → yalnızca `req.url` değişir.
|
|
502
|
+
|
|
503
|
+
Ayrıntı: [03-routing.md](./03-routing.md).
|
|
504
|
+
|
|
505
|
+
## `cache()`
|
|
506
|
+
|
|
507
|
+
**Tip:** `() => { html?: Record<string, number>, prewarm?: object }` —
|
|
508
|
+
**Varsayılan:** `{ html: {}, prewarm: { enabled: true, max: 400, intervalSeconds: 0 } }`
|
|
509
|
+
|
|
510
|
+
### `cache().html`
|
|
511
|
+
|
|
512
|
+
Desen → saniye eşlemesi. Eşleşen kural, route'un kendi `revalidate` değerini
|
|
513
|
+
**ezer**. Negatif ya da sonlu olmayan değerler yok sayılır; `0` "önbellekleme"
|
|
514
|
+
anlamına gelir.
|
|
515
|
+
|
|
516
|
+
```js
|
|
517
|
+
html: {
|
|
518
|
+
"/": 60,
|
|
519
|
+
"/haber/:slug": 300,
|
|
520
|
+
"/arama": 0,
|
|
521
|
+
}
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
### `cache().prewarm`
|
|
525
|
+
|
|
526
|
+
| Alan | Tip | Varsayılan | Anlamı |
|
|
527
|
+
| --- | --- | --- | --- |
|
|
528
|
+
| `enabled` | `boolean` | `true` | `false` ise ısıtma yapılmaz (`PREWARM=1` ile ezilebilir) |
|
|
529
|
+
| `max` | `number` | `400` | En fazla kaç yol ısıtılır |
|
|
530
|
+
| `concurrency` | `number` | prod 4, dev 2 | Paralel işçi sayısı |
|
|
531
|
+
| `delayMs` | `number` | prod 500, dev 3000 | Açılıştan sonra ilk turun gecikmesi |
|
|
532
|
+
| `intervalSeconds` | `number` | `0` | 0'dan büyükse tur periyodik tekrarlanır |
|
|
533
|
+
|
|
534
|
+
Her biri aynı adı taşıyan ortam değişkeniyle ezilebilir; env önceliklidir.
|
|
535
|
+
Ayrıntı: [06-cache.md](./06-cache.md).
|
|
536
|
+
|
|
537
|
+
## `hooks`
|
|
538
|
+
|
|
539
|
+
**Tip:** `Record<string, Function>` — **Varsayılan:** `{}`
|
|
540
|
+
|
|
541
|
+
Hepsi opsiyonel, hepsi `async` olabilir. Bir hook hata verirse framework kendi
|
|
542
|
+
varsayılanına döner ve uyarır — sayfa düşmez.
|
|
543
|
+
|
|
544
|
+
| Hook | İmza | Döndürdüğü | Belge |
|
|
545
|
+
| --- | --- | --- | --- |
|
|
546
|
+
| `metadata` | `(page) => object` | Her sayfanın metadata varsayılanı; controller `metadata`sı üzerine biner | [04](./04-render-ve-sablonlar.md) |
|
|
547
|
+
| `layoutContext` | `({ pathname, metadata }) => object` | Layout local'leri; `lang`, `structuredData`, `extraHead`, `bodyClass` özel yorumlanır | [04](./04-render-ve-sablonlar.md) |
|
|
548
|
+
| `notFound` | `() => object \| null` | 404 sayfa tanımı; `null` ise framework'ün hata sayfası | [03](./03-routing.md) |
|
|
549
|
+
| `error` | `({ status, error }) => object \| string \| null` | 404 dışındaki hata sayfaları (ve `notFound` yoksa 404); sayfa tanımı ya da doğrudan HTML | [03](./03-routing.md) |
|
|
550
|
+
| `prewarmPaths` | `() => string[]` | Isıtılacak yollar; tanımlı değilse ısıtma hiç kurulmaz | [06](./06-cache.md) |
|
|
551
|
+
|
|
552
|
+
```js
|
|
553
|
+
hooks: {
|
|
554
|
+
metadata() {
|
|
555
|
+
return { titleTemplate: "%s | Örnek", siteUrl: "https://ornek.com" };
|
|
556
|
+
},
|
|
557
|
+
|
|
558
|
+
async layoutContext({ pathname }) {
|
|
559
|
+
return { navigation: await getNavigation(), isHome: pathname === "/" };
|
|
560
|
+
},
|
|
561
|
+
|
|
562
|
+
notFound() {
|
|
563
|
+
return {
|
|
564
|
+
view: "pages/not-found",
|
|
565
|
+
metadata: { title: "Sayfa bulunamadı", robots: { index: false } },
|
|
566
|
+
};
|
|
567
|
+
},
|
|
568
|
+
|
|
569
|
+
error({ status }) {
|
|
570
|
+
return {
|
|
571
|
+
view: "pages/error",
|
|
572
|
+
data: { status },
|
|
573
|
+
metadata: { title: "Bir hata oluştu", robots: { index: false } },
|
|
574
|
+
};
|
|
575
|
+
},
|
|
576
|
+
|
|
577
|
+
async prewarmPaths() {
|
|
578
|
+
return ["/", ...(await getArticlePaths())];
|
|
579
|
+
},
|
|
580
|
+
}
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
## `source` desen sözdizimi
|
|
584
|
+
|
|
585
|
+
`headers()`, `redirects()`, `rewrites()` ve `cache().html` aynı küçük derleyiciyi
|
|
586
|
+
kullanır. Bu, Next'in tam `path-to-regexp` yüzeyi değil; config'te fiilen
|
|
587
|
+
kullanılan alt küme bilinçli olarak seçildi ve tanınmayan bir sözdizimi sessizce
|
|
588
|
+
literal kabul edilmez, uyarı üretir.
|
|
589
|
+
|
|
590
|
+
| Desen | Regex karşılığı | Örnek eşleşme |
|
|
591
|
+
| --- | --- | --- |
|
|
592
|
+
| `/hakkinda` | tam eşleşme | `/hakkinda` |
|
|
593
|
+
| `/haber/:slug` | `([^/]+)` — tek segment | `/haber/abc` (✗ `/haber/a/b`) |
|
|
594
|
+
| `/:path*` | `(.*)` — sıfır veya daha fazla segment | `/`, `/a`, `/a/b/c` |
|
|
595
|
+
| `/blog/:path*` | joker alt yol; öndeki `/` opsiyonel | `/blog`, `/blog/`, `/blog/a/b` |
|
|
596
|
+
| `/:path*.svg` | joker + sabit son ek | `/ikon.svg`, `/a/b/c.svg` |
|
|
597
|
+
| `/etiket-:slug` | segment ortasında parametre | `/etiket-finans` |
|
|
598
|
+
|
|
599
|
+
Kurallar:
|
|
600
|
+
|
|
601
|
+
- `source` **`/` ile başlamak zorundadır**; başlamazsa kural yok sayılır ve
|
|
602
|
+
uyarı basılır.
|
|
603
|
+
- Parametre adı `[A-Za-z_][A-Za-z0-9_]*` kalıbına uymalıdır.
|
|
604
|
+
- Desen daima **baştan sona** eşleşir (`^…$`); önek eşleşmesi için `:path*`
|
|
605
|
+
kullanın.
|
|
606
|
+
- `:path*` sıfır segment de yakalar ve hemen öncesindeki `/` opsiyoneldir:
|
|
607
|
+
`/hesabim/:path*` bölümün kök yolunu (`/hesabim`) da kapsar. Aksi hâlde bir
|
|
608
|
+
bölümü tamamen kapatmak isteyen kural tam da giriş sayfasını atlıyordu.
|
|
609
|
+
- Parametreler dışındaki tüm karakterler literal kabul edilir ve regex için
|
|
610
|
+
kaçışlanır — `.` gerçekten nokta demektir.
|
|
611
|
+
- Yakalanan değerler `destination` içindeki aynı adlı `:param`'lara yazılır.
|
|
612
|
+
Karşılığı olmayan bir yer tutucu olduğu gibi bırakılır.
|
|
613
|
+
|
|
614
|
+
## Ortam değişkenleri
|
|
615
|
+
|
|
616
|
+
Framework'ün okuduğu tüm değişkenler. `.env` dosyası varsa CLI tarafından
|
|
617
|
+
otomatik yüklenir (`--env-file=.env`); yoksa bayrak hiç geçilmez ve uyarı
|
|
618
|
+
basılmaz.
|
|
619
|
+
|
|
620
|
+
| Değişken | Kim okur | Varsayılan | Anlamı |
|
|
621
|
+
| --- | --- | --- | --- |
|
|
622
|
+
| `NODE_ENV` | her yer | `production` (start/build), `development` (dev) | Dev overlay, EJS cache, manifest yeniden okuma, route hata davranışı ve prewarm varsayılanlarını belirler. `jskelet dev` bunu kendisi ayarlar — `cross-env` gerekmez. |
|
|
623
|
+
| `PORT` | `startServer` | `3000` | Dinlenecek port |
|
|
624
|
+
| `HOST` | `startServer` | `0.0.0.0` | Bağlanılacak arayüz |
|
|
625
|
+
| `DEV_TOKEN` | `devGate`, `prewarm` | — | Ayarlıysa token taşımayan her isteğe 404 döner. Isıtma token'ı çerez olarak taşır. [09](./09-dev-araclari.md) |
|
|
626
|
+
| `PREWARM` | `startPrewarm` | — | `0` ısıtmayı kapatır; `1` config'teki `enabled: false`'u ezip açar |
|
|
627
|
+
| `PREWARM_MAX` | `prewarm` | `400` | En fazla kaç yol ısıtılır |
|
|
628
|
+
| `PREWARM_CONCURRENCY` | `prewarm` | prod 4, dev 2 | Paralel işçi sayısı |
|
|
629
|
+
| `PREWARM_DELAY_MS` | `startPrewarm` | prod 500, dev 3000 | İlk turun gecikmesi |
|
|
630
|
+
| `PREWARM_INTERVAL_SECONDS` | `startPrewarm` | `0` | 0'dan büyükse periyodik tur |
|
|
631
|
+
| `JSKELET_VERBOSE` | `jskelet dev` | — | `1` ise restart'ta değişen dosyaların tamamı listelenir |
|
|
632
|
+
| `JSKELET_COLOR` | `jskelet/log` | — | `1` ise renk zorlanır. Alt süreçler boruya yazdığı için renk algılaması kapanır; `jskelet dev` bunu kendisi ayarlar. |
|
|
633
|
+
| `JSKELET_CHILD` | `jskelet build` | — | Dev script'i tarafından ayarlanır; build banner'ı ve "Ready" özetini bastırır |
|
|
634
|
+
| `NO_COLOR` | `jskelet/log` | — | Ayarlıysa renk hiç kullanılmaz (`JSKELET_COLOR`u da ezer) |
|
|
635
|
+
|
|
636
|
+
Uygulamanızın kendi değişkenleri (API origin'i, token'lar) framework tarafından
|
|
637
|
+
okunmaz; doğrudan `process.env` üzerinden kullanın. Tarayıcıya ulaşması
|
|
638
|
+
gerekenleri `clientEnv` ile bildirin.
|
|
639
|
+
|
|
640
|
+
Sayısal prewarm ayarları yalnızca **pozitif ve sonlu** değer kabul eder;
|
|
641
|
+
geçersiz bir değer sessizce bir sonraki katmana (config → kod varsayılanı)
|
|
642
|
+
düşer.
|
|
643
|
+
|
|
644
|
+
## Programatik erişim
|
|
645
|
+
|
|
646
|
+
```js
|
|
647
|
+
import { getConfig, loadConfig } from "jskelet";
|
|
648
|
+
|
|
649
|
+
await loadConfig(); // proje kökünden okur
|
|
650
|
+
await loadConfig({ root: "/baska/proje" }); // farklı kök
|
|
651
|
+
await loadConfig({ configFile: "jskelet.test.mjs" });
|
|
652
|
+
await loadConfig({ force: true }); // önbelleği atlayıp yeniden oku
|
|
653
|
+
|
|
654
|
+
const config = getConfig(); // çözümlenmiş config
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
`loadConfig()` aynı süreçte ikinci çağrıda önbelleğe düşer: `jskelet start` hem
|
|
658
|
+
`ensure-build` hem `createApp` üzerinden çağırıyor ve config'i iki kez okuyup iki
|
|
659
|
+
kez loglamanın faydası yok.
|
|
660
|
+
|
|
661
|
+
`getConfig()` `loadConfig()` çağrılmadan kullanılırsa **hata verir**: sessiz
|
|
662
|
+
yanlış yol, "stylesheet neden yok" gibi teşhisi zor sorunlara dönüşüyor.
|
|
663
|
+
|
|
664
|
+
Çözümlenmiş config'te dizinler mutlak yol olarak `config.dirs` altındadır
|
|
665
|
+
(`views`, `public`, `client`, `routes`, `styles`, `generated`, `assets`,
|
|
666
|
+
`fonts`), desenler derlenmiş hâldedir ve `config.loaded` dosyanın gerçekten
|
|
667
|
+
okunup okunmadığını söyler.
|
|
668
|
+
|
|
669
|
+
## Sırada ne var
|
|
670
|
+
|
|
671
|
+
- Build tarafındaki alanların etkisi: [08-build.md](./08-build.md)
|
|
672
|
+
- Dev akışı ve `DEV_TOKEN`: [09-dev-araclari.md](./09-dev-araclari.md)
|
|
673
|
+
- Ortam değişkenlerinin dağıtımda kullanımı: [10-dagitim.md](./10-dagitim.md)
|