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,437 @@
|
|
|
1
|
+
# 03 — Routing
|
|
2
|
+
|
|
3
|
+
Bu belge bir isteğin hangi controller'a düştüğünü belirleyen her mekanizmayı
|
|
4
|
+
anlatır: route modüllerinin sözleşmesi ve yükleme sırası, `route()` sarmalayıcısı,
|
|
5
|
+
controller'ın döndürdüğü sayfa tanımı, `ctx` nesnesi, `params`, `notFound()` ve
|
|
6
|
+
`redirect()` kontrol akışı, ve `jskelet.config.mjs` üzerinden gelen
|
|
7
|
+
redirect/rewrite kuralları. Sayfa tanımının şablon tarafı
|
|
8
|
+
[04-render-ve-sablonlar.md](./04-render-ve-sablonlar.md)'de, `revalidate`
|
|
9
|
+
davranışı [06-cache.md](./06-cache.md)'de anlatılıyor.
|
|
10
|
+
|
|
11
|
+
## Route modülü sözleşmesi
|
|
12
|
+
|
|
13
|
+
Bir route modülü, **default export** ya da `register` adlı **named export**
|
|
14
|
+
olarak `(app, api) => void | Promise<void>` imzalı bir fonksiyon açar.
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
// routes/10-pages.mjs
|
|
18
|
+
export default function register(app, { route }) {
|
|
19
|
+
app.get("/", route(async () => ({ view: "pages/home" })));
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`app` doğrudan Express uygulamasıdır: `app.get`, `app.post`, `app.use`,
|
|
24
|
+
`app.all` — Express 5'in tüm yüzeyi kullanılabilir. `api` ise framework'ün route
|
|
25
|
+
dosyalarına geçirdiği hazır yüzeydir, böylece her dosyada tek tek import yapmak
|
|
26
|
+
gerekmez:
|
|
27
|
+
|
|
28
|
+
| Alan | Karşılığı |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| `route` | `jskelet` → `route` |
|
|
31
|
+
| `renderView` | `jskelet` → `renderView` |
|
|
32
|
+
| `renderPage` | `jskelet` → `renderPage` |
|
|
33
|
+
| `notFound` | `jskelet` → `notFound` |
|
|
34
|
+
| `redirect` | `jskelet` → `redirect` |
|
|
35
|
+
| `permanentRedirect` | `jskelet` → `permanentRedirect` |
|
|
36
|
+
|
|
37
|
+
İstersen doğrudan import da edebilirsin; `api` yalnızca kolaylık:
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
import { route, notFound } from "jskelet";
|
|
41
|
+
|
|
42
|
+
export function register(app) {
|
|
43
|
+
app.get("/haber/:slug", route(async ({ params }) => { /* … */ }));
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Modül geçerli bir fonksiyon açmazsa uyarı basılır ve atlanır:
|
|
48
|
+
`[router] <dosya> default ya da 'register' fonksiyonu dışa açmıyor, atlandı`.
|
|
49
|
+
|
|
50
|
+
## Yükleme sırası
|
|
51
|
+
|
|
52
|
+
Dosya sistemine dayalı otomatik URL türetme **yok**. Sıra iki şekilde
|
|
53
|
+
belirlenir:
|
|
54
|
+
|
|
55
|
+
**1. Açık liste (`jskelet.config.mjs` → `routes`).** Proje köküne göre göreli
|
|
56
|
+
yollar, verdiğin sırada yüklenir:
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
export default {
|
|
60
|
+
routes: ["./routes/api.js", "./routes/pages.js", "./routes/catch-all.js"],
|
|
61
|
+
};
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**2. Liste yoksa `routes/` dizini alfabetik taranır.** Tarama özyinelemelidir
|
|
65
|
+
(alt dizinler de dâhil), yalnızca `.js` ve `.mjs` dosyaları alınır ve adı `_`
|
|
66
|
+
ile başlayan dosyalar atlanır (`_helpers.js` gibi paylaşılan modüller için).
|
|
67
|
+
|
|
68
|
+
Bu durumda dosya adlarına sayısal önek verin:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
routes/
|
|
72
|
+
├── 10-pages.mjs
|
|
73
|
+
├── 50-blog.mjs
|
|
74
|
+
└── 99-catch-all.mjs
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Sıranın açık olması bir tasarım kararı: `/:slug` gibi tek segmentli bir
|
|
78
|
+
yakalayıcı `/hakkinda` rotasından önce kaydedilirse "hakkinda" bir slug sanılır.
|
|
79
|
+
Sırayı dosya adına gizlemek yerine görünür kılmak teşhisi kolaylaştırıyor
|
|
80
|
+
([02-mimari.md](./02-mimari.md)).
|
|
81
|
+
|
|
82
|
+
Hiç route modülü bulunamazsa uyarı basılır ve sunucu yalnızca statik dosyalar +
|
|
83
|
+
404 ile ayağa kalkar.
|
|
84
|
+
|
|
85
|
+
### Bozuk modül davranışı
|
|
86
|
+
|
|
87
|
+
- **Development:** modül import edilemezse uyarı basılır ve atlanır; sunucu
|
|
88
|
+
ayakta kalır.
|
|
89
|
+
- **Production:** hata fırlatılır ve süreç açılmaz. Yarım route tablosuyla
|
|
90
|
+
yayına çıkmak, sessizce 404 dönen sayfalar demek.
|
|
91
|
+
|
|
92
|
+
## `route()` — controller sarmalayıcısı
|
|
93
|
+
|
|
94
|
+
`route(controller, options?)` bir Express request handler döndürür ve şu işleri
|
|
95
|
+
üstlenir:
|
|
96
|
+
|
|
97
|
+
- `ctx` nesnesini kurar ve controller'ı çağırır.
|
|
98
|
+
- HTML TTL cache'ini uygular (`revalidate` varsa ve metot `GET` ise).
|
|
99
|
+
- `notFound()` / `redirect()` kontrol akışını yakalar.
|
|
100
|
+
- Yanıt başlıklarını yazar: `Content-Type`, cache'lenebilir yanıtlarda
|
|
101
|
+
`Cache-Control`, ve her zaman `X-JSkelet-Cache`.
|
|
102
|
+
- Önbellekte saklanan sıkıştırılmış gövdeyi kullanarak yanıtı gönderir.
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
app.get(
|
|
106
|
+
"/hakkinda",
|
|
107
|
+
route(
|
|
108
|
+
async () => ({
|
|
109
|
+
view: "pages/about",
|
|
110
|
+
metadata: { title: "Hakkında", canonical: "/hakkinda" },
|
|
111
|
+
}),
|
|
112
|
+
{ revalidate: 300 },
|
|
113
|
+
),
|
|
114
|
+
);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`options` tek bir alan kabul eder:
|
|
118
|
+
|
|
119
|
+
| Alan | Tip | Anlamı |
|
|
120
|
+
| --- | --- | --- |
|
|
121
|
+
| `revalidate` | `number` (saniye) | HTML önbellek TTL'i. Verilmezse ya da 0 ise bu route önbelleklenmez. `jskelet.config.mjs` → `cache().html` içindeki eşleşen bir kural bu değeri **ezer**. |
|
|
122
|
+
|
|
123
|
+
## `ctx` — controller bağlamı
|
|
124
|
+
|
|
125
|
+
Controller tek argüman alır:
|
|
126
|
+
|
|
127
|
+
```js
|
|
128
|
+
{
|
|
129
|
+
params, // Express route parametreleri (req.params)
|
|
130
|
+
query, // Ayrıştırılmış query string (req.query)
|
|
131
|
+
pathname, // req.path — query'siz yol
|
|
132
|
+
req, // Express Request; ihtiyaç olursa tam erişim
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`params` Express'in kendi desen sözdizimini kullanır (Express 5 /
|
|
137
|
+
`path-to-regexp`), config'teki `source` sözdizimini değil:
|
|
138
|
+
|
|
139
|
+
```js
|
|
140
|
+
app.get("/haber/:slug", route(async ({ params }) => {
|
|
141
|
+
const article = await getArticle(params.slug);
|
|
142
|
+
if (!article) notFound();
|
|
143
|
+
return { view: "pages/article", data: { article } };
|
|
144
|
+
}));
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`pathname` hem cache anahtarında hem de `renderPage`'e geçen `pathname`
|
|
148
|
+
local'inde kullanılır; layout'un "bu ana sayfa mı" gibi kararları buna bakar.
|
|
149
|
+
|
|
150
|
+
## Controller'ın döndürdüğü sayfa tanımı
|
|
151
|
+
|
|
152
|
+
Controller `async (ctx) => sayfa` biçimindedir ve şu alanları döndürebilir:
|
|
153
|
+
|
|
154
|
+
| Alan | Tip | Varsayılan | Anlamı |
|
|
155
|
+
| --- | --- | --- | --- |
|
|
156
|
+
| `view` | `string` | — | `views/` altındaki şablon yolu, uzantısız: `"pages/home"` → `views/pages/home.ejs`. |
|
|
157
|
+
| `data` | `object` | `{}` | Şablona local olarak geçen veriler. |
|
|
158
|
+
| `metadata` | `object` | `{}` | `<head>` etiketlerine çevrilir; `hooks.metadata()` çıktısının üzerine biner. Şema: [04-render-ve-sablonlar.md](./04-render-ve-sablonlar.md). |
|
|
159
|
+
| `status` | `number` | `200` | HTTP durum kodu. Yalnızca 200 önbelleğe yazılır. |
|
|
160
|
+
| `head` | `string` | `""` | `<head>`e olduğu gibi basılacak ham HTML (ör. LCP preload'ı). |
|
|
161
|
+
| `bodyClass` | `string` | `hooks.layoutContext().bodyClass ?? ""` | `<body class="…">`. |
|
|
162
|
+
| `entries` | `string[]` | `[]` | Bu sayfada ek olarak yüklenecek client entry adları: `["chart.js"]`. |
|
|
163
|
+
|
|
164
|
+
`revalidate` **`route()`'un ikinci argümanıdır**, controller'ın döndürdüğü
|
|
165
|
+
nesnenin alanı değil.
|
|
166
|
+
|
|
167
|
+
Örnek, hepsi bir arada:
|
|
168
|
+
|
|
169
|
+
```js
|
|
170
|
+
import { headHints } from "jskelet";
|
|
171
|
+
|
|
172
|
+
app.get(
|
|
173
|
+
"/piyasalar",
|
|
174
|
+
route(
|
|
175
|
+
async ({ query }) => {
|
|
176
|
+
const data = await getMarkets(query.tab ?? "hisse");
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
view: "pages/markets",
|
|
180
|
+
data: { markets: data.items, tab: query.tab ?? "hisse" },
|
|
181
|
+
metadata: {
|
|
182
|
+
title: "Piyasalar",
|
|
183
|
+
canonical: "/piyasalar",
|
|
184
|
+
openGraph: { image: data.cover },
|
|
185
|
+
},
|
|
186
|
+
head: headHints({ href: data.cover }),
|
|
187
|
+
bodyClass: "bg-slate-50",
|
|
188
|
+
entries: ["chart.js"],
|
|
189
|
+
};
|
|
190
|
+
},
|
|
191
|
+
{ revalidate: 30 },
|
|
192
|
+
),
|
|
193
|
+
);
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## `notFound()` ve `redirect()`
|
|
197
|
+
|
|
198
|
+
`next/navigation` içindeki kontrol akışının karşılığı: derinlerdeki bir
|
|
199
|
+
fonksiyon `throw` eder, framework yakalar. Böylece veri katmanındaki bir
|
|
200
|
+
fonksiyon, controller'a dönüş değeri taşımak zorunda kalmadan 404 üretebilir.
|
|
201
|
+
|
|
202
|
+
```js
|
|
203
|
+
import { notFound, redirect, permanentRedirect } from "jskelet";
|
|
204
|
+
|
|
205
|
+
notFound(); // 404 → hooks.notFound() sayfası
|
|
206
|
+
redirect("/yeni-adres"); // 307 (geçici)
|
|
207
|
+
permanentRedirect("/yeni"); // 308 (kalıcı)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Üçü de `never` döner (her zaman fırlatır). Ayrıntı:
|
|
211
|
+
|
|
212
|
+
- `notFound()` → `NotFoundError` (`statusCode: 404`)
|
|
213
|
+
- `redirect(location)` → `RedirectError` (`statusCode: 307`)
|
|
214
|
+
- `permanentRedirect(location)` → `RedirectError` (`statusCode: 308`)
|
|
215
|
+
|
|
216
|
+
Özel bir durum kodu gerekiyorsa sınıfı doğrudan kullanabilirsin:
|
|
217
|
+
|
|
218
|
+
```js
|
|
219
|
+
import { RedirectError } from "jskelet";
|
|
220
|
+
|
|
221
|
+
throw new RedirectError("/eski-kurulum-uyumu", 301);
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Ayırt etmek için `isNotFoundError(error)` ve `isRedirectError(error)` dışa açık.
|
|
225
|
+
|
|
226
|
+
Yakalanma noktaları:
|
|
227
|
+
|
|
228
|
+
1. **`route()` içinde:** redirect doğrudan yanıta yazılır; notFound `produce()`
|
|
229
|
+
içinde yakalanır ve 404 sayfası üretilir (bu çıktı önbelleğe **yazılmaz**,
|
|
230
|
+
çünkü yalnızca 200 saklanır).
|
|
231
|
+
2. **Express hata yöneticisinde:** bir middleware ya da route dışı kodda
|
|
232
|
+
fırlatılmışsa burada karşılanır.
|
|
233
|
+
|
|
234
|
+
## 404 sayfası
|
|
235
|
+
|
|
236
|
+
Bir istek hiçbir route'a düşmezse framework `hooks.notFound()` hook'unu çağırır
|
|
237
|
+
ve dönen sayfa tanımını `pathname: "/404"` ile render eder.
|
|
238
|
+
|
|
239
|
+
```js
|
|
240
|
+
// jskelet.config.mjs
|
|
241
|
+
export default {
|
|
242
|
+
hooks: {
|
|
243
|
+
notFound() {
|
|
244
|
+
return {
|
|
245
|
+
view: "pages/not-found",
|
|
246
|
+
metadata: { title: "Sayfa bulunamadı", robots: { index: false } },
|
|
247
|
+
};
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Hook tanımlı değilse ya da 404 render'ı da hata verirse framework şablonsuz,
|
|
254
|
+
minimal bir HTML döner. Bu geri dönüş bilinçli olarak şablonsuz: 404 render'ı da
|
|
255
|
+
patlarsa ziyaretçi boş yanıt görmesin.
|
|
256
|
+
|
|
257
|
+
## Hata sayfaları (500 ve diğerleri)
|
|
258
|
+
|
|
259
|
+
Bir controller ya da middleware beklenmeyen bir hata fırlattığında Express'in
|
|
260
|
+
hata yöneticisi devreye girer, hatayı loglar ve framework'ün kendi hata sayfasını
|
|
261
|
+
`Cache-Control: no-store` ile döner. Durum kodu hatanın `statusCode` (ya da
|
|
262
|
+
`status`) alanından okunur; 400–599 aralığında değilse 500 kullanılır.
|
|
263
|
+
|
|
264
|
+
Framework'ün sayfası bilinçli olarak yalın: durum kodu, tek satır başlık ve tek
|
|
265
|
+
satır açıklama. Marka adı, gezinme ya da hata ayrıntısı taşımaz — sunucunun içi
|
|
266
|
+
ziyaretçiye açılmaz. Dil `brand.lang`ten gelir (`tr` ve `en` hazır, diğerleri
|
|
267
|
+
`en`e düşer).
|
|
268
|
+
|
|
269
|
+
Kendi sayfanı vermek için `hooks.error()`:
|
|
270
|
+
|
|
271
|
+
```js
|
|
272
|
+
// jskelet.config.mjs
|
|
273
|
+
export default {
|
|
274
|
+
hooks: {
|
|
275
|
+
error({ status }) {
|
|
276
|
+
return {
|
|
277
|
+
view: "pages/error",
|
|
278
|
+
data: { status },
|
|
279
|
+
metadata: { title: "Bir hata oluştu", robots: { index: false } },
|
|
280
|
+
};
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Hook bir sayfa tanımı yerine doğrudan HTML string de döndürebilir; layout'a
|
|
287
|
+
bağlı olmayan bir hata sayfası istiyorsan bu yol daha güvenli, çünkü layout'un
|
|
288
|
+
kendisi hata veriyorsa sayfa tanımı da render edilemez. Hook yoksa, `null`
|
|
289
|
+
dönerse ya da render'ı patlarsa framework gömülü sayfaya düşer.
|
|
290
|
+
|
|
291
|
+
404 için `hooks.notFound()` önceliklidir; yalnızca o tanımlı değilse
|
|
292
|
+
`hooks.error()` `status: 404` ile çağrılır.
|
|
293
|
+
|
|
294
|
+
Sayfayı programatik olarak da üretebilirsin:
|
|
295
|
+
|
|
296
|
+
```js
|
|
297
|
+
import { renderStatusPage } from "jskelet";
|
|
298
|
+
|
|
299
|
+
const html = await renderStatusPage(503);
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## Layout'suz render: `renderView`
|
|
303
|
+
|
|
304
|
+
`renderView(view, data)` tek bir şablonu layout olmadan render eder ve string
|
|
305
|
+
döner. Fragment uçları, e-posta şablonları ve island'ların sonradan çektiği
|
|
306
|
+
HTML parçaları için:
|
|
307
|
+
|
|
308
|
+
```js
|
|
309
|
+
export default function register(app, { renderView }) {
|
|
310
|
+
app.get("/_fragment/yorumlar/:id", async (req, res) => {
|
|
311
|
+
const comments = await getComments(req.params.id);
|
|
312
|
+
res.type("html").send(await renderView("fragments/comments", { comments }));
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
`/_fragment/` öneki varsayılan `prewarmSkip` listesinde yer alır, yani ısıtma
|
|
318
|
+
turu bu uçları taramaz ([06-cache.md](./06-cache.md)).
|
|
319
|
+
|
|
320
|
+
## Config: `redirects()`
|
|
321
|
+
|
|
322
|
+
`jskelet.config.mjs` → `redirects()` bir dizi döndürür ve middleware zincirinde
|
|
323
|
+
route'lardan **önce** çalışır (bkz. [02-mimari.md](./02-mimari.md)).
|
|
324
|
+
|
|
325
|
+
```js
|
|
326
|
+
export default {
|
|
327
|
+
async redirects() {
|
|
328
|
+
return [
|
|
329
|
+
{ source: "/eski-blog/:slug", destination: "/blog/:slug", permanent: true },
|
|
330
|
+
{ source: "/kampanya", destination: "/kampanyalar" },
|
|
331
|
+
{ source: "/legacy", destination: "/", statusCode: 301 },
|
|
332
|
+
];
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Davranış:
|
|
338
|
+
|
|
339
|
+
- **İlk eşleşen kural kazanır**, sonrası denenmez. Sıralama config'teki yazım
|
|
340
|
+
sırasıdır.
|
|
341
|
+
- **Query string korunur:** `/eski-blog/x?utm=a` → `/blog/x?utm=a`. Yönlendirme
|
|
342
|
+
kampanya parametrelerini düşürürse trafik kaynağı kaybolur.
|
|
343
|
+
- **Durum kodu:** `permanent: true` → 308, aksi hâlde 307 (Next semantiği).
|
|
344
|
+
Farklı bir kod isteyen `statusCode` verebilir; örneğin eski kurulumlarla uyum
|
|
345
|
+
için 301.
|
|
346
|
+
- `source` ya da `destination` geçersizse kural sessizce düşmez, uyarı basılır.
|
|
347
|
+
|
|
348
|
+
## Config: `rewrites()`
|
|
349
|
+
|
|
350
|
+
Rewrite, tarayıcının adres çubuğunu değiştirmeden isteği başka bir yere taşır.
|
|
351
|
+
İki faz vardır:
|
|
352
|
+
|
|
353
|
+
```js
|
|
354
|
+
export default {
|
|
355
|
+
async rewrites() {
|
|
356
|
+
return {
|
|
357
|
+
beforeFiles: [
|
|
358
|
+
{ source: "/sitemap-:page.xml", destination: "/sitemap?page=:page" },
|
|
359
|
+
],
|
|
360
|
+
afterFiles: [
|
|
361
|
+
{ source: "/api/:path*", destination: "https://api.example.com/:path*" },
|
|
362
|
+
],
|
|
363
|
+
};
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Bir dizi döndürürsen tamamı `afterFiles` sayılır:
|
|
369
|
+
|
|
370
|
+
```js
|
|
371
|
+
async rewrites() {
|
|
372
|
+
return [{ source: "/api/:path*", destination: "https://api.example.com/:path*" }];
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
- **`beforeFiles`** statik dosyalardan da önce çalışır. `/assets/…` gibi yolları
|
|
377
|
+
yeniden yazmak gerekiyorsa buraya konmalı.
|
|
378
|
+
- **`afterFiles`** statik denendikten sonra, route'lardan önce çalışır.
|
|
379
|
+
|
|
380
|
+
Hedefin biçimi davranışı belirler:
|
|
381
|
+
|
|
382
|
+
- **Mutlak (`http://` / `https://`):** istek gömülü ters proxy ile dışa taşınır.
|
|
383
|
+
Harici paket yok; `fetch` ile stream eden ince bir katman. Hop-by-hop
|
|
384
|
+
başlıklar (`host`, `connection`, `content-length`, `accept-encoding`)
|
|
385
|
+
temizlenir; yanıtta `content-encoding`, `content-length`,
|
|
386
|
+
`transfer-encoding`, `connection` düşürülür. `redirect: "manual"` sayesinde
|
|
387
|
+
upstream'in 302'si burada tüketilmez, tarayıcıya iletilir.
|
|
388
|
+
- **Göreli:** yalnızca `req.url` değiştirilir ve istek kendi route tablosunda
|
|
389
|
+
devam eder. Bu fazda ilk eşleşen kural döngüyü kırar.
|
|
390
|
+
|
|
391
|
+
Tipik kullanım `/api/*` yolunu backend'e taşımaktır. Tarayıcı bunu same-origin
|
|
392
|
+
çağırdığı için CORS ve third-party cookie sorunları oluşmaz.
|
|
393
|
+
|
|
394
|
+
### Elle proxy: `createProxy`
|
|
395
|
+
|
|
396
|
+
Aynı proxy'yi kendi route'unda da kullanabilirsin:
|
|
397
|
+
|
|
398
|
+
```js
|
|
399
|
+
import { createProxy } from "jskelet";
|
|
400
|
+
|
|
401
|
+
export default function register(app) {
|
|
402
|
+
app.use("/ws-api", createProxy((req) => `${process.env.API_ORIGIN}${req.url}`));
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
`resolveTarget` fırlatırsa ya da boş döndürürse istek proxy'lenmez ve zincire
|
|
407
|
+
devam eder: hedef origin yapılandırılmamış bir kurulumda 500 yerine normal bir
|
|
408
|
+
404 almak daha doğru.
|
|
409
|
+
|
|
410
|
+
## `source` desen sözdizimi
|
|
411
|
+
|
|
412
|
+
`redirects()`, `rewrites()`, `headers()` ve `cache().html` aynı küçük desen
|
|
413
|
+
derleyicisini kullanır. Bu, Next'in tam `path-to-regexp` yüzeyi değil; config'te
|
|
414
|
+
fiilen kullanılan alt küme bilinçli olarak seçildi.
|
|
415
|
+
|
|
416
|
+
| Desen | Anlamı |
|
|
417
|
+
| --- | --- |
|
|
418
|
+
| `/haber/:slug` | Tek segment yakalar (`[^/]+`) |
|
|
419
|
+
| `/:path*` | Sıfır veya daha fazla segment yakalar; öndeki `/` opsiyoneldir, yani `/blog/:path*` `/blog`u da kapsar |
|
|
420
|
+
| `/:path*.svg` | Joker + sabit son ek; uzantı kuralları böyle yazılır |
|
|
421
|
+
| `/etiket-:slug` | Segment ortasında parametre |
|
|
422
|
+
|
|
423
|
+
Yakalanan değerler `destination` içindeki aynı adlı `:param`'lara yazılır.
|
|
424
|
+
Parametre adı `[A-Za-z_][A-Za-z0-9_]*` kalıbına uymalıdır.
|
|
425
|
+
|
|
426
|
+
`source` mutlaka `/` ile başlamalı; başlamazsa kural yok sayılır ve uyarı
|
|
427
|
+
basılır (`[config] geçersiz source (\`/\` ile başlamalı): …`). Tanınmayan bir
|
|
428
|
+
sözdizimi sessizce literal kabul edilmez.
|
|
429
|
+
|
|
430
|
+
Tam desen listesi ve config referansı: [07-yapilandirma.md](./07-yapilandirma.md).
|
|
431
|
+
|
|
432
|
+
## Sırada ne var
|
|
433
|
+
|
|
434
|
+
- Şablon katmanı, bileşenler ve metadata:
|
|
435
|
+
[04-render-ve-sablonlar.md](./04-render-ve-sablonlar.md)
|
|
436
|
+
- `revalidate`, cache anahtarı ve `X-JSkelet-Cache`: [06-cache.md](./06-cache.md)
|
|
437
|
+
- Config alanlarının tam referansı: [07-yapilandirma.md](./07-yapilandirma.md)
|