@timber-js/app 0.2.0-alpha.182 → 0.2.0-alpha.184

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/_chunks/{build-output-helper-DF5uScqS.js → build-output-helper-DOGYFb_X.js} +47 -9
  2. package/dist/_chunks/build-output-helper-DOGYFb_X.js.map +1 -0
  3. package/dist/_chunks/{cloudflare-AHoWYTYr.js → cloudflare-CnT5Lr7U.js} +21 -3
  4. package/dist/_chunks/{cloudflare-AHoWYTYr.js.map → cloudflare-CnT5Lr7U.js.map} +1 -1
  5. package/dist/_chunks/plugin-context---kTF5v8.js.map +1 -1
  6. package/dist/adapters/build-output-helper.d.ts +6 -0
  7. package/dist/adapters/build-output-helper.d.ts.map +1 -1
  8. package/dist/adapters/cloudflare-dev.js +1 -1
  9. package/dist/adapters/cloudflare-kv-cache.js +1 -1
  10. package/dist/adapters/cloudflare.d.ts +10 -0
  11. package/dist/adapters/cloudflare.d.ts.map +1 -1
  12. package/dist/adapters/cloudflare.js +2 -2
  13. package/dist/adapters/nitro.d.ts +14 -0
  14. package/dist/adapters/nitro.d.ts.map +1 -1
  15. package/dist/adapters/nitro.js +14 -1
  16. package/dist/adapters/nitro.js.map +1 -1
  17. package/dist/adapters/shared.d.ts +1 -1
  18. package/dist/adapters/shared.d.ts.map +1 -1
  19. package/dist/adapters/types.d.ts +14 -0
  20. package/dist/adapters/types.d.ts.map +1 -1
  21. package/dist/client/internal.js +113 -11
  22. package/dist/client/internal.js.map +1 -1
  23. package/dist/client/rsc-fetch.d.ts +9 -0
  24. package/dist/client/rsc-fetch.d.ts.map +1 -1
  25. package/dist/index.js +437 -200
  26. package/dist/index.js.map +1 -1
  27. package/dist/plugin-context.d.ts +12 -0
  28. package/dist/plugin-context.d.ts.map +1 -1
  29. package/dist/plugins/adapter-build.d.ts.map +1 -1
  30. package/dist/plugins/build-report.d.ts +4 -3
  31. package/dist/plugins/build-report.d.ts.map +1 -1
  32. package/dist/plugins/static-build.d.ts +55 -2
  33. package/dist/plugins/static-build.d.ts.map +1 -1
  34. package/dist/server/rsc-entry/index.d.ts +12 -0
  35. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  36. package/dist/server/sitemap-generator.d.ts +1 -1
  37. package/dist/server/sitemap-generator.d.ts.map +1 -1
  38. package/dist/server/static-generator.d.ts +106 -0
  39. package/dist/server/static-generator.d.ts.map +1 -0
  40. package/docs/learn/13-configuration.mdx +5 -1
  41. package/package.json +1 -1
  42. package/src/adapters/build-output-helper.ts +40 -1
  43. package/src/adapters/cloudflare.ts +43 -1
  44. package/src/adapters/nitro.ts +58 -2
  45. package/src/adapters/shared.ts +42 -11
  46. package/src/adapters/types.ts +14 -0
  47. package/src/client/browser-entry/index.ts +7 -1
  48. package/src/client/rsc-fetch.ts +187 -19
  49. package/src/index.ts +1 -1
  50. package/src/plugin-context.ts +12 -0
  51. package/src/plugins/adapter-build.ts +5 -0
  52. package/src/plugins/build-report.ts +6 -12
  53. package/src/plugins/static-build.ts +391 -2
  54. package/src/server/rsc-entry/index.ts +24 -0
  55. package/src/server/sitemap-generator.ts +1 -1
  56. package/src/server/static-generator.ts +692 -0
  57. package/dist/_chunks/build-output-helper-DF5uScqS.js.map +0 -1
@@ -131,6 +131,18 @@ export interface PluginContext {
131
131
  * ranges and rewrite CODE via MagicString; the AST itself is never mutated.
132
132
  */
133
133
  parseCached?: ParseMemo;
134
+ /**
135
+ * Content-Type mappings for extensionless static API route outputs.
136
+ * Populated during static generation, consumed by adapter-build to
137
+ * write per-path Content-Type rules in _headers. See TIM-1241.
138
+ */
139
+ staticContentTypes?: Record<string, string>;
140
+ /**
141
+ * RSC flight payload manifest mapping unhashed → hashed URLs.
142
+ * Populated during static generation, consumed by adapters to set
143
+ * immutable cache headers for `_rsc/` files. See TIM-1254.
144
+ */
145
+ rscManifest?: Record<string, string>;
134
146
  }
135
147
  /**
136
148
  * Content-keyed LRU memo for `parseAst`. Transforms for the same module run
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-context.d.ts","sourceRoot":"","sources":["../src/plugin-context.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plugin-context.d.ts","sourceRoot":"","sources":["../src/plugin-context.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"adapter-build.d.ts","sourceRoot":"","sources":["../../src/plugins/adapter-build.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAInC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAGjE,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAwE7D;AAgHD;;;;;;;GAOG;AACH;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,aAAa,EAC5B,kBAAkB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,GAC5C,OAAO,CAAC,IAAI,CAAC,CAmDf;AAED,wBAAsB,6BAA6B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA+CnF"}
1
+ {"version":3,"file":"adapter-build.d.ts","sourceRoot":"","sources":["../../src/plugins/adapter-build.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAInC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAGjE,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CA6E7D;AAgHD;;;;;;;GAOG;AACH;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,aAAa,EAC5B,kBAAkB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,GAC5C,OAAO,CAAC,IAAI,CAAC,CAmDf;AAED,wBAAsB,6BAA6B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA+CnF"}
@@ -30,9 +30,10 @@ export interface RouteEntry {
30
30
  /**
31
31
  * Classify a route by its segment chain and output mode.
32
32
  *
33
- * In server mode (default), all pages are dynamic (rendered per-request).
34
- * In static mode, only pages with dynamic/catch-all segments are dynamic.
35
- * API routes (route.ts) are always classified as function.
33
+ * In server mode (default), all pages are dynamic (rendered per-request)
34
+ * and API routes (route.ts) are classified as function.
35
+ * In static mode, everything is pre-rendered at build time — all routes
36
+ * are static regardless of dynamic segments or route.ts presence.
36
37
  */
37
38
  export declare function classifyRoute(segments: SegmentNode[], outputMode?: 'server' | 'static'): RouteType;
38
39
  export { formatSize };
@@ -1 +1 @@
1
- {"version":3,"file":"build-report.d.ts","sourceRoot":"","sources":["../../src/plugins/build-report.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"build-report.d.ts","sourceRoot":"","sources":["../../src/plugins/build-report.ts"],"names":[],"mappings":"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"}
@@ -3,12 +3,13 @@
3
3
  *
4
4
  * When `output: 'static'` is set in timber.config.ts, this plugin:
5
5
  * 1. Validates that no dynamic APIs (getCookies(), getHeaders()) are used
6
- * 2. Coordinates build-time rendering of all pages
6
+ * 2. After the build, renders all routes to static HTML + RSC flight files
7
7
  *
8
- * Design doc: design/15-future-prerendering.md
8
+ * Design doc: design/11-platform.md §"static output mode"
9
9
  */
10
10
  import type { Plugin } from 'vite';
11
11
  import type { PluginContext } from '../plugin-context.js';
12
+ import type { StaticPageEntry, StaticRedirectEntry } from '../server/static-generator.js';
12
13
  export interface StaticValidationError {
13
14
  type: 'dynamic-api';
14
15
  file: string;
@@ -22,6 +23,57 @@ export interface StaticValidationError {
22
23
  * Returns an array of validation errors.
23
24
  */
24
25
  export declare function detectDynamicApis(code: string, fileId: string): StaticValidationError[];
26
+ /**
27
+ * Generate a `_redirects` file from collected redirect entries.
28
+ * Uses the Cloudflare Pages / Netlify format: `/from /to statusCode`
29
+ *
30
+ * @internal Exported for testing.
31
+ */
32
+ export declare function generateRedirectsFile(redirects: StaticRedirectEntry[]): string;
33
+ /**
34
+ * Determine the output file path for a static page entry.
35
+ *
36
+ * Pages: /about → about/index.html, / → index.html
37
+ * RSC: /about → _rsc/about.rsc, / → _rsc/index.rsc
38
+ * API: /feed.xml → feed.xml, /api/data → api/data
39
+ */
40
+ export declare function staticOutputPath(entry: StaticPageEntry): string;
41
+ /**
42
+ * Compute a short content hash for immutable caching.
43
+ * Uses 8 chars of base64url-encoded SHA-256, matching Vite's asset hash style.
44
+ */
45
+ export declare function contentHash(data: Buffer | Uint8Array): string;
46
+ /**
47
+ * Hash all RSC flight payloads and params sidecars in `_rsc/`, rename
48
+ * them with content hashes, and inject a manifest into each HTML file.
49
+ *
50
+ * Before: `_rsc/index.rsc`, `_rsc/about.rsc`
51
+ * After: `_rsc/index-B7YxEKdN.rsc`, `_rsc/about-C8ZzFLfO.rsc`
52
+ *
53
+ * The manifest maps the original `_rsc/` URL to the hashed filename:
54
+ * `{"/_rsc/index.rsc":"/_rsc/index-B7YxEKdN.rsc", ...}`
55
+ *
56
+ * Injected into HTML as:
57
+ * `<script>window.__TIMBER_RSC_MANIFEST__={...}</script>`
58
+ *
59
+ * @internal Exported for testing.
60
+ */
61
+ export declare function hashRscPayloads(staticDir: string): Promise<Record<string, string>>;
62
+ /**
63
+ * Inject the RSC manifest into all HTML files as a `<script>` tag
64
+ * before `</head>`. This lets the client resolve hashed RSC URLs
65
+ * without an extra network request.
66
+ *
67
+ * @internal Exported for testing.
68
+ */
69
+ export declare function injectRscManifestIntoHtml(staticDir: string, manifest: Record<string, string>): Promise<void>;
70
+ /**
71
+ * Read a user-authored `_redirects` file from the project's `public/`
72
+ * directory. Returns `null` if no file exists.
73
+ *
74
+ * @internal Exported for testing.
75
+ */
76
+ export declare function readUserRedirects(root: string): Promise<string | null>;
25
77
  /**
26
78
  * Create the timber-static-build Vite plugin.
27
79
  *
@@ -29,6 +81,7 @@ export declare function detectDynamicApis(code: string, fileId: string): StaticV
29
81
  *
30
82
  * Hooks:
31
83
  * - transform: Validates source files for static mode violations
84
+ * - buildApp (post): Renders all routes to static HTML after the build
32
85
  */
33
86
  export declare function timberStaticBuild(ctx: PluginContext): Plugin;
34
87
  //# sourceMappingURL=static-build.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"static-build.d.ts","sourceRoot":"","sources":["../../src/plugins/static-build.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAM1D,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,aAAa,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAmBD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,qBAAqB,EAAE,CA2BvF;AAMD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CA2C5D"}
1
+ {"version":3,"file":"static-build.d.ts","sourceRoot":"","sources":["../../src/plugins/static-build.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAMnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAGV,eAAe,EACf,mBAAmB,EACpB,MAAM,+BAA+B,CAAC;AAQvC,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,aAAa,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAmBD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,qBAAqB,EAAE,CA2BvF;AAMD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,mBAAmB,EAAE,GAAG,MAAM,CAG9E;AAaD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAsB/D;AAkLD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAE7D;AAwBD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAwBxF;AAED;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAC7C,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,OAAO,CAAC,IAAI,CAAC,CAkBf;AAMD;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAU5E;AAMD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CA8D5D"}
@@ -24,6 +24,18 @@ export { runWithWorkersCachePurge } from '../workers-cache-bridge.js';
24
24
  * See design/44-render-at-build-time.md §Build Phase.
25
25
  */
26
26
  export declare function __timber_capturePrebuilt(options: import('../prebuilt-builder.js').CaptureOptions): Promise<import('../prebuilt-builder.js').CaptureSummary>;
27
+ /**
28
+ * Post-build static site generation entry point (TIM-1238). Renders every
29
+ * route in the manifest to HTML + RSC flight payloads. Must run inside the
30
+ * built RSC bundle — the handler, route manifest, and page module loaders
31
+ * are module instances in THIS bundle's graph.
32
+ *
33
+ * Unlike capture mode, this needs the full pipeline handler, so it must
34
+ * NOT run when __TIMBER_CAPTURE_MODE__ is set.
35
+ *
36
+ * See design/11-platform.md §"static output mode".
37
+ */
38
+ export declare function __timber_generateStaticSite(options: import('../static-generator.js').StaticGenerationOptions): Promise<import('../static-generator.js').StaticGenerationSummary>;
27
39
  /**
28
40
  * The pipeline configuration object. Exported so the dev server can set
29
41
  * `devPipelineErrorHandler` after import without a module-level singleton.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/index.ts"],"names":[],"mappings":"AA+BA,OAAO,uCAAuC,CAAC;AAqB/C,OAAO,KAAK,EAAuB,cAAc,EAAc,MAAM,gBAAgB,CAAC;AAmCtF;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI,CAE5E;AAkVD,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAInE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAK1D,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAEtE;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,OAAO,wBAAwB,EAAE,cAAc,GACvD,OAAO,CAAC,OAAO,wBAAwB,EAAE,cAAc,CAAC,CAE1D;AAyCD;;;GAGG;AACH,eAAO,MAAM,cAAc,gBAAmB,CAAC;8BAb1B,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/index.ts"],"names":[],"mappings":"AA+BA,OAAO,uCAAuC,CAAC;AAqB/C,OAAO,KAAK,EAAuB,cAAc,EAAc,MAAM,gBAAgB,CAAC;AAoCtF;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI,CAE5E;AAkVD,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAInE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAK1D,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAEtE;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,OAAO,wBAAwB,EAAE,cAAc,GACvD,OAAO,CAAC,OAAO,wBAAwB,EAAE,cAAc,CAAC,CAE1D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,OAAO,wBAAwB,EAAE,uBAAuB,GAChE,OAAO,CAAC,OAAO,wBAAwB,EAAE,uBAAuB,CAAC,CAQnE;AAyCD;;;GAGG;AACH,eAAO,MAAM,cAAc,gBAAmB,CAAC;8BAb1B,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC"}
@@ -125,5 +125,5 @@ export declare function generateSitemap(root: SegmentNode, config: SitemapConfig
125
125
  * When a user-authored sitemap exists at the root, auto-generation is disabled
126
126
  * to avoid conflicts — the user takes full control.
127
127
  */
128
- export declare function hasUserSitemap(root: SegmentNode): boolean;
128
+ export declare function hasUserSitemap(root: SegmentNode<unknown>): boolean;
129
129
  //# sourceMappingURL=sitemap-generator.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sitemap-generator.d.ts","sourceRoot":"","sources":["../../src/server/sitemap-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAIvD,kEAAkE;AAClE,MAAM,WAAW,aAAa;IAC5B,yDAAyD;IACzD,OAAO,EAAE,OAAO,CAAC;IACjB,uFAAuF;IACvF,OAAO,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,4DAA4D;IAC5D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,6DAA6D;IAC7D,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,sDAAsD;AACtD,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,qFAAqF;IACrF,SAAS,EAAE,OAAO,CAAC;IACnB,wDAAwD;IACxD,UAAU,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CACjE;AAED;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE;IAClC,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;CAClB,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEvC,oCAAoC;AACpC,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC;AAIpE,8DAA8D;AAC9D,eAAO,MAAM,gBAAgB,QAAS,CAAC;AAIvC;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,GAAG,YAAY,EAAE,CAI7F;AAwDD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE3D;AAID;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,YAAY,EAAE,EACtB,MAAM,EAAE,aAAa,EACrB,UAAU,CAAC,EAAE,YAAY,GACxB,OAAO,CAAC,YAAY,EAAE,CAAC,CAqDzB;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,GACxC,MAAM,GAAG,IAAI,CA2Cf;AAID;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,aAAa,EACrB,UAAU,CAAC,EAAE,YAAY,GACxB,OAAO,CAAC,aAAa,CAAC,CA+BxB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAGzD"}
1
+ {"version":3,"file":"sitemap-generator.d.ts","sourceRoot":"","sources":["../../src/server/sitemap-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAIvD,kEAAkE;AAClE,MAAM,WAAW,aAAa;IAC5B,yDAAyD;IACzD,OAAO,EAAE,OAAO,CAAC;IACjB,uFAAuF;IACvF,OAAO,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,4DAA4D;IAC5D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,6DAA6D;IAC7D,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,sDAAsD;AACtD,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,qFAAqF;IACrF,SAAS,EAAE,OAAO,CAAC;IACnB,wDAAwD;IACxD,UAAU,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CACjE;AAED;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE;IAClC,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;CAClB,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEvC,oCAAoC;AACpC,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC;AAIpE,8DAA8D;AAC9D,eAAO,MAAM,gBAAgB,QAAS,CAAC;AAIvC;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,GAAG,YAAY,EAAE,CAI7F;AAwDD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE3D;AAID;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,YAAY,EAAE,EACtB,MAAM,EAAE,aAAa,EACrB,UAAU,CAAC,EAAE,YAAY,GACxB,OAAO,CAAC,YAAY,EAAE,CAAC,CAqDzB;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,GACxC,MAAM,GAAG,IAAI,CA2Cf;AAID;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,aAAa,EACrB,UAAU,CAAC,EAAE,YAAY,GACxB,OAAO,CAAC,aAAa,CAAC,CA+BxB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,OAAO,CAGlE"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Static site generator — enumerates routes and renders them to
3
+ * HTML + RSC flight payloads for `output: 'static'` mode.
4
+ *
5
+ * Runs INSIDE the built RSC bundle (same pattern as prebuilt-builder.ts)
6
+ * so it has access to the route manifest, page module loaders, and the
7
+ * request pipeline handler.
8
+ *
9
+ * For each page route:
10
+ * - Renders HTML (Accept: text/html) for initial load
11
+ * - Renders RSC flight (Accept: text/x-component) for client SPA nav
12
+ *
13
+ * For each API route (route.ts):
14
+ * - Calls the GET handler and captures the response
15
+ *
16
+ * The caller (plugins/static-build.ts) handles file I/O — keeping
17
+ * node:fs out of the server bundle so it runs on Cloudflare Workers.
18
+ *
19
+ * Design docs: design/11-platform.md §"static output mode"
20
+ */
21
+ import type { ManifestRoot, ManifestSegmentNode } from './route-matcher.js';
22
+ /** A single rendered static page ready for the caller to write to disk. */
23
+ export interface StaticPageEntry {
24
+ /** The URL path (e.g., "/about", "/blog/first-post"). */
25
+ urlPath: string;
26
+ /** HTTP status code from the response. */
27
+ status: number;
28
+ /** Response headers. */
29
+ headers: Record<string, string>;
30
+ /** The response body as bytes. */
31
+ body: Uint8Array;
32
+ /** Content type for choosing file extension. */
33
+ contentType: string;
34
+ /**
35
+ * Whether this is an RSC flight payload (for client navigation)
36
+ * vs an HTML page (for initial load).
37
+ */
38
+ kind: 'html' | 'rsc' | 'api';
39
+ }
40
+ /** An error encountered during static generation. */
41
+ export interface StaticGenerationError {
42
+ urlPath: string;
43
+ message: string;
44
+ status?: number;
45
+ }
46
+ /** A redirect discovered during static generation. */
47
+ export interface StaticRedirectEntry {
48
+ /** The source URL path (e.g., "/old-page"). */
49
+ from: string;
50
+ /** The redirect target (Location header value). */
51
+ to: string;
52
+ /** HTTP status code (301, 302, 307, 308). */
53
+ status: number;
54
+ }
55
+ /** Result of the static generation pass. */
56
+ export interface StaticGenerationSummary {
57
+ /** Total pages rendered (HTML + RSC pairs count as 2). */
58
+ rendered: number;
59
+ /** Pages that failed to render. */
60
+ errors: StaticGenerationError[];
61
+ /** Redirects from middleware/proxy that need platform redirect rules. */
62
+ redirects: StaticRedirectEntry[];
63
+ }
64
+ export interface StaticGenerationOptions {
65
+ /** Called for each rendered page/asset. File I/O is the caller's job. */
66
+ onEntry: (entry: StaticPageEntry) => Promise<void>;
67
+ /** Progress/warning logger. */
68
+ log?: (message: string) => void;
69
+ /** Timeout per page render in milliseconds. Default: 30000. */
70
+ renderTimeoutMs?: number;
71
+ /**
72
+ * When true and no user-authored sitemap exists, the generator will
73
+ * render `/sitemap.xml` (and any paginated sub-sitemaps) through the
74
+ * handler to capture auto-generated sitemaps in the static output.
75
+ */
76
+ autoSitemapEnabled?: boolean;
77
+ }
78
+ interface StaticUrl {
79
+ urlPath: string;
80
+ kind: 'page' | 'api';
81
+ }
82
+ /**
83
+ * Walk the manifest tree and collect all concrete URLs to render.
84
+ *
85
+ * Static routes produce their URL directly. Dynamic routes call
86
+ * `generateStaticSegmentParams()` from the page module. Dynamic routes
87
+ * without the export are a build error in static mode.
88
+ */
89
+ export declare function enumerateStaticUrls(root: ManifestSegmentNode, log: (message: string) => void, timeoutMs?: number): Promise<{
90
+ urls: StaticUrl[];
91
+ errors: StaticGenerationError[];
92
+ }>;
93
+ /**
94
+ * Generate all static pages from the route manifest.
95
+ *
96
+ * For each page: renders HTML (for initial load) and RSC flight (for SPA nav).
97
+ * For each API route: calls the GET handler.
98
+ *
99
+ * Must run inside the built RSC bundle — the handler and manifest are
100
+ * module instances from this bundle's graph.
101
+ */
102
+ export declare function generateStaticSite(manifest: ManifestRoot, handler: (req: Request) => Promise<Response>, options: StaticGenerationOptions): Promise<StaticGenerationSummary>;
103
+ /** Extract /sitemap/N.xml URLs from a sitemap index XML. */
104
+ export declare function discoverSubSitemapUrls(xml: string): string[];
105
+ export {};
106
+ //# sourceMappingURL=static-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"static-generator.d.ts","sourceRoot":"","sources":["../../src/server/static-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAgB,MAAM,oBAAoB,CAAC;AAO1F,2EAA2E;AAC3E,MAAM,WAAW,eAAe;IAC9B,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,wBAAwB;IACxB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,kCAAkC;IAClC,IAAI,EAAE,UAAU,CAAC;IACjB,gDAAgD;IAChD,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,CAAC;CAC9B;AAED,qDAAqD;AACrD,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,sDAAsD;AACtD,MAAM,WAAW,mBAAmB;IAClC,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,EAAE,EAAE,MAAM,CAAC;IACX,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,4CAA4C;AAC5C,MAAM,WAAW,uBAAuB;IACtC,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,MAAM,EAAE,qBAAqB,EAAE,CAAC;IAChC,yEAAyE;IACzE,SAAS,EAAE,mBAAmB,EAAE,CAAC;CAClC;AAED,MAAM,WAAW,uBAAuB;IACtC,yEAAyE;IACzE,OAAO,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,+BAA+B;IAC/B,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,+DAA+D;IAC/D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAID,UAAU,SAAS;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC;CACtB;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,mBAAmB,EACzB,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EAC9B,SAAS,GAAE,MAAkC,GAC5C,OAAO,CAAC;IAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IAAC,MAAM,EAAE,qBAAqB,EAAE,CAAA;CAAE,CAAC,CA2GjE;AA6LD;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,EAC5C,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,uBAAuB,CAAC,CAwNlC;AAED,4DAA4D;AAC5D,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAe5D"}
@@ -53,7 +53,11 @@ The `output` field shifts work between two phases:
53
53
  | `'server'` | Request time | Request time | Request time |
54
54
  | `'static'` | **Build time only** | **Build time** (once, to HTML) | **Request time**, split-deployed as endpoints |
55
55
 
56
- This is why `cookies()` and `headers()` raise warnings during a static build — there's no request at the moment components render.
56
+ This is why `cookies()` and `headers()` are build errors in static mode — there's no request at the moment components render.
57
+
58
+ Dynamic routes (`[param]`) must export `generateStaticSegmentParams` when using `output: 'static'` — the build needs to know which URLs to render. API routes (`route.ts`) are also pre-rendered at build time.
59
+
60
+ For each page, the build generates both HTML (for initial loads) and RSC flight data (for client-side SPA navigation). No server is needed at runtime.
57
61
 
58
62
  ## Adapters
59
63
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timber-js/app",
3
- "version": "0.2.0-alpha.182",
3
+ "version": "0.2.0-alpha.184",
4
4
  "description": "Vite-native React framework built for Servers and Serverless Platforms — correct HTTP semantics, real status codes, pages that work without JavaScript",
5
5
  "keywords": [
6
6
  "cloudflare-workers",
@@ -11,7 +11,7 @@
11
11
  // IMPORTANT: This module must remain a leaf — no imports from ../server/,
12
12
  // ../client/, or Vite-dependent code. See adapters/shared.ts header.
13
13
 
14
- import { writeFile, mkdir, cp, readdir, readFile } from 'node:fs/promises';
14
+ import { writeFile, mkdir, cp, rm, readdir, readFile } from 'node:fs/promises';
15
15
  import { join, basename, relative } from 'node:path';
16
16
  import { generateHeadersFile } from './shared.js';
17
17
  import type { TimberConfig } from './types';
@@ -141,6 +141,7 @@ export async function findUnhashedStaticFiles(publicDir: string): Promise<string
141
141
  for (const file of files) {
142
142
  const rel = relative(publicDir, file).replace(/\\/g, '/');
143
143
  if (rel.startsWith('assets/') || rel.startsWith('_timber/')) continue;
144
+ if (rel.startsWith('rsc/') || rel.startsWith('ssr/') || rel.startsWith('_rsc/')) continue;
144
145
  if (basename(rel).startsWith('_')) continue;
145
146
  const urlPath = '/' + rel;
146
147
  if (PRIORITY_FILES.has(urlPath)) {
@@ -184,6 +185,44 @@ async function findReferencedWasm(clientDir: string): Promise<Set<string>> {
184
185
  return referenced;
185
186
  }
186
187
 
188
+ /**
189
+ * Build a static-only output directory (no server entry). Copies client
190
+ * assets and pre-rendered HTML/RSC/API files into `outDir/publicDirName`.
191
+ * Used by both Cloudflare and Nitro adapters in `output: 'static'` mode.
192
+ */
193
+ export async function buildStaticDirectoryOutput(
194
+ config: TimberConfig,
195
+ buildDir: string,
196
+ outDir: string,
197
+ publicDirName: string
198
+ ): Promise<void> {
199
+ // Clean stale artifacts from prior builds (e.g. server-mode builds that
200
+ // left rsc/, ssr/, _worker.js in the outDir). Without this, a static
201
+ // build after a server build inherits 140+ unused server chunks.
202
+ await rm(outDir, { recursive: true, force: true }).catch(() => {});
203
+ await mkdir(outDir, { recursive: true });
204
+
205
+ const publicDir = join(outDir, publicDirName);
206
+ await mkdir(publicDir, { recursive: true });
207
+
208
+ // Copy client assets (JS, CSS, fonts, images).
209
+ const clientDir = join(buildDir, 'client');
210
+ await cp(clientDir, publicDir, {
211
+ recursive: true,
212
+ filter: (src: string) => {
213
+ if (config.clientJavascriptDisabled && src.endsWith('.js')) return false;
214
+ return true;
215
+ },
216
+ }).catch((e: NodeJS.ErrnoException) => {
217
+ if (e.code !== 'ENOENT') throw e;
218
+ });
219
+
220
+ // Copy pre-rendered static pages (HTML + RSC flight + API routes).
221
+ if (config.staticPagesDir) {
222
+ await cp(config.staticPagesDir, publicDir, { recursive: true });
223
+ }
224
+ }
225
+
187
226
  async function collectFiles(dir: string): Promise<string[]> {
188
227
  const result: string[] = [];
189
228
  const entries = await readdir(dir, { withFileTypes: true });
@@ -9,7 +9,12 @@ import { join, relative } from 'node:path';
9
9
  import { parse as parseJsonc } from 'jsonc-parser';
10
10
  import { AsyncLocalStorage } from 'node:async_hooks';
11
11
  import type { TimberPlatformAdapter, TimberConfig } from './types';
12
- import { runSharedBuildSteps } from './build-output-helper.js';
12
+ import {
13
+ runSharedBuildSteps,
14
+ findUnhashedStaticFiles,
15
+ buildStaticDirectoryOutput,
16
+ } from './build-output-helper.js';
17
+ import { generateHeadersFile } from './shared.js';
13
18
 
14
19
  // ─── Bindings passthrough ─────────────────────────────────────────────────
15
20
  // ALS stores the env object per-request so server components and middleware
@@ -235,6 +240,14 @@ export function cloudflare(options: CloudflareAdapterOptions = {}): TimberPlatfo
235
240
  async buildOutput(config: TimberConfig, buildDir: string) {
236
241
  const outDir = join(buildDir, 'cloudflare');
237
242
 
243
+ // ── Static mode: Cloudflare Pages-compatible output ──────────────
244
+ // No Workers entry — just HTML files + client assets in a flat
245
+ // directory that Cloudflare Pages can serve directly.
246
+ if (config.output === 'static' && config.staticPagesDir) {
247
+ await buildStaticOutput(config, buildDir, outDir, options);
248
+ return;
249
+ }
250
+
238
251
  // Clean stale prebuilt artifacts from prior builds.
239
252
  await rm(join(outDir, 'prebuilt'), { recursive: true, force: true }).catch(() => {});
240
253
  await rm(join(outDir, 'static', 'prebuilt'), { recursive: true, force: true }).catch(
@@ -769,6 +782,35 @@ export function generateWranglerConfig(
769
782
  return merged;
770
783
  }
771
784
 
785
+ // ─── Static output ──────────────────────────────────────────────────────────
786
+
787
+ /**
788
+ * Build a Cloudflare Pages-compatible static output directory.
789
+ *
790
+ * No Workers entry — just pre-rendered HTML, RSC flight files, and client
791
+ * assets in a flat directory. Uses the shared buildStaticDirectoryOutput
792
+ * then adds a `_headers` file for cache control.
793
+ *
794
+ * @internal Exported for testing.
795
+ */
796
+ export async function buildStaticOutput(
797
+ config: TimberConfig,
798
+ buildDir: string,
799
+ outDir: string,
800
+ _options: CloudflareAdapterOptions
801
+ ): Promise<void> {
802
+ // Cloudflare Pages serves from the outDir root directly (no subdirectory).
803
+ // Use '.' as publicDirName so files land at outDir/ not outDir/static/.
804
+ await buildStaticDirectoryOutput(config, buildDir, outDir, '.');
805
+
806
+ // Write _headers file for cache control + content-type rules.
807
+ const unhashedPaths = await findUnhashedStaticFiles(outDir);
808
+ await writeFile(
809
+ join(outDir, '_headers'),
810
+ generateHeadersFile(unhashedPaths, config.staticContentTypes)
811
+ );
812
+ }
813
+
772
814
  // ─── Preview ─────────────────────────────────────────────────────────────────
773
815
 
774
816
  /** Command descriptor for preview — testable without spawning a process. */
@@ -10,8 +10,12 @@ import { execFile } from 'node:child_process';
10
10
  import { join, relative, dirname, basename } from 'node:path';
11
11
  import type { TimberPlatformAdapter, TimberConfig } from './types';
12
12
  import { generateCompressModule } from './compress-module.js';
13
- import { IMMUTABLE_CACHE } from './shared.js';
14
- import { runSharedBuildSteps } from './build-output-helper.js';
13
+ import { IMMUTABLE_CACHE, generateHeadersFile } from './shared.js';
14
+ import {
15
+ runSharedBuildSteps,
16
+ buildStaticDirectoryOutput,
17
+ findUnhashedStaticFiles,
18
+ } from './build-output-helper.js';
15
19
 
16
20
  // ─── Presets ─────────────────────────────────────────────────────────────────
17
21
 
@@ -141,6 +145,15 @@ const PRESET_CONFIGS: Record<NitroPreset, PresetConfig> = {
141
145
 
142
146
  // ─── Options ─────────────────────────────────────────────────────────────────
143
147
 
148
+ /**
149
+ * Static deploy target for `output: 'static'` mode.
150
+ *
151
+ * Controls platform-specific files written alongside the static output:
152
+ * - `'github-pages'` — writes `.nojekyll` to skip Jekyll processing
153
+ * - `'generic'` — no extra files (default)
154
+ */
155
+ export type StaticTarget = 'github-pages' | 'generic';
156
+
144
157
  /** Options for the Nitro adapter. */
145
158
  export interface NitroAdapterOptions {
146
159
  /**
@@ -166,6 +179,13 @@ export interface NitroAdapterOptions {
166
179
  */
167
180
  compress?: boolean;
168
181
 
182
+ /**
183
+ * Static deploy target for `output: 'static'` mode. Controls
184
+ * platform-specific files (e.g. `.nojekyll` for GitHub Pages).
185
+ * @default 'generic'
186
+ */
187
+ staticTarget?: StaticTarget;
188
+
169
189
  /**
170
190
  * Additional Nitro configuration to merge into the generated config.
171
191
  * Overrides default values for the selected preset.
@@ -201,6 +221,42 @@ export function nitro(options: NitroAdapterOptions = {}): TimberPlatformAdapter
201
221
  async buildOutput(config: TimberConfig, buildDir: string) {
202
222
  const outDir = join(buildDir, 'nitro');
203
223
 
224
+ // ── Static mode: plain directory output ────────────────────────
225
+ // No Nitro server — just pre-rendered HTML + client assets.
226
+ if (config.output === 'static' && config.staticPagesDir) {
227
+ await buildStaticDirectoryOutput(config, buildDir, outDir, 'public');
228
+ const publicDir = join(outDir, 'public');
229
+
230
+ // Write _headers for cache control + extensionless Content-Type.
231
+ // Netlify supports the _headers convention just like Cloudflare.
232
+ const unhashedPaths = await findUnhashedStaticFiles(publicDir);
233
+ await writeFile(
234
+ join(publicDir, '_headers'),
235
+ generateHeadersFile(unhashedPaths, config.staticContentTypes)
236
+ );
237
+
238
+ // Platform-specific files for static deploy targets.
239
+ const target = options.staticTarget ?? 'generic';
240
+ if (target === 'github-pages') {
241
+ await writeFile(join(publicDir, '.nojekyll'), '');
242
+ // GitHub Pages doesn't support _redirects files. Warn if any
243
+ // redirects were generated — those routes will 404 on GH Pages.
244
+ try {
245
+ const redirects = await readFile(join(publicDir, '_redirects'), 'utf-8');
246
+ if (redirects.trim().length > 0) {
247
+ console.warn(
248
+ `[timber] Warning: GitHub Pages does not support _redirects files. ` +
249
+ `Redirect rules from middleware/proxy will not work on this platform.`
250
+ );
251
+ }
252
+ } catch {
253
+ // No _redirects file — nothing to warn about
254
+ }
255
+ }
256
+
257
+ return;
258
+ }
259
+
204
260
  // Steps 1–5: shared across all adapters (mkdir, copy client/rsc/ssr,
205
261
  // write _headers, write manifest-init).
206
262
  await runSharedBuildSteps({
@@ -35,10 +35,13 @@ export const STATIC_CACHE = 'public, max-age=3600, must-revalidate';
35
35
  * (invalid per RFC 9111).
36
36
  */
37
37
  // Cloudflare Workers Static Assets caps _headers at 100 rules.
38
- // Reserve 2 for the wildcard hashed-asset rules.
39
- const MAX_UNHASHED_RULES = 98;
38
+ // Reserve 3 for the wildcard hashed-asset rules (/assets/*, /_timber/fonts/*, /_rsc/*).
39
+ const MAX_PER_FILE_RULES = 97;
40
40
 
41
- export function generateHeadersFile(unhashedPaths?: string[]): string {
41
+ export function generateHeadersFile(
42
+ unhashedPaths?: string[],
43
+ contentTypes?: Record<string, string>
44
+ ): string {
42
45
  let content = `# Auto-generated by @timber-js/app — static asset cache headers.
43
46
  # See design/25-production-deployments.md §"CDN / Edge Cache"
44
47
 
@@ -47,20 +50,48 @@ export function generateHeadersFile(unhashedPaths?: string[]): string {
47
50
 
48
51
  /_timber/fonts/*
49
52
  Cache-Control: ${IMMUTABLE_CACHE}
53
+
54
+ /_rsc/*
55
+ Cache-Control: ${IMMUTABLE_CACHE}
50
56
  `;
51
57
 
58
+ // Content-Type rules are higher priority than cache headers for
59
+ // extensionless files — without them the file is unreadable.
60
+ const allCtEntries = contentTypes
61
+ ? Object.entries(contentTypes).sort(([a], [b]) => a.localeCompare(b))
62
+ : [];
63
+ const ctEntries = allCtEntries.slice(0, MAX_PER_FILE_RULES);
64
+ if (allCtEntries.length > MAX_PER_FILE_RULES) {
65
+ console.warn(
66
+ `[timber] _headers: ${allCtEntries.length} content-type rules exceed ` +
67
+ `Cloudflare's 100-rule limit. Only the first ${MAX_PER_FILE_RULES} ` +
68
+ `extensionless API routes will get Content-Type headers.`
69
+ );
70
+ }
71
+ const remainingBudget = MAX_PER_FILE_RULES - ctEntries.length;
72
+
52
73
  if (unhashedPaths && unhashedPaths.length > 0) {
53
- const capped = unhashedPaths.slice(0, MAX_UNHASHED_RULES);
54
- if (unhashedPaths.length > MAX_UNHASHED_RULES) {
74
+ const capped = unhashedPaths.slice(0, Math.max(0, remainingBudget));
75
+ if (unhashedPaths.length > remainingBudget) {
55
76
  console.warn(
56
- `[timber] _headers: ${unhashedPaths.length} unhashed static files found, ` +
57
- `but Cloudflare limits _headers to 100 rules. Only the first ${MAX_UNHASHED_RULES} ` +
58
- `unhashed files will get Cache-Control headers.`
77
+ `[timber] _headers: ${unhashedPaths.length} unhashed static files + ` +
78
+ `${ctEntries.length} content-type rules found, but Cloudflare limits ` +
79
+ `_headers to 100 rules. Only the first ${capped.length} unhashed ` +
80
+ `files will get Cache-Control headers.`
59
81
  );
60
82
  }
61
- content += `\n# Unhashed static assets — short-lived cache with revalidation\n`;
62
- for (const p of capped) {
63
- content += `\n${p}\n Cache-Control: ${STATIC_CACHE}\n`;
83
+ if (capped.length > 0) {
84
+ content += `\n# Unhashed static assets — short-lived cache with revalidation\n`;
85
+ for (const p of capped) {
86
+ content += `\n${p}\n Cache-Control: ${STATIC_CACHE}\n`;
87
+ }
88
+ }
89
+ }
90
+
91
+ if (ctEntries.length > 0) {
92
+ content += `\n# Content-Type for extensionless API routes\n`;
93
+ for (const [urlPath, ct] of ctEntries) {
94
+ content += `\n${urlPath}\n Content-Type: ${ct}\n`;
64
95
  }
65
96
  }
66
97