@moku-labs/web 1.5.3 → 1.6.0

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.
@@ -1550,15 +1550,24 @@ declare function defineRoutes<T extends RouteMap>(routes: T): T;
1550
1550
  * directly: no `app.router` reference, no manual "bind", no module global, no
1551
1551
  * "not bound" guard, and no createApp ↔ routes cycle.
1552
1552
  *
1553
+ * Pass `defaultLocale` so the builder serves that locale at BARE paths, matching the
1554
+ * runtime `router.toUrl` (which reads the default locale from the i18n plugin):
1555
+ * `toUrl("home", { lang: defaultLocale })` then resolves to `/` instead of
1556
+ * `/{defaultLocale}/`. It is app-free, so the default locale cannot be inferred — the
1557
+ * consumer supplies it (e.g. `createUrls(routes, "en")`). Omit it to keep every locale
1558
+ * prefixed.
1559
+ *
1553
1560
  * @param routes - The route map (typically the value returned by {@link defineRoutes}).
1561
+ * @param defaultLocale - The locale to serve bare (its `{lang:?}` prefix is omitted).
1554
1562
  * @returns A {@link Urls} builder whose `toUrl` accepts only this map's route names.
1555
1563
  * @example
1556
1564
  * ```ts
1557
- * const url = createUrls(routes);
1558
- * url.toUrl("article", { lang: "en", slug: "hello" }); // "/en/hello/"
1565
+ * const url = createUrls(routes, "en");
1566
+ * url.toUrl("article", { lang: "en", slug: "hello" }); // "/hello/"
1567
+ * url.toUrl("article", { lang: "ru", slug: "hello" }); // "/ru/hello/"
1559
1568
  * ```
1560
1569
  */
1561
- declare function createUrls<T extends RouteMap>(routes: T): Urls<T>;
1570
+ declare function createUrls<T extends RouteMap>(routes: T, defaultLocale?: string): Urls<T>;
1562
1571
  //#endregion
1563
1572
  //#region src/plugins/router/index.d.ts
1564
1573
  /**
package/dist/browser.mjs CHANGED
@@ -1587,15 +1587,22 @@ function patternToUrlPattern(pattern, variant, langRegex) {
1587
1587
  * param is absent has its segment skipped entirely (no empty segment), so a missing
1588
1588
  * `{lang:?}` collapses cleanly instead of leaving a double slash.
1589
1589
  *
1590
+ * The default locale is served at BARE paths: when `defaultLocale` is given, the
1591
+ * optional `{lang:?}` segment is also skipped for it (so `{ lang: defaultLocale }`
1592
+ * resolves to `/…` while every other locale keeps its `/{locale}/…` prefix).
1593
+ *
1590
1594
  * @param pattern - The route pattern.
1591
1595
  * @param params - Param values to substitute.
1596
+ * @param defaultLocale - The locale served bare (its `{lang:?}` segment is omitted).
1592
1597
  * @returns The resolved relative URL string.
1593
1598
  * @example
1594
1599
  * ```ts
1595
1600
  * buildUrl("/{slug}/", { slug: "hello" }); // "/hello/"
1601
+ * buildUrl("/{lang:?}/", { lang: "en" }, "en"); // "/"
1602
+ * buildUrl("/{lang:?}/", { lang: "ru" }, "en"); // "/ru/"
1596
1603
  * ```
1597
1604
  */
1598
- function buildUrl(pattern, params) {
1605
+ function buildUrl(pattern, params, defaultLocale) {
1599
1606
  const out = [];
1600
1607
  for (const segment of pattern.split("/")) {
1601
1608
  const placeholder = parsePlaceholder(segment);
@@ -1605,6 +1612,7 @@ function buildUrl(pattern, params) {
1605
1612
  }
1606
1613
  const value = params[placeholder.name] ?? "";
1607
1614
  if (placeholder.optional && value === "") continue;
1615
+ if (placeholder.name === "lang" && placeholder.optional && value === defaultLocale) continue;
1608
1616
  out.push(value);
1609
1617
  }
1610
1618
  return out.join("/");
@@ -1614,14 +1622,15 @@ function buildUrl(pattern, params) {
1614
1622
  *
1615
1623
  * @param pattern - The route pattern.
1616
1624
  * @param params - Param values to substitute.
1625
+ * @param defaultLocale - The locale served bare (forwarded to {@link buildUrl}).
1617
1626
  * @returns The output file path, e.g. `hello/index.html`.
1618
1627
  * @example
1619
1628
  * ```ts
1620
1629
  * buildFilePath("/{slug}/", { slug: "hello" });
1621
1630
  * ```
1622
1631
  */
1623
- function buildFilePath(pattern, params) {
1624
- const cleanPath = buildUrl(pattern, params).replace(/^\//, "").replace(/\/$/, "");
1632
+ function buildFilePath(pattern, params, defaultLocale) {
1633
+ const cleanPath = buildUrl(pattern, params, defaultLocale).replace(/^\//, "").replace(/\/$/, "");
1625
1634
  return cleanPath === "" ? "index.html" : `${cleanPath}/index.html`;
1626
1635
  }
1627
1636
  /**
@@ -1650,15 +1659,16 @@ function buildMatchers(pattern, locales) {
1650
1659
  * pattern.
1651
1660
  *
1652
1661
  * @param pattern - The route pattern bound into the closure.
1662
+ * @param defaultLocale - The locale served bare (bound into the closure).
1653
1663
  * @returns A function mapping params to the resolved relative URL.
1654
1664
  * @example
1655
1665
  * ```ts
1656
- * const toUrl = createToUrlFn("/{slug}/");
1666
+ * const toUrl = createToUrlFn("/{slug}/", "en");
1657
1667
  * toUrl({ slug: "x" }); // "/x/"
1658
1668
  * ```
1659
1669
  */
1660
- function createToUrlFunction(pattern) {
1661
- return (params) => buildUrl(pattern, params);
1670
+ function createToUrlFunction(pattern, defaultLocale) {
1671
+ return (params) => buildUrl(pattern, params, defaultLocale);
1662
1672
  }
1663
1673
  /**
1664
1674
  * Build the `toFile` closure for a route — resolves the output file path from
@@ -1667,15 +1677,16 @@ function createToUrlFunction(pattern) {
1667
1677
  *
1668
1678
  * @param pattern - The route pattern bound into the closure.
1669
1679
  * @param definition - The route definition carrying any `toFile` override.
1680
+ * @param defaultLocale - The locale served bare (bound into the closure).
1670
1681
  * @returns A function mapping params to the output file path.
1671
1682
  * @example
1672
1683
  * ```ts
1673
- * const toFile = createToFileFn("/{slug}/", definition);
1684
+ * const toFile = createToFileFn("/{slug}/", definition, "en");
1674
1685
  * toFile({ slug: "x" }); // "x/index.html"
1675
1686
  * ```
1676
1687
  */
1677
- function createToFileFunction(pattern, definition) {
1678
- return (params) => definition._handlers.toFile?.(params) ?? buildFilePath(pattern, params);
1688
+ function createToFileFunction(pattern, definition, defaultLocale) {
1689
+ return (params) => definition._handlers.toFile?.(params) ?? buildFilePath(pattern, params, defaultLocale);
1679
1690
  }
1680
1691
  /**
1681
1692
  * Compile a single route definition into its `CompiledRoute` entry.
@@ -1692,8 +1703,8 @@ function createToFileFunction(pattern, definition) {
1692
1703
  function compileRoute(name, definition, input) {
1693
1704
  const { pattern } = definition;
1694
1705
  const matchers = buildMatchers(pattern, input.locales);
1695
- const toUrl = createToUrlFunction(pattern);
1696
- const toFile = createToFileFunction(pattern, definition);
1706
+ const toUrl = createToUrlFunction(pattern, input.defaultLocale);
1707
+ const toFile = createToFileFunction(pattern, definition, input.defaultLocale);
1697
1708
  return {
1698
1709
  name,
1699
1710
  pattern,
@@ -2124,15 +2135,24 @@ function defineRoutes(routes) {
2124
2135
  * directly: no `app.router` reference, no manual "bind", no module global, no
2125
2136
  * "not bound" guard, and no createApp ↔ routes cycle.
2126
2137
  *
2138
+ * Pass `defaultLocale` so the builder serves that locale at BARE paths, matching the
2139
+ * runtime `router.toUrl` (which reads the default locale from the i18n plugin):
2140
+ * `toUrl("home", { lang: defaultLocale })` then resolves to `/` instead of
2141
+ * `/{defaultLocale}/`. It is app-free, so the default locale cannot be inferred — the
2142
+ * consumer supplies it (e.g. `createUrls(routes, "en")`). Omit it to keep every locale
2143
+ * prefixed.
2144
+ *
2127
2145
  * @param routes - The route map (typically the value returned by {@link defineRoutes}).
2146
+ * @param defaultLocale - The locale to serve bare (its `{lang:?}` prefix is omitted).
2128
2147
  * @returns A {@link Urls} builder whose `toUrl` accepts only this map's route names.
2129
2148
  * @example
2130
2149
  * ```ts
2131
- * const url = createUrls(routes);
2132
- * url.toUrl("article", { lang: "en", slug: "hello" }); // "/en/hello/"
2150
+ * const url = createUrls(routes, "en");
2151
+ * url.toUrl("article", { lang: "en", slug: "hello" }); // "/hello/"
2152
+ * url.toUrl("article", { lang: "ru", slug: "hello" }); // "/ru/hello/"
2133
2153
  * ```
2134
2154
  */
2135
- function createUrls(routes) {
2155
+ function createUrls(routes, defaultLocale) {
2136
2156
  return {
2137
2157
  /**
2138
2158
  * Build a route's URL path from its name and params.
@@ -2143,13 +2163,13 @@ function createUrls(routes) {
2143
2163
  * @throws {Error} If `name` is not present in the route map.
2144
2164
  * @example
2145
2165
  * ```ts
2146
- * url.toUrl("home", { lang: "en" }); // "/en/"
2166
+ * url.toUrl("home", { lang: "ru" }); // "/ru/"
2147
2167
  * ```
2148
2168
  */
2149
2169
  toUrl(name, params = {}) {
2150
2170
  const definition = routes[name];
2151
2171
  if (!definition) throw new Error(`[web] router: unknown route name "${String(name)}".\n Check the name matches a key in the route map passed to createUrls.`);
2152
- return buildUrl(definition.pattern, params);
2172
+ return buildUrl(definition.pattern, params, defaultLocale);
2153
2173
  } };
2154
2174
  }
2155
2175
  //#endregion
@@ -4424,10 +4444,31 @@ function contentApi(ctx) {
4424
4444
  */
4425
4445
  async function resolveArticle(ctx, slug, locale) {
4426
4446
  const native = await ctx.provider.readArticle(slug, locale, locale, false);
4427
- if (native !== null) return native;
4447
+ if (native !== null) return bareDefaultLocaleUrl(ctx, native);
4428
4448
  const fallbackLocale = ctx.defaultLocale();
4429
4449
  if (fallbackLocale === locale) return null;
4430
- return ctx.provider.readArticle(slug, fallbackLocale, locale, true);
4450
+ const fallback = await ctx.provider.readArticle(slug, fallbackLocale, locale, true);
4451
+ return fallback === null ? fallback : bareDefaultLocaleUrl(ctx, fallback);
4452
+ }
4453
+ /**
4454
+ * The default locale is served at BARE paths, so strip the leading `/{defaultLocale}`
4455
+ * from a default-locale article's `url` (`/en/hello/` → `/hello/`). Providers build
4456
+ * always-prefixed URLs (they have no i18n access); this is the single place the bare
4457
+ * default is applied, so article canonicals + feed GUIDs match the served URL.
4458
+ * Idempotent (a bare URL has no prefix to strip) and a no-op for non-default locales.
4459
+ *
4460
+ * @param ctx - Kernel-free domain context (i18n helpers).
4461
+ * @param article - The resolved article (mutated in place).
4462
+ * @returns The same article, with a bare `url` when it is the default locale.
4463
+ * @example
4464
+ * ```ts
4465
+ * bareDefaultLocaleUrl(ctx, { ...article, locale: "en", url: "/en/hello/" }).url; // "/hello/"
4466
+ * ```
4467
+ */
4468
+ function bareDefaultLocaleUrl(ctx, article) {
4469
+ const prefix = `/${ctx.defaultLocale()}`;
4470
+ if (article.locale === ctx.defaultLocale() && article.url.startsWith(`${prefix}/`)) article.url = article.url.slice(prefix.length);
4471
+ return article;
4431
4472
  }
4432
4473
  /**
4433
4474
  * Comparator sorting articles by frontmatter date descending (newest first),
package/dist/index.cjs CHANGED
@@ -1232,10 +1232,31 @@ function contentApi(ctx) {
1232
1232
  */
1233
1233
  async function resolveArticle(ctx, slug, locale) {
1234
1234
  const native = await ctx.provider.readArticle(slug, locale, locale, false);
1235
- if (native !== null) return native;
1235
+ if (native !== null) return bareDefaultLocaleUrl(ctx, native);
1236
1236
  const fallbackLocale = ctx.defaultLocale();
1237
1237
  if (fallbackLocale === locale) return null;
1238
- return ctx.provider.readArticle(slug, fallbackLocale, locale, true);
1238
+ const fallback = await ctx.provider.readArticle(slug, fallbackLocale, locale, true);
1239
+ return fallback === null ? fallback : bareDefaultLocaleUrl(ctx, fallback);
1240
+ }
1241
+ /**
1242
+ * The default locale is served at BARE paths, so strip the leading `/{defaultLocale}`
1243
+ * from a default-locale article's `url` (`/en/hello/` → `/hello/`). Providers build
1244
+ * always-prefixed URLs (they have no i18n access); this is the single place the bare
1245
+ * default is applied, so article canonicals + feed GUIDs match the served URL.
1246
+ * Idempotent (a bare URL has no prefix to strip) and a no-op for non-default locales.
1247
+ *
1248
+ * @param ctx - Kernel-free domain context (i18n helpers).
1249
+ * @param article - The resolved article (mutated in place).
1250
+ * @returns The same article, with a bare `url` when it is the default locale.
1251
+ * @example
1252
+ * ```ts
1253
+ * bareDefaultLocaleUrl(ctx, { ...article, locale: "en", url: "/en/hello/" }).url; // "/hello/"
1254
+ * ```
1255
+ */
1256
+ function bareDefaultLocaleUrl(ctx, article) {
1257
+ const prefix = `/${ctx.defaultLocale()}`;
1258
+ if (article.locale === ctx.defaultLocale() && article.url.startsWith(`${prefix}/`)) article.url = article.url.slice(prefix.length);
1259
+ return article;
1239
1260
  }
1240
1261
  /**
1241
1262
  * Comparator sorting articles by frontmatter date descending (newest first),
@@ -2145,15 +2166,22 @@ function patternToUrlPattern(pattern, variant, langRegex) {
2145
2166
  * param is absent has its segment skipped entirely (no empty segment), so a missing
2146
2167
  * `{lang:?}` collapses cleanly instead of leaving a double slash.
2147
2168
  *
2169
+ * The default locale is served at BARE paths: when `defaultLocale` is given, the
2170
+ * optional `{lang:?}` segment is also skipped for it (so `{ lang: defaultLocale }`
2171
+ * resolves to `/…` while every other locale keeps its `/{locale}/…` prefix).
2172
+ *
2148
2173
  * @param pattern - The route pattern.
2149
2174
  * @param params - Param values to substitute.
2175
+ * @param defaultLocale - The locale served bare (its `{lang:?}` segment is omitted).
2150
2176
  * @returns The resolved relative URL string.
2151
2177
  * @example
2152
2178
  * ```ts
2153
2179
  * buildUrl("/{slug}/", { slug: "hello" }); // "/hello/"
2180
+ * buildUrl("/{lang:?}/", { lang: "en" }, "en"); // "/"
2181
+ * buildUrl("/{lang:?}/", { lang: "ru" }, "en"); // "/ru/"
2154
2182
  * ```
2155
2183
  */
2156
- function buildUrl(pattern, params) {
2184
+ function buildUrl(pattern, params, defaultLocale) {
2157
2185
  const out = [];
2158
2186
  for (const segment of pattern.split("/")) {
2159
2187
  const placeholder = parsePlaceholder(segment);
@@ -2163,6 +2191,7 @@ function buildUrl(pattern, params) {
2163
2191
  }
2164
2192
  const value = params[placeholder.name] ?? "";
2165
2193
  if (placeholder.optional && value === "") continue;
2194
+ if (placeholder.name === "lang" && placeholder.optional && value === defaultLocale) continue;
2166
2195
  out.push(value);
2167
2196
  }
2168
2197
  return out.join("/");
@@ -2172,14 +2201,15 @@ function buildUrl(pattern, params) {
2172
2201
  *
2173
2202
  * @param pattern - The route pattern.
2174
2203
  * @param params - Param values to substitute.
2204
+ * @param defaultLocale - The locale served bare (forwarded to {@link buildUrl}).
2175
2205
  * @returns The output file path, e.g. `hello/index.html`.
2176
2206
  * @example
2177
2207
  * ```ts
2178
2208
  * buildFilePath("/{slug}/", { slug: "hello" });
2179
2209
  * ```
2180
2210
  */
2181
- function buildFilePath(pattern, params) {
2182
- const cleanPath = buildUrl(pattern, params).replace(/^\//, "").replace(/\/$/, "");
2211
+ function buildFilePath(pattern, params, defaultLocale) {
2212
+ const cleanPath = buildUrl(pattern, params, defaultLocale).replace(/^\//, "").replace(/\/$/, "");
2183
2213
  return cleanPath === "" ? "index.html" : `${cleanPath}/index.html`;
2184
2214
  }
2185
2215
  /**
@@ -2208,15 +2238,16 @@ function buildMatchers(pattern, locales) {
2208
2238
  * pattern.
2209
2239
  *
2210
2240
  * @param pattern - The route pattern bound into the closure.
2241
+ * @param defaultLocale - The locale served bare (bound into the closure).
2211
2242
  * @returns A function mapping params to the resolved relative URL.
2212
2243
  * @example
2213
2244
  * ```ts
2214
- * const toUrl = createToUrlFn("/{slug}/");
2245
+ * const toUrl = createToUrlFn("/{slug}/", "en");
2215
2246
  * toUrl({ slug: "x" }); // "/x/"
2216
2247
  * ```
2217
2248
  */
2218
- function createToUrlFunction(pattern) {
2219
- return (params) => buildUrl(pattern, params);
2249
+ function createToUrlFunction(pattern, defaultLocale) {
2250
+ return (params) => buildUrl(pattern, params, defaultLocale);
2220
2251
  }
2221
2252
  /**
2222
2253
  * Build the `toFile` closure for a route — resolves the output file path from
@@ -2225,15 +2256,16 @@ function createToUrlFunction(pattern) {
2225
2256
  *
2226
2257
  * @param pattern - The route pattern bound into the closure.
2227
2258
  * @param definition - The route definition carrying any `toFile` override.
2259
+ * @param defaultLocale - The locale served bare (bound into the closure).
2228
2260
  * @returns A function mapping params to the output file path.
2229
2261
  * @example
2230
2262
  * ```ts
2231
- * const toFile = createToFileFn("/{slug}/", definition);
2263
+ * const toFile = createToFileFn("/{slug}/", definition, "en");
2232
2264
  * toFile({ slug: "x" }); // "x/index.html"
2233
2265
  * ```
2234
2266
  */
2235
- function createToFileFunction(pattern, definition) {
2236
- return (params) => definition._handlers.toFile?.(params) ?? buildFilePath(pattern, params);
2267
+ function createToFileFunction(pattern, definition, defaultLocale) {
2268
+ return (params) => definition._handlers.toFile?.(params) ?? buildFilePath(pattern, params, defaultLocale);
2237
2269
  }
2238
2270
  /**
2239
2271
  * Compile a single route definition into its `CompiledRoute` entry.
@@ -2250,8 +2282,8 @@ function createToFileFunction(pattern, definition) {
2250
2282
  function compileRoute(name, definition, input) {
2251
2283
  const { pattern } = definition;
2252
2284
  const matchers = buildMatchers(pattern, input.locales);
2253
- const toUrl = createToUrlFunction(pattern);
2254
- const toFile = createToFileFunction(pattern, definition);
2285
+ const toUrl = createToUrlFunction(pattern, input.defaultLocale);
2286
+ const toFile = createToFileFunction(pattern, definition, input.defaultLocale);
2255
2287
  return {
2256
2288
  name,
2257
2289
  pattern,
@@ -2682,15 +2714,24 @@ function defineRoutes(routes) {
2682
2714
  * directly: no `app.router` reference, no manual "bind", no module global, no
2683
2715
  * "not bound" guard, and no createApp ↔ routes cycle.
2684
2716
  *
2717
+ * Pass `defaultLocale` so the builder serves that locale at BARE paths, matching the
2718
+ * runtime `router.toUrl` (which reads the default locale from the i18n plugin):
2719
+ * `toUrl("home", { lang: defaultLocale })` then resolves to `/` instead of
2720
+ * `/{defaultLocale}/`. It is app-free, so the default locale cannot be inferred — the
2721
+ * consumer supplies it (e.g. `createUrls(routes, "en")`). Omit it to keep every locale
2722
+ * prefixed.
2723
+ *
2685
2724
  * @param routes - The route map (typically the value returned by {@link defineRoutes}).
2725
+ * @param defaultLocale - The locale to serve bare (its `{lang:?}` prefix is omitted).
2686
2726
  * @returns A {@link Urls} builder whose `toUrl` accepts only this map's route names.
2687
2727
  * @example
2688
2728
  * ```ts
2689
- * const url = createUrls(routes);
2690
- * url.toUrl("article", { lang: "en", slug: "hello" }); // "/en/hello/"
2729
+ * const url = createUrls(routes, "en");
2730
+ * url.toUrl("article", { lang: "en", slug: "hello" }); // "/hello/"
2731
+ * url.toUrl("article", { lang: "ru", slug: "hello" }); // "/ru/hello/"
2691
2732
  * ```
2692
2733
  */
2693
- function createUrls(routes) {
2734
+ function createUrls(routes, defaultLocale) {
2694
2735
  return {
2695
2736
  /**
2696
2737
  * Build a route's URL path from its name and params.
@@ -2701,13 +2742,13 @@ function createUrls(routes) {
2701
2742
  * @throws {Error} If `name` is not present in the route map.
2702
2743
  * @example
2703
2744
  * ```ts
2704
- * url.toUrl("home", { lang: "en" }); // "/en/"
2745
+ * url.toUrl("home", { lang: "ru" }); // "/ru/"
2705
2746
  * ```
2706
2747
  */
2707
2748
  toUrl(name, params = {}) {
2708
2749
  const definition = routes[name];
2709
2750
  if (!definition) throw new Error(`[web] router: unknown route name "${String(name)}".\n Check the name matches a key in the route map passed to createUrls.`);
2710
- return buildUrl(definition.pattern, params);
2751
+ return buildUrl(definition.pattern, params, defaultLocale);
2711
2752
  } };
2712
2753
  }
2713
2754
  //#endregion
@@ -5117,7 +5158,24 @@ function renderBodyCached(ctx, instance, routeContext, data, reuse) {
5117
5158
  * ```
5118
5159
  */
5119
5160
  async function writeDocument(outDir, entry, params, html) {
5120
- const filePath = node_path.default.join(outDir, entry.toFile(params));
5161
+ await writeDocumentAt(outDir, entry.toFile(params), html);
5162
+ }
5163
+ /**
5164
+ * Write an HTML document to an explicit relative path under `outDir`, creating parent
5165
+ * directories first. Backs both the canonical page path ({@link writeDocument}) and the
5166
+ * default-locale `/{defaultLocale}/…` alias copy ({@link renderInstance}).
5167
+ *
5168
+ * @param outDir - The build output directory.
5169
+ * @param relativeFile - The output file path relative to `outDir` (e.g. `en/index.html`).
5170
+ * @param html - The complete HTML document to write.
5171
+ * @returns A promise resolved once the file is written.
5172
+ * @example
5173
+ * ```ts
5174
+ * await writeDocumentAt("dist", "en/about/index.html", "<!DOCTYPE html>…");
5175
+ * ```
5176
+ */
5177
+ async function writeDocumentAt(outDir, relativeFile, html) {
5178
+ const filePath = node_path.default.join(outDir, relativeFile);
5121
5179
  await (0, node_fs_promises.mkdir)(node_path.default.dirname(filePath), { recursive: true });
5122
5180
  await (0, node_fs_promises.writeFile)(filePath, html, "utf8");
5123
5181
  }
@@ -5127,14 +5185,19 @@ async function writeDocument(outDir, entry, params, html) {
5127
5185
  * `<head>`/body → assemble the document (template fill or in-code shell) → write.
5128
5186
  * Uses the configured shell `template` when supplied, otherwise the in-code shell.
5129
5187
  *
5188
+ * The default locale is served at BARE paths, so each default-locale page on a
5189
+ * `{lang:?}` route is ALSO written to `/{defaultLocale}/…` — the SAME rendered HTML (its
5190
+ * canonical already points at the bare URL) — so an explicit `/{defaultLocale}/…` link
5191
+ * serves the page directly with no redirect. Both pages are returned so each gets a sidecar.
5192
+ *
5130
5193
  * @param ctx - Plugin context (provides `require`, `state`, `config`, `has`).
5131
5194
  * @param instance - The concrete page instance to render.
5132
- * @param shell - Per-build wiring shared across instances (asset tags + template).
5195
+ * @param shell - Per-build wiring shared across instances (asset tags + template + default locale).
5133
5196
  * @param reuse - Whether this run may reuse a cached body (incremental, no code change).
5134
- * @returns The instance's URL, rendered HTML, loaded data, and client-nav flag.
5197
+ * @returns The rendered page(s): the canonical page, plus the `/{defaultLocale}/` alias when emitted.
5135
5198
  * @example
5136
5199
  * ```ts
5137
- * await renderInstance(ctx, instance, { assets: "", template: null }, false);
5200
+ * await renderInstance(ctx, instance, shell, false);
5138
5201
  * ```
5139
5202
  */
5140
5203
  async function renderInstance(ctx, instance, shell, reuse) {
@@ -5156,20 +5219,31 @@ async function renderInstance(ctx, instance, shell, reuse) {
5156
5219
  };
5157
5220
  const html = shell.template === null ? renderDocument(parts) : fillTemplate(shell.template, parts);
5158
5221
  await writeDocument(ctx.config.outDir, entry, params, html);
5159
- return {
5222
+ const clientNavigable = definition._handlers.render !== void 0;
5223
+ const pages = [{
5160
5224
  url,
5161
5225
  html,
5162
5226
  data,
5163
- clientNavigable: definition._handlers.render !== void 0
5164
- };
5227
+ clientNavigable
5228
+ }];
5229
+ if (locale === shell.defaultLocale && entry.pattern.includes("{lang:?}")) {
5230
+ await writeDocumentAt(ctx.config.outDir, `${shell.defaultLocale}/${entry.toFile(params)}`, html);
5231
+ pages.push({
5232
+ url: `/${shell.defaultLocale}${url}`,
5233
+ html,
5234
+ data,
5235
+ clientNavigable
5236
+ });
5237
+ }
5238
+ return pages;
5165
5239
  }
5166
5240
  /**
5167
5241
  * Prepare the per-build {@link RenderShell} ONCE (O(1) per page): read the optional
5168
5242
  * shell `template` from disk when configured + present, and precompute the injected
5169
5243
  * asset tags. `template` is `null` when unset/missing (use the in-code shell).
5170
5244
  *
5171
- * @param ctx - Plugin context (provides `config`, `state`).
5172
- * @returns The shared shell wiring (asset tags + template-or-null) for every page.
5245
+ * @param ctx - Plugin context (provides `config`, `state`, `require`).
5246
+ * @returns The shared shell wiring (asset tags + template-or-null + default locale) for every page.
5173
5247
  * @example
5174
5248
  * ```ts
5175
5249
  * const shell = await prepareShell(ctx);
@@ -5180,7 +5254,8 @@ async function prepareShell(ctx) {
5180
5254
  const template = typeof templatePath === "string" && (0, node_fs.existsSync)(templatePath) ? await (0, node_fs_promises.readFile)(templatePath, "utf8") : null;
5181
5255
  return {
5182
5256
  assets: buildAssetTags(ctx),
5183
- template
5257
+ template,
5258
+ defaultLocale: ctx.require(i18nPlugin).defaultLocale()
5184
5259
  };
5185
5260
  }
5186
5261
  /**
@@ -5314,7 +5389,7 @@ async function renderPages(ctx, options) {
5314
5389
  const byPattern = makeEntryMap(router);
5315
5390
  if (!reuse) ctx.state.renderCache.clear();
5316
5391
  const shell = await prepareShell(ctx);
5317
- const rendered = await renderInBatches(await expandAllInstances(manifest, locales, byPattern, ctx), reuse ? INCREMENTAL_BATCH_SIZE : RENDER_BATCH_SIZE, (instance) => renderInstance(ctx, instance, shell, reuse));
5392
+ const rendered = (await renderInBatches(await expandAllInstances(manifest, locales, byPattern, ctx), reuse ? INCREMENTAL_BATCH_SIZE : RENDER_BATCH_SIZE, (instance) => renderInstance(ctx, instance, shell, reuse))).flat();
5318
5393
  await writeDataSidecars(ctx, rendered, router.mode());
5319
5394
  ctx.log.debug("build:pages", { count: rendered.length });
5320
5395
  return {
package/dist/index.d.cts CHANGED
@@ -3326,15 +3326,24 @@ declare function defineRoutes<T extends RouteMap>(routes: T): T;
3326
3326
  * directly: no `app.router` reference, no manual "bind", no module global, no
3327
3327
  * "not bound" guard, and no createApp ↔ routes cycle.
3328
3328
  *
3329
+ * Pass `defaultLocale` so the builder serves that locale at BARE paths, matching the
3330
+ * runtime `router.toUrl` (which reads the default locale from the i18n plugin):
3331
+ * `toUrl("home", { lang: defaultLocale })` then resolves to `/` instead of
3332
+ * `/{defaultLocale}/`. It is app-free, so the default locale cannot be inferred — the
3333
+ * consumer supplies it (e.g. `createUrls(routes, "en")`). Omit it to keep every locale
3334
+ * prefixed.
3335
+ *
3329
3336
  * @param routes - The route map (typically the value returned by {@link defineRoutes}).
3337
+ * @param defaultLocale - The locale to serve bare (its `{lang:?}` prefix is omitted).
3330
3338
  * @returns A {@link Urls} builder whose `toUrl` accepts only this map's route names.
3331
3339
  * @example
3332
3340
  * ```ts
3333
- * const url = createUrls(routes);
3334
- * url.toUrl("article", { lang: "en", slug: "hello" }); // "/en/hello/"
3341
+ * const url = createUrls(routes, "en");
3342
+ * url.toUrl("article", { lang: "en", slug: "hello" }); // "/hello/"
3343
+ * url.toUrl("article", { lang: "ru", slug: "hello" }); // "/ru/hello/"
3335
3344
  * ```
3336
3345
  */
3337
- declare function createUrls<T extends RouteMap>(routes: T): Urls<T>;
3346
+ declare function createUrls<T extends RouteMap>(routes: T, defaultLocale?: string): Urls<T>;
3338
3347
  //#endregion
3339
3348
  //#region src/plugins/router/index.d.ts
3340
3349
  /**
package/dist/index.d.mts CHANGED
@@ -3326,15 +3326,24 @@ declare function defineRoutes<T extends RouteMap>(routes: T): T;
3326
3326
  * directly: no `app.router` reference, no manual "bind", no module global, no
3327
3327
  * "not bound" guard, and no createApp ↔ routes cycle.
3328
3328
  *
3329
+ * Pass `defaultLocale` so the builder serves that locale at BARE paths, matching the
3330
+ * runtime `router.toUrl` (which reads the default locale from the i18n plugin):
3331
+ * `toUrl("home", { lang: defaultLocale })` then resolves to `/` instead of
3332
+ * `/{defaultLocale}/`. It is app-free, so the default locale cannot be inferred — the
3333
+ * consumer supplies it (e.g. `createUrls(routes, "en")`). Omit it to keep every locale
3334
+ * prefixed.
3335
+ *
3329
3336
  * @param routes - The route map (typically the value returned by {@link defineRoutes}).
3337
+ * @param defaultLocale - The locale to serve bare (its `{lang:?}` prefix is omitted).
3330
3338
  * @returns A {@link Urls} builder whose `toUrl` accepts only this map's route names.
3331
3339
  * @example
3332
3340
  * ```ts
3333
- * const url = createUrls(routes);
3334
- * url.toUrl("article", { lang: "en", slug: "hello" }); // "/en/hello/"
3341
+ * const url = createUrls(routes, "en");
3342
+ * url.toUrl("article", { lang: "en", slug: "hello" }); // "/hello/"
3343
+ * url.toUrl("article", { lang: "ru", slug: "hello" }); // "/ru/hello/"
3335
3344
  * ```
3336
3345
  */
3337
- declare function createUrls<T extends RouteMap>(routes: T): Urls<T>;
3346
+ declare function createUrls<T extends RouteMap>(routes: T, defaultLocale?: string): Urls<T>;
3338
3347
  //#endregion
3339
3348
  //#region src/plugins/router/index.d.ts
3340
3349
  /**
package/dist/index.mjs CHANGED
@@ -1219,10 +1219,31 @@ function contentApi(ctx) {
1219
1219
  */
1220
1220
  async function resolveArticle(ctx, slug, locale) {
1221
1221
  const native = await ctx.provider.readArticle(slug, locale, locale, false);
1222
- if (native !== null) return native;
1222
+ if (native !== null) return bareDefaultLocaleUrl(ctx, native);
1223
1223
  const fallbackLocale = ctx.defaultLocale();
1224
1224
  if (fallbackLocale === locale) return null;
1225
- return ctx.provider.readArticle(slug, fallbackLocale, locale, true);
1225
+ const fallback = await ctx.provider.readArticle(slug, fallbackLocale, locale, true);
1226
+ return fallback === null ? fallback : bareDefaultLocaleUrl(ctx, fallback);
1227
+ }
1228
+ /**
1229
+ * The default locale is served at BARE paths, so strip the leading `/{defaultLocale}`
1230
+ * from a default-locale article's `url` (`/en/hello/` → `/hello/`). Providers build
1231
+ * always-prefixed URLs (they have no i18n access); this is the single place the bare
1232
+ * default is applied, so article canonicals + feed GUIDs match the served URL.
1233
+ * Idempotent (a bare URL has no prefix to strip) and a no-op for non-default locales.
1234
+ *
1235
+ * @param ctx - Kernel-free domain context (i18n helpers).
1236
+ * @param article - The resolved article (mutated in place).
1237
+ * @returns The same article, with a bare `url` when it is the default locale.
1238
+ * @example
1239
+ * ```ts
1240
+ * bareDefaultLocaleUrl(ctx, { ...article, locale: "en", url: "/en/hello/" }).url; // "/hello/"
1241
+ * ```
1242
+ */
1243
+ function bareDefaultLocaleUrl(ctx, article) {
1244
+ const prefix = `/${ctx.defaultLocale()}`;
1245
+ if (article.locale === ctx.defaultLocale() && article.url.startsWith(`${prefix}/`)) article.url = article.url.slice(prefix.length);
1246
+ return article;
1226
1247
  }
1227
1248
  /**
1228
1249
  * Comparator sorting articles by frontmatter date descending (newest first),
@@ -2132,15 +2153,22 @@ function patternToUrlPattern(pattern, variant, langRegex) {
2132
2153
  * param is absent has its segment skipped entirely (no empty segment), so a missing
2133
2154
  * `{lang:?}` collapses cleanly instead of leaving a double slash.
2134
2155
  *
2156
+ * The default locale is served at BARE paths: when `defaultLocale` is given, the
2157
+ * optional `{lang:?}` segment is also skipped for it (so `{ lang: defaultLocale }`
2158
+ * resolves to `/…` while every other locale keeps its `/{locale}/…` prefix).
2159
+ *
2135
2160
  * @param pattern - The route pattern.
2136
2161
  * @param params - Param values to substitute.
2162
+ * @param defaultLocale - The locale served bare (its `{lang:?}` segment is omitted).
2137
2163
  * @returns The resolved relative URL string.
2138
2164
  * @example
2139
2165
  * ```ts
2140
2166
  * buildUrl("/{slug}/", { slug: "hello" }); // "/hello/"
2167
+ * buildUrl("/{lang:?}/", { lang: "en" }, "en"); // "/"
2168
+ * buildUrl("/{lang:?}/", { lang: "ru" }, "en"); // "/ru/"
2141
2169
  * ```
2142
2170
  */
2143
- function buildUrl(pattern, params) {
2171
+ function buildUrl(pattern, params, defaultLocale) {
2144
2172
  const out = [];
2145
2173
  for (const segment of pattern.split("/")) {
2146
2174
  const placeholder = parsePlaceholder(segment);
@@ -2150,6 +2178,7 @@ function buildUrl(pattern, params) {
2150
2178
  }
2151
2179
  const value = params[placeholder.name] ?? "";
2152
2180
  if (placeholder.optional && value === "") continue;
2181
+ if (placeholder.name === "lang" && placeholder.optional && value === defaultLocale) continue;
2153
2182
  out.push(value);
2154
2183
  }
2155
2184
  return out.join("/");
@@ -2159,14 +2188,15 @@ function buildUrl(pattern, params) {
2159
2188
  *
2160
2189
  * @param pattern - The route pattern.
2161
2190
  * @param params - Param values to substitute.
2191
+ * @param defaultLocale - The locale served bare (forwarded to {@link buildUrl}).
2162
2192
  * @returns The output file path, e.g. `hello/index.html`.
2163
2193
  * @example
2164
2194
  * ```ts
2165
2195
  * buildFilePath("/{slug}/", { slug: "hello" });
2166
2196
  * ```
2167
2197
  */
2168
- function buildFilePath(pattern, params) {
2169
- const cleanPath = buildUrl(pattern, params).replace(/^\//, "").replace(/\/$/, "");
2198
+ function buildFilePath(pattern, params, defaultLocale) {
2199
+ const cleanPath = buildUrl(pattern, params, defaultLocale).replace(/^\//, "").replace(/\/$/, "");
2170
2200
  return cleanPath === "" ? "index.html" : `${cleanPath}/index.html`;
2171
2201
  }
2172
2202
  /**
@@ -2195,15 +2225,16 @@ function buildMatchers(pattern, locales) {
2195
2225
  * pattern.
2196
2226
  *
2197
2227
  * @param pattern - The route pattern bound into the closure.
2228
+ * @param defaultLocale - The locale served bare (bound into the closure).
2198
2229
  * @returns A function mapping params to the resolved relative URL.
2199
2230
  * @example
2200
2231
  * ```ts
2201
- * const toUrl = createToUrlFn("/{slug}/");
2232
+ * const toUrl = createToUrlFn("/{slug}/", "en");
2202
2233
  * toUrl({ slug: "x" }); // "/x/"
2203
2234
  * ```
2204
2235
  */
2205
- function createToUrlFunction(pattern) {
2206
- return (params) => buildUrl(pattern, params);
2236
+ function createToUrlFunction(pattern, defaultLocale) {
2237
+ return (params) => buildUrl(pattern, params, defaultLocale);
2207
2238
  }
2208
2239
  /**
2209
2240
  * Build the `toFile` closure for a route — resolves the output file path from
@@ -2212,15 +2243,16 @@ function createToUrlFunction(pattern) {
2212
2243
  *
2213
2244
  * @param pattern - The route pattern bound into the closure.
2214
2245
  * @param definition - The route definition carrying any `toFile` override.
2246
+ * @param defaultLocale - The locale served bare (bound into the closure).
2215
2247
  * @returns A function mapping params to the output file path.
2216
2248
  * @example
2217
2249
  * ```ts
2218
- * const toFile = createToFileFn("/{slug}/", definition);
2250
+ * const toFile = createToFileFn("/{slug}/", definition, "en");
2219
2251
  * toFile({ slug: "x" }); // "x/index.html"
2220
2252
  * ```
2221
2253
  */
2222
- function createToFileFunction(pattern, definition) {
2223
- return (params) => definition._handlers.toFile?.(params) ?? buildFilePath(pattern, params);
2254
+ function createToFileFunction(pattern, definition, defaultLocale) {
2255
+ return (params) => definition._handlers.toFile?.(params) ?? buildFilePath(pattern, params, defaultLocale);
2224
2256
  }
2225
2257
  /**
2226
2258
  * Compile a single route definition into its `CompiledRoute` entry.
@@ -2237,8 +2269,8 @@ function createToFileFunction(pattern, definition) {
2237
2269
  function compileRoute(name, definition, input) {
2238
2270
  const { pattern } = definition;
2239
2271
  const matchers = buildMatchers(pattern, input.locales);
2240
- const toUrl = createToUrlFunction(pattern);
2241
- const toFile = createToFileFunction(pattern, definition);
2272
+ const toUrl = createToUrlFunction(pattern, input.defaultLocale);
2273
+ const toFile = createToFileFunction(pattern, definition, input.defaultLocale);
2242
2274
  return {
2243
2275
  name,
2244
2276
  pattern,
@@ -2669,15 +2701,24 @@ function defineRoutes(routes) {
2669
2701
  * directly: no `app.router` reference, no manual "bind", no module global, no
2670
2702
  * "not bound" guard, and no createApp ↔ routes cycle.
2671
2703
  *
2704
+ * Pass `defaultLocale` so the builder serves that locale at BARE paths, matching the
2705
+ * runtime `router.toUrl` (which reads the default locale from the i18n plugin):
2706
+ * `toUrl("home", { lang: defaultLocale })` then resolves to `/` instead of
2707
+ * `/{defaultLocale}/`. It is app-free, so the default locale cannot be inferred — the
2708
+ * consumer supplies it (e.g. `createUrls(routes, "en")`). Omit it to keep every locale
2709
+ * prefixed.
2710
+ *
2672
2711
  * @param routes - The route map (typically the value returned by {@link defineRoutes}).
2712
+ * @param defaultLocale - The locale to serve bare (its `{lang:?}` prefix is omitted).
2673
2713
  * @returns A {@link Urls} builder whose `toUrl` accepts only this map's route names.
2674
2714
  * @example
2675
2715
  * ```ts
2676
- * const url = createUrls(routes);
2677
- * url.toUrl("article", { lang: "en", slug: "hello" }); // "/en/hello/"
2716
+ * const url = createUrls(routes, "en");
2717
+ * url.toUrl("article", { lang: "en", slug: "hello" }); // "/hello/"
2718
+ * url.toUrl("article", { lang: "ru", slug: "hello" }); // "/ru/hello/"
2678
2719
  * ```
2679
2720
  */
2680
- function createUrls(routes) {
2721
+ function createUrls(routes, defaultLocale) {
2681
2722
  return {
2682
2723
  /**
2683
2724
  * Build a route's URL path from its name and params.
@@ -2688,13 +2729,13 @@ function createUrls(routes) {
2688
2729
  * @throws {Error} If `name` is not present in the route map.
2689
2730
  * @example
2690
2731
  * ```ts
2691
- * url.toUrl("home", { lang: "en" }); // "/en/"
2732
+ * url.toUrl("home", { lang: "ru" }); // "/ru/"
2692
2733
  * ```
2693
2734
  */
2694
2735
  toUrl(name, params = {}) {
2695
2736
  const definition = routes[name];
2696
2737
  if (!definition) throw new Error(`[web] router: unknown route name "${String(name)}".\n Check the name matches a key in the route map passed to createUrls.`);
2697
- return buildUrl(definition.pattern, params);
2738
+ return buildUrl(definition.pattern, params, defaultLocale);
2698
2739
  } };
2699
2740
  }
2700
2741
  //#endregion
@@ -5104,7 +5145,24 @@ function renderBodyCached(ctx, instance, routeContext, data, reuse) {
5104
5145
  * ```
5105
5146
  */
5106
5147
  async function writeDocument(outDir, entry, params, html) {
5107
- const filePath = path.join(outDir, entry.toFile(params));
5148
+ await writeDocumentAt(outDir, entry.toFile(params), html);
5149
+ }
5150
+ /**
5151
+ * Write an HTML document to an explicit relative path under `outDir`, creating parent
5152
+ * directories first. Backs both the canonical page path ({@link writeDocument}) and the
5153
+ * default-locale `/{defaultLocale}/…` alias copy ({@link renderInstance}).
5154
+ *
5155
+ * @param outDir - The build output directory.
5156
+ * @param relativeFile - The output file path relative to `outDir` (e.g. `en/index.html`).
5157
+ * @param html - The complete HTML document to write.
5158
+ * @returns A promise resolved once the file is written.
5159
+ * @example
5160
+ * ```ts
5161
+ * await writeDocumentAt("dist", "en/about/index.html", "<!DOCTYPE html>…");
5162
+ * ```
5163
+ */
5164
+ async function writeDocumentAt(outDir, relativeFile, html) {
5165
+ const filePath = path.join(outDir, relativeFile);
5108
5166
  await mkdir(path.dirname(filePath), { recursive: true });
5109
5167
  await writeFile(filePath, html, "utf8");
5110
5168
  }
@@ -5114,14 +5172,19 @@ async function writeDocument(outDir, entry, params, html) {
5114
5172
  * `<head>`/body → assemble the document (template fill or in-code shell) → write.
5115
5173
  * Uses the configured shell `template` when supplied, otherwise the in-code shell.
5116
5174
  *
5175
+ * The default locale is served at BARE paths, so each default-locale page on a
5176
+ * `{lang:?}` route is ALSO written to `/{defaultLocale}/…` — the SAME rendered HTML (its
5177
+ * canonical already points at the bare URL) — so an explicit `/{defaultLocale}/…` link
5178
+ * serves the page directly with no redirect. Both pages are returned so each gets a sidecar.
5179
+ *
5117
5180
  * @param ctx - Plugin context (provides `require`, `state`, `config`, `has`).
5118
5181
  * @param instance - The concrete page instance to render.
5119
- * @param shell - Per-build wiring shared across instances (asset tags + template).
5182
+ * @param shell - Per-build wiring shared across instances (asset tags + template + default locale).
5120
5183
  * @param reuse - Whether this run may reuse a cached body (incremental, no code change).
5121
- * @returns The instance's URL, rendered HTML, loaded data, and client-nav flag.
5184
+ * @returns The rendered page(s): the canonical page, plus the `/{defaultLocale}/` alias when emitted.
5122
5185
  * @example
5123
5186
  * ```ts
5124
- * await renderInstance(ctx, instance, { assets: "", template: null }, false);
5187
+ * await renderInstance(ctx, instance, shell, false);
5125
5188
  * ```
5126
5189
  */
5127
5190
  async function renderInstance(ctx, instance, shell, reuse) {
@@ -5143,20 +5206,31 @@ async function renderInstance(ctx, instance, shell, reuse) {
5143
5206
  };
5144
5207
  const html = shell.template === null ? renderDocument(parts) : fillTemplate(shell.template, parts);
5145
5208
  await writeDocument(ctx.config.outDir, entry, params, html);
5146
- return {
5209
+ const clientNavigable = definition._handlers.render !== void 0;
5210
+ const pages = [{
5147
5211
  url,
5148
5212
  html,
5149
5213
  data,
5150
- clientNavigable: definition._handlers.render !== void 0
5151
- };
5214
+ clientNavigable
5215
+ }];
5216
+ if (locale === shell.defaultLocale && entry.pattern.includes("{lang:?}")) {
5217
+ await writeDocumentAt(ctx.config.outDir, `${shell.defaultLocale}/${entry.toFile(params)}`, html);
5218
+ pages.push({
5219
+ url: `/${shell.defaultLocale}${url}`,
5220
+ html,
5221
+ data,
5222
+ clientNavigable
5223
+ });
5224
+ }
5225
+ return pages;
5152
5226
  }
5153
5227
  /**
5154
5228
  * Prepare the per-build {@link RenderShell} ONCE (O(1) per page): read the optional
5155
5229
  * shell `template` from disk when configured + present, and precompute the injected
5156
5230
  * asset tags. `template` is `null` when unset/missing (use the in-code shell).
5157
5231
  *
5158
- * @param ctx - Plugin context (provides `config`, `state`).
5159
- * @returns The shared shell wiring (asset tags + template-or-null) for every page.
5232
+ * @param ctx - Plugin context (provides `config`, `state`, `require`).
5233
+ * @returns The shared shell wiring (asset tags + template-or-null + default locale) for every page.
5160
5234
  * @example
5161
5235
  * ```ts
5162
5236
  * const shell = await prepareShell(ctx);
@@ -5167,7 +5241,8 @@ async function prepareShell(ctx) {
5167
5241
  const template = typeof templatePath === "string" && existsSync(templatePath) ? await readFile(templatePath, "utf8") : null;
5168
5242
  return {
5169
5243
  assets: buildAssetTags(ctx),
5170
- template
5244
+ template,
5245
+ defaultLocale: ctx.require(i18nPlugin).defaultLocale()
5171
5246
  };
5172
5247
  }
5173
5248
  /**
@@ -5301,7 +5376,7 @@ async function renderPages(ctx, options) {
5301
5376
  const byPattern = makeEntryMap(router);
5302
5377
  if (!reuse) ctx.state.renderCache.clear();
5303
5378
  const shell = await prepareShell(ctx);
5304
- const rendered = await renderInBatches(await expandAllInstances(manifest, locales, byPattern, ctx), reuse ? INCREMENTAL_BATCH_SIZE : RENDER_BATCH_SIZE, (instance) => renderInstance(ctx, instance, shell, reuse));
5379
+ const rendered = (await renderInBatches(await expandAllInstances(manifest, locales, byPattern, ctx), reuse ? INCREMENTAL_BATCH_SIZE : RENDER_BATCH_SIZE, (instance) => renderInstance(ctx, instance, shell, reuse))).flat();
5305
5380
  await writeDataSidecars(ctx, rendered, router.mode());
5306
5381
  ctx.log.debug("build:pages", { count: rendered.length });
5307
5382
  return {
package/package.json CHANGED
@@ -113,5 +113,5 @@
113
113
  "test:cli-e2e": "bun test src/plugins/cli/__tests__/e2e/",
114
114
  "test:coverage": "vitest run --project unit --project integration --coverage"
115
115
  },
116
- "version": "1.5.3"
116
+ "version": "1.6.0"
117
117
  }