@timber-js/app 0.2.0-alpha.55 → 0.2.0-alpha.57
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/dist/_chunks/{interception-D2djYaIm.js → interception-Cey5DCGr.js} +18 -1
- package/dist/_chunks/interception-Cey5DCGr.js.map +1 -0
- package/dist/_chunks/{stale-reload-4L-_skC7.js → stale-reload-Db4wqE46.js} +16 -2
- package/dist/_chunks/stale-reload-Db4wqE46.js.map +1 -0
- package/dist/client/index.js +1 -1
- package/dist/client/stale-reload.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/routing/index.js +1 -1
- package/dist/routing/scanner.d.ts.map +1 -1
- package/dist/routing/types.d.ts +10 -0
- package/dist/routing/types.d.ts.map +1 -1
- package/dist/server/fallback-error.d.ts +2 -1
- package/dist/server/fallback-error.d.ts.map +1 -1
- package/dist/server/index.js +4 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/route-matcher.d.ts +7 -0
- package/dist/server/route-matcher.d.ts.map +1 -1
- package/dist/server/rsc-entry/error-renderer.d.ts +10 -1
- package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-renderer.d.ts +3 -0
- package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client/stale-reload.ts +27 -3
- package/src/plugins/routing.ts +8 -0
- package/src/routing/scanner.ts +30 -0
- package/src/routing/types.ts +10 -0
- package/src/server/fallback-error.ts +5 -2
- package/src/server/pipeline.ts +6 -0
- package/src/server/route-matcher.ts +7 -0
- package/src/server/rsc-entry/error-renderer.ts +105 -2
- package/src/server/rsc-entry/index.ts +16 -4
- package/src/server/rsc-entry/ssr-renderer.ts +10 -4
- package/dist/_chunks/interception-D2djYaIm.js.map +0 -1
- package/dist/_chunks/stale-reload-4L-_skC7.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/server/pipeline.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/server/pipeline.ts"],"names":[],"mappings":"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"}
|
|
@@ -46,6 +46,13 @@ export interface ManifestSegmentNode {
|
|
|
46
46
|
export interface ManifestRoot {
|
|
47
47
|
root: ManifestSegmentNode;
|
|
48
48
|
proxy?: ManifestFile;
|
|
49
|
+
/**
|
|
50
|
+
* Global error page: app/global-error.{tsx,ts,jsx,js}
|
|
51
|
+
* Tier 2 — standalone full-page replacement (no layouts) when no
|
|
52
|
+
* segment-level error file is found. SSR-only render.
|
|
53
|
+
* See design/10-error-handling.md §"Tier 2"
|
|
54
|
+
*/
|
|
55
|
+
globalError?: ManifestFile;
|
|
49
56
|
}
|
|
50
57
|
/**
|
|
51
58
|
* Create a route matcher function from a manifest.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-matcher.d.ts","sourceRoot":"","sources":["../../src/server/route-matcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,sBAAsB,CAAC;AAM9B,6DAA6D;AAC7D,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,gFAAgF;AAChF,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EACP,QAAQ,GACR,SAAS,GACT,WAAW,GACX,oBAAoB,GACpB,OAAO,GACP,MAAM,GACN,cAAc,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,kBAAkB,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;IAC3D,8EAA8E;IAC9E,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAEhC,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,2FAA2F;IAC3F,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAC3C,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAC/C,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACjD,sFAAsF;IACtF,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAE9C,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;CAC5C;AAED,6DAA6D;AAC7D,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,mBAAmB,CAAC;IAC1B,KAAK,CAAC,EAAE,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"route-matcher.d.ts","sourceRoot":"","sources":["../../src/server/route-matcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,sBAAsB,CAAC;AAM9B,6DAA6D;AAC7D,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,gFAAgF;AAChF,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EACP,QAAQ,GACR,SAAS,GACT,WAAW,GACX,oBAAoB,GACpB,OAAO,GACP,MAAM,GACN,cAAc,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,kBAAkB,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;IAC3D,8EAA8E;IAC9E,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAEhC,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,2FAA2F;IAC3F,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAC3C,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAC/C,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACjD,sFAAsF;IACtF,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAE9C,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;CAC5C;AAED,6DAA6D;AAC7D,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,mBAAmB,CAAC;IAC1B,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,YAAY,CAAC;CAC5B;AAID;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,YAAY,GACrB,CAAC,QAAQ,EAAE,MAAM,KAAK,UAAU,GAAG,IAAI,CAEzC;AA4MD,2CAA2C;AAC3C,MAAM,WAAW,kBAAkB;IACjC,4DAA4D;IAC5D,IAAI,EAAE,iBAAiB,CAAC;IACxB,4CAA4C;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB,0DAA0D;IAC1D,IAAI,EAAE,YAAY,CAAC;IACnB,0DAA0D;IAC1D,OAAO,EAAE,mBAAmB,CAAC;IAC7B;;;OAGG;IACH,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,YAAY,GACrB,CAAC,QAAQ,EAAE,MAAM,KAAK,kBAAkB,GAAG,IAAI,CAMjD"}
|
|
@@ -15,6 +15,15 @@ import type { RouteMatch } from '#/server/pipeline.js';
|
|
|
15
15
|
import type { ManifestSegmentNode } from '#/server/route-matcher.js';
|
|
16
16
|
import type { ClientBootstrapConfig } from '#/server/html-injectors.js';
|
|
17
17
|
import type { LayoutEntry } from '#/server/deny-renderer.js';
|
|
18
|
+
/**
|
|
19
|
+
* A manifest file reference with lazy import and path.
|
|
20
|
+
* Mirrors the shape from ManifestSegmentNode but kept local to avoid
|
|
21
|
+
* exporting internal types from route-matcher.
|
|
22
|
+
*/
|
|
23
|
+
export interface GlobalErrorFile {
|
|
24
|
+
load: () => Promise<unknown>;
|
|
25
|
+
filePath: string;
|
|
26
|
+
}
|
|
18
27
|
/**
|
|
19
28
|
* Render an error page for unhandled throws or RenderError outside Suspense.
|
|
20
29
|
*
|
|
@@ -22,7 +31,7 @@ import type { LayoutEntry } from '#/server/deny-renderer.js';
|
|
|
22
31
|
* - TSX/JSX: SSR-only (bypass RSC, render directly through Fizz)
|
|
23
32
|
* - MDX: RSC → SSR (server components need Flight, but props are plain)
|
|
24
33
|
*/
|
|
25
|
-
export declare function renderErrorPage(error: unknown, status: number, segments: ManifestSegmentNode[], layoutComponents: LayoutEntry[], req: Request, match: RouteMatch, responseHeaders: Headers, clientBootstrap: ClientBootstrapConfig): Promise<Response>;
|
|
34
|
+
export declare function renderErrorPage(error: unknown, status: number, segments: ManifestSegmentNode[], layoutComponents: LayoutEntry[], req: Request, match: RouteMatch, responseHeaders: Headers, clientBootstrap: ClientBootstrapConfig, globalError?: GlobalErrorFile): Promise<Response>;
|
|
26
35
|
/**
|
|
27
36
|
* Render a 404 page for URLs that don't match any route.
|
|
28
37
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-renderer.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/error-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAErE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAGxE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"error-renderer.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/error-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAErE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAGxE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAO7D;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAuGD;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,mBAAmB,EAAE,EAC/B,gBAAgB,EAAE,WAAW,EAAE,EAC/B,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,UAAU,EACjB,eAAe,EAAE,OAAO,EACxB,eAAe,EAAE,qBAAqB,EACtC,WAAW,CAAC,EAAE,eAAe,GAC5B,OAAO,CAAC,QAAQ,CAAC,CAoDnB;AAyLD;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,OAAO,EACZ,WAAW,EAAE,mBAAmB,EAChC,eAAe,EAAE,OAAO,EACxB,eAAe,EAAE,qBAAqB,GACrC,OAAO,CAAC,QAAQ,CAAC,CA6BnB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/index.ts"],"names":[],"mappings":"AA4DA,OAAO,EAKL,KAAK,mBAAmB,EACzB,MAAM,cAAc,CAAC;AAiCtB;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,mBAAmB,EAAE,KAAK,IAAI,GACtF,IAAI,CAEN;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/index.ts"],"names":[],"mappings":"AA4DA,OAAO,EAKL,KAAK,mBAAmB,EACzB,MAAM,cAAc,CAAC;AAiCtB;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,mBAAmB,EAAE,KAAK,IAAI,GACtF,IAAI,CAEN;AAqdD,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAIzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;8BA7S3C,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;AA+ShD,wBAAiE"}
|
|
@@ -15,6 +15,7 @@ import type { ClientBootstrapConfig } from '#/server/html-injectors.js';
|
|
|
15
15
|
import type { RouteMatch } from '#/server/pipeline.js';
|
|
16
16
|
import type { LayoutComponentEntry } from '#/server/route-element-builder.js';
|
|
17
17
|
import type { ManifestSegmentNode } from '#/server/route-matcher.js';
|
|
18
|
+
import { type GlobalErrorFile } from './error-renderer.js';
|
|
18
19
|
import type { RenderSignals } from './rsc-stream.js';
|
|
19
20
|
/**
|
|
20
21
|
* Test-only observable: records how many microtask yields were consumed
|
|
@@ -40,6 +41,8 @@ interface SsrRenderOptions {
|
|
|
40
41
|
clientJsDisabled: boolean;
|
|
41
42
|
headHtml: string;
|
|
42
43
|
deferSuspenseFor: number;
|
|
44
|
+
/** Tier 2 global-error.tsx file, if present in app/. */
|
|
45
|
+
globalError?: GlobalErrorFile;
|
|
43
46
|
}
|
|
44
47
|
/**
|
|
45
48
|
* Render the RSC stream to HTML via SSR.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr-renderer.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/ssr-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAIxE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"ssr-renderer.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/ssr-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAIxE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAUrE,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAYrD;;;;;;;;GAQG;AACH,eAAO,MAAM,gCAAgC,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAiB,CAAC;AAEhF,UAAU,gBAAgB;IACxB,GAAG,EAAE,OAAO,CAAC;IACb,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACtC,OAAO,EAAE,aAAa,CAAC;IACvB,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,gBAAgB,EAAE,oBAAoB,EAAE,CAAC;IACzC,KAAK,EAAE,UAAU,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;IACzB,eAAe,EAAE,qBAAqB,CAAC;IACvC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,wDAAwD;IACxD,WAAW,CAAC,EAAE,eAAe,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAkPjF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timber-js/app",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.57",
|
|
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",
|
|
@@ -16,6 +16,16 @@
|
|
|
16
16
|
|
|
17
17
|
const RELOAD_FLAG_KEY = '__timber_stale_reload';
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* In-memory fallback counter for environments where sessionStorage is
|
|
21
|
+
* unavailable (private browsing, storage full, extension interference).
|
|
22
|
+
* Incremented each time triggerStaleReload() falls into the catch path.
|
|
23
|
+
* If the counter exceeds 0 on a subsequent call, the reload is suppressed
|
|
24
|
+
* to prevent an infinite loop. Resets naturally on page load (module
|
|
25
|
+
* re-evaluates) and can be manually reset via clearStaleReloadFlag().
|
|
26
|
+
*/
|
|
27
|
+
let memoryReloadCount = 0;
|
|
28
|
+
|
|
19
29
|
/**
|
|
20
30
|
* Check if an error is a stale client reference error from React's
|
|
21
31
|
* Flight client. These errors have the message pattern:
|
|
@@ -93,9 +103,22 @@ export function triggerStaleReload(): boolean {
|
|
|
93
103
|
window.location.reload();
|
|
94
104
|
return true;
|
|
95
105
|
} catch {
|
|
96
|
-
// sessionStorage
|
|
97
|
-
//
|
|
98
|
-
|
|
106
|
+
// sessionStorage unavailable (private browsing, storage full, etc.)
|
|
107
|
+
// Use in-memory counter as fallback loop guard
|
|
108
|
+
if (memoryReloadCount > 0) {
|
|
109
|
+
console.warn(
|
|
110
|
+
'[timber] Stale client reference detected again after reload. ' +
|
|
111
|
+
'Not reloading to prevent infinite loop. ' +
|
|
112
|
+
'This may indicate a deployment issue — try a hard refresh.'
|
|
113
|
+
);
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
memoryReloadCount++;
|
|
118
|
+
console.warn(
|
|
119
|
+
'[timber] Stale client reference detected — the server has been ' +
|
|
120
|
+
'redeployed with new bundles. Reloading to pick up the new version.'
|
|
121
|
+
);
|
|
99
122
|
window.location.reload();
|
|
100
123
|
return true;
|
|
101
124
|
}
|
|
@@ -107,6 +130,7 @@ export function triggerStaleReload(): boolean {
|
|
|
107
130
|
* reference error should trigger a fresh reload attempt.
|
|
108
131
|
*/
|
|
109
132
|
export function clearStaleReloadFlag(): void {
|
|
133
|
+
memoryReloadCount = 0;
|
|
110
134
|
try {
|
|
111
135
|
sessionStorage.removeItem(RELOAD_FLAG_KEY);
|
|
112
136
|
} catch {
|
package/src/plugins/routing.ts
CHANGED
|
@@ -423,6 +423,13 @@ function generateManifestModule(tree: RouteTree): string {
|
|
|
423
423
|
proxyLine = ` proxy: { load: ${v}, filePath: ${JSON.stringify(tree.proxy.filePath)} },`;
|
|
424
424
|
}
|
|
425
425
|
|
|
426
|
+
// Global error page (Tier 2)
|
|
427
|
+
let globalErrorLine = '';
|
|
428
|
+
if (tree.globalError) {
|
|
429
|
+
const v = addImport(tree.globalError);
|
|
430
|
+
globalErrorLine = ` globalError: { load: ${v}, filePath: ${JSON.stringify(tree.globalError.filePath)} },`;
|
|
431
|
+
}
|
|
432
|
+
|
|
426
433
|
// Interception rewrites — computed at build time from the route tree.
|
|
427
434
|
// Only interceptedPattern and interceptingPrefix are needed at runtime.
|
|
428
435
|
const rewrites = collectInterceptionRewrites(tree.root);
|
|
@@ -439,6 +446,7 @@ function generateManifestModule(tree: RouteTree): string {
|
|
|
439
446
|
'',
|
|
440
447
|
'const manifest = {',
|
|
441
448
|
proxyLine,
|
|
449
|
+
globalErrorLine,
|
|
442
450
|
rewritesLine,
|
|
443
451
|
` root: ${rootSerialized},`,
|
|
444
452
|
'};',
|
package/src/routing/scanner.ts
CHANGED
|
@@ -83,6 +83,14 @@ export function scanRoutes(appDir: string, config: ScannerConfig = {}): RouteTre
|
|
|
83
83
|
tree.proxy = proxyFile;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
// Check for global-error.{tsx,ts,jsx,js} at app root.
|
|
87
|
+
// Tier 2 error page — renders standalone (no layouts) when no segment-level
|
|
88
|
+
// error file is found. See design/10-error-handling.md §"Tier 2".
|
|
89
|
+
const globalErrorFile = findPageExtFile(appDir, 'global-error', extSet);
|
|
90
|
+
if (globalErrorFile) {
|
|
91
|
+
tree.globalError = globalErrorFile;
|
|
92
|
+
}
|
|
93
|
+
|
|
86
94
|
// Scan the root directory's files
|
|
87
95
|
scanSegmentFiles(appDir, tree.root, extSet);
|
|
88
96
|
|
|
@@ -547,3 +555,25 @@ function findFixedFile(dirPath: string, name: string): RouteFile | undefined {
|
|
|
547
555
|
}
|
|
548
556
|
return undefined;
|
|
549
557
|
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Find a file using the configured page extensions (tsx, ts, jsx, js, mdx, etc.).
|
|
561
|
+
* Used for app-root conventions like global-error that aren't per-segment.
|
|
562
|
+
*/
|
|
563
|
+
function findPageExtFile(
|
|
564
|
+
dirPath: string,
|
|
565
|
+
name: string,
|
|
566
|
+
extSet: Set<string>
|
|
567
|
+
): RouteFile | undefined {
|
|
568
|
+
for (const ext of extSet) {
|
|
569
|
+
const fullPath = join(dirPath, `${name}.${ext}`);
|
|
570
|
+
try {
|
|
571
|
+
if (statSync(fullPath).isFile()) {
|
|
572
|
+
return { filePath: fullPath, extension: ext };
|
|
573
|
+
}
|
|
574
|
+
} catch {
|
|
575
|
+
// File doesn't exist
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return undefined;
|
|
579
|
+
}
|
package/src/routing/types.ts
CHANGED
|
@@ -91,6 +91,16 @@ export interface RouteTree {
|
|
|
91
91
|
root: SegmentNode;
|
|
92
92
|
/** All discovered proxy.ts files (should be at most one, in app/) */
|
|
93
93
|
proxy?: RouteFile;
|
|
94
|
+
/**
|
|
95
|
+
* Global error page: app/global-error.{tsx,ts,jsx,js}
|
|
96
|
+
*
|
|
97
|
+
* Rendered as a standalone full-page replacement (no layout wrapping)
|
|
98
|
+
* when no segment-level error file is found. SSR-only render path.
|
|
99
|
+
* Must provide its own <html> and <body>.
|
|
100
|
+
*
|
|
101
|
+
* See design/10-error-handling.md §"Tier 2 — Global Error Page"
|
|
102
|
+
*/
|
|
103
|
+
globalError?: RouteFile;
|
|
94
104
|
}
|
|
95
105
|
|
|
96
106
|
/** Configuration passed to the scanner */
|
|
@@ -14,6 +14,7 @@ import type { RouteMatch } from '#/server/pipeline.js';
|
|
|
14
14
|
import type { ManifestSegmentNode } from '#/server/route-matcher.js';
|
|
15
15
|
import type { ClientBootstrapConfig } from '#/server/html-injectors.js';
|
|
16
16
|
import type { LayoutEntry } from '#/server/deny-renderer.js';
|
|
17
|
+
import type { GlobalErrorFile } from '#/server/rsc-entry/error-renderer.js';
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Render a fallback error page when the render pipeline throws.
|
|
@@ -27,7 +28,8 @@ export async function renderFallbackError(
|
|
|
27
28
|
responseHeaders: Headers,
|
|
28
29
|
isDev: boolean,
|
|
29
30
|
rootSegment: ManifestSegmentNode,
|
|
30
|
-
clientBootstrap: ClientBootstrapConfig
|
|
31
|
+
clientBootstrap: ClientBootstrapConfig,
|
|
32
|
+
globalError?: GlobalErrorFile
|
|
31
33
|
): Promise<Response> {
|
|
32
34
|
if (isDev) {
|
|
33
35
|
return renderDevErrorPage(error);
|
|
@@ -54,7 +56,8 @@ export async function renderFallbackError(
|
|
|
54
56
|
req,
|
|
55
57
|
match,
|
|
56
58
|
responseHeaders,
|
|
57
|
-
clientBootstrap
|
|
59
|
+
clientBootstrap,
|
|
60
|
+
globalError
|
|
58
61
|
);
|
|
59
62
|
}
|
|
60
63
|
|
package/src/server/pipeline.ts
CHANGED
|
@@ -480,6 +480,12 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
|
|
|
480
480
|
await coerceSegmentParams(match);
|
|
481
481
|
} catch (error) {
|
|
482
482
|
if (error instanceof ParamCoercionError) {
|
|
483
|
+
// Route through the app's 404 page (404.tsx in root layout) instead of
|
|
484
|
+
// returning a bare empty 404 Response. Falls back to bare 404 only if
|
|
485
|
+
// no renderNoMatch renderer is configured.
|
|
486
|
+
if (config.renderNoMatch) {
|
|
487
|
+
return config.renderNoMatch(req, responseHeaders);
|
|
488
|
+
}
|
|
483
489
|
return new Response(null, { status: 404 });
|
|
484
490
|
}
|
|
485
491
|
throw error;
|
|
@@ -69,6 +69,13 @@ export interface ManifestSegmentNode {
|
|
|
69
69
|
export interface ManifestRoot {
|
|
70
70
|
root: ManifestSegmentNode;
|
|
71
71
|
proxy?: ManifestFile;
|
|
72
|
+
/**
|
|
73
|
+
* Global error page: app/global-error.{tsx,ts,jsx,js}
|
|
74
|
+
* Tier 2 — standalone full-page replacement (no layouts) when no
|
|
75
|
+
* segment-level error file is found. SSR-only render.
|
|
76
|
+
* See design/10-error-handling.md §"Tier 2"
|
|
77
|
+
*/
|
|
78
|
+
globalError?: ManifestFile;
|
|
72
79
|
}
|
|
73
80
|
|
|
74
81
|
// ─── Matcher ──────────────────────────────────────────────────────────────
|
|
@@ -29,6 +29,16 @@ import { getCookiesForSsr } from '#/server/request-context.js';
|
|
|
29
29
|
import { callSsr } from './ssr-bridge.js';
|
|
30
30
|
import { callSsrErrorPage } from './ssr-error-bridge.js';
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* A manifest file reference with lazy import and path.
|
|
34
|
+
* Mirrors the shape from ManifestSegmentNode but kept local to avoid
|
|
35
|
+
* exporting internal types from route-matcher.
|
|
36
|
+
*/
|
|
37
|
+
export interface GlobalErrorFile {
|
|
38
|
+
load: () => Promise<unknown>;
|
|
39
|
+
filePath: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
32
42
|
/** MDX/markdown extensions — server components that need RSC rendering. */
|
|
33
43
|
const MDX_EXTENSIONS = new Set(['.mdx', '.md']);
|
|
34
44
|
|
|
@@ -145,14 +155,28 @@ export async function renderErrorPage(
|
|
|
145
155
|
req: Request,
|
|
146
156
|
match: RouteMatch,
|
|
147
157
|
responseHeaders: Headers,
|
|
148
|
-
clientBootstrap: ClientBootstrapConfig
|
|
158
|
+
clientBootstrap: ClientBootstrapConfig,
|
|
159
|
+
globalError?: GlobalErrorFile
|
|
149
160
|
): Promise<Response> {
|
|
150
161
|
const h = createElement as (...args: unknown[]) => React.ReactElement;
|
|
151
162
|
|
|
152
163
|
// Walk segments from leaf to root to find the error component
|
|
153
164
|
const resolution = await resolveErrorFile(status, segments);
|
|
154
165
|
|
|
155
|
-
// Tier
|
|
166
|
+
// Tier 2: No segment-level error file — try global-error.tsx
|
|
167
|
+
if (!resolution && globalError) {
|
|
168
|
+
return renderGlobalErrorPage(
|
|
169
|
+
error,
|
|
170
|
+
status,
|
|
171
|
+
globalError,
|
|
172
|
+
req,
|
|
173
|
+
match,
|
|
174
|
+
responseHeaders,
|
|
175
|
+
clientBootstrap
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Tier 3: No error component found at any level — bare response
|
|
156
180
|
if (!resolution) {
|
|
157
181
|
return new Response(null, { status, headers: responseHeaders });
|
|
158
182
|
}
|
|
@@ -291,6 +315,85 @@ async function renderErrorPageViaMdx(
|
|
|
291
315
|
return callSsr(ssrStream, navContext);
|
|
292
316
|
}
|
|
293
317
|
|
|
318
|
+
/**
|
|
319
|
+
* Tier 2 — Render global-error.tsx as a standalone full-page replacement.
|
|
320
|
+
*
|
|
321
|
+
* No layout wrapping. The component must provide its own <html> and <body>.
|
|
322
|
+
* Uses SSR-only render (bypasses RSC Flight) since global-error.tsx is
|
|
323
|
+
* a 'use client' component receiving { error, digest, reset }.
|
|
324
|
+
*
|
|
325
|
+
* MDX global-error files go through RSC → SSR with plain props.
|
|
326
|
+
*
|
|
327
|
+
* See design/10-error-handling.md §"Tier 2 — Global Error Page"
|
|
328
|
+
*/
|
|
329
|
+
async function renderGlobalErrorPage(
|
|
330
|
+
error: unknown,
|
|
331
|
+
status: number,
|
|
332
|
+
globalError: GlobalErrorFile,
|
|
333
|
+
req: Request,
|
|
334
|
+
match: RouteMatch,
|
|
335
|
+
responseHeaders: Headers,
|
|
336
|
+
clientBootstrap: ClientBootstrapConfig
|
|
337
|
+
): Promise<Response> {
|
|
338
|
+
const h = createElement as (...args: unknown[]) => React.ReactElement;
|
|
339
|
+
|
|
340
|
+
if (isMdxFile(globalError.filePath)) {
|
|
341
|
+
// MDX global-error: RSC → SSR with plain props (no Error object)
|
|
342
|
+
const mod = (await globalError.load()) as Record<string, unknown>;
|
|
343
|
+
if (!mod.default) {
|
|
344
|
+
return new Response(null, { status, headers: responseHeaders });
|
|
345
|
+
}
|
|
346
|
+
const component = mod.default as (...args: unknown[]) => unknown;
|
|
347
|
+
const element = h(component, { status });
|
|
348
|
+
|
|
349
|
+
const rscStream = renderToReadableStream(element, {
|
|
350
|
+
onError(err: unknown) {
|
|
351
|
+
logRenderError({ method: req.method, path: new URL(req.url).pathname, error: err });
|
|
352
|
+
},
|
|
353
|
+
debugChannel: createDebugChannelSink(),
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
const [ssrStream, inlineStream] = rscStream.tee();
|
|
357
|
+
|
|
358
|
+
const navContext: NavContext = {
|
|
359
|
+
pathname: new URL(req.url).pathname,
|
|
360
|
+
params: match.params,
|
|
361
|
+
searchParams: Object.fromEntries(new URL(req.url).searchParams),
|
|
362
|
+
statusCode: status,
|
|
363
|
+
responseHeaders,
|
|
364
|
+
headHtml: flightInitScript(),
|
|
365
|
+
bootstrapScriptContent: clientBootstrap.bootstrapScriptContent,
|
|
366
|
+
rscStream: inlineStream,
|
|
367
|
+
cookies: getCookiesForSsr(),
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
return callSsr(ssrStream, navContext);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// TSX/JSX global-error: SSR-only render, no layouts
|
|
374
|
+
const digest =
|
|
375
|
+
error instanceof RenderError ? { code: error.code, data: error.digest.data } : null;
|
|
376
|
+
|
|
377
|
+
return callSsrErrorPage({
|
|
378
|
+
pathname: new URL(req.url).pathname,
|
|
379
|
+
params: match.params,
|
|
380
|
+
searchParams: Object.fromEntries(new URL(req.url).searchParams),
|
|
381
|
+
statusCode: status,
|
|
382
|
+
responseHeaders,
|
|
383
|
+
headHtml: '',
|
|
384
|
+
bootstrapScriptContent: clientBootstrap.bootstrapScriptContent,
|
|
385
|
+
signal: req.signal,
|
|
386
|
+
cookies: getCookiesForSsr(),
|
|
387
|
+
errorProps: {
|
|
388
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
389
|
+
digest,
|
|
390
|
+
reset: undefined,
|
|
391
|
+
},
|
|
392
|
+
errorComponentPath: globalError.filePath,
|
|
393
|
+
layoutPaths: [], // No layouts — global-error is standalone
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
294
397
|
/**
|
|
295
398
|
* Render a 404 page for URLs that don't match any route.
|
|
296
399
|
*
|
|
@@ -234,7 +234,8 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
|
|
|
234
234
|
clientBootstrap,
|
|
235
235
|
clientJsDisabled,
|
|
236
236
|
interception,
|
|
237
|
-
manifest.root
|
|
237
|
+
manifest.root,
|
|
238
|
+
manifest.globalError
|
|
238
239
|
);
|
|
239
240
|
},
|
|
240
241
|
renderNoMatch: async (req: Request, responseHeaders: Headers) => {
|
|
@@ -259,7 +260,15 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
|
|
|
259
260
|
}
|
|
260
261
|
: undefined,
|
|
261
262
|
renderFallbackError: (error, req, responseHeaders) =>
|
|
262
|
-
renderFallback(
|
|
263
|
+
renderFallback(
|
|
264
|
+
error,
|
|
265
|
+
req,
|
|
266
|
+
responseHeaders,
|
|
267
|
+
isDev,
|
|
268
|
+
manifest.root,
|
|
269
|
+
clientBootstrap,
|
|
270
|
+
manifest.globalError
|
|
271
|
+
),
|
|
263
272
|
};
|
|
264
273
|
|
|
265
274
|
const pipeline = createPipeline(pipelineConfig);
|
|
@@ -339,7 +348,8 @@ async function renderRoute(
|
|
|
339
348
|
clientBootstrap: ClientBootstrapConfig,
|
|
340
349
|
clientJsDisabled: boolean,
|
|
341
350
|
interception?: InterceptionContext,
|
|
342
|
-
rootSegment?: ManifestSegmentNode
|
|
351
|
+
rootSegment?: ManifestSegmentNode,
|
|
352
|
+
globalError?: { load: () => Promise<unknown>; filePath: string }
|
|
343
353
|
): Promise<Response> {
|
|
344
354
|
const segments = match.segments as unknown as ManifestSegmentNode[];
|
|
345
355
|
const leaf = segments[segments.length - 1];
|
|
@@ -529,7 +539,8 @@ async function renderRoute(
|
|
|
529
539
|
_req,
|
|
530
540
|
match,
|
|
531
541
|
responseHeaders,
|
|
532
|
-
clientBootstrap
|
|
542
|
+
clientBootstrap,
|
|
543
|
+
globalError
|
|
533
544
|
);
|
|
534
545
|
}
|
|
535
546
|
|
|
@@ -563,6 +574,7 @@ async function renderRoute(
|
|
|
563
574
|
clientJsDisabled,
|
|
564
575
|
headHtml,
|
|
565
576
|
deferSuspenseFor,
|
|
577
|
+
globalError,
|
|
566
578
|
});
|
|
567
579
|
}
|
|
568
580
|
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
isAbortError,
|
|
30
30
|
} from './helpers.js';
|
|
31
31
|
import { getCookiesForSsr } from '#/server/request-context.js';
|
|
32
|
-
import { renderErrorPage } from './error-renderer.js';
|
|
32
|
+
import { renderErrorPage, type GlobalErrorFile } from './error-renderer.js';
|
|
33
33
|
import { callSsr } from './ssr-bridge.js';
|
|
34
34
|
import type { RenderSignals } from './rsc-stream.js';
|
|
35
35
|
import { recordTiming } from '#/server/server-timing.js';
|
|
@@ -66,6 +66,8 @@ interface SsrRenderOptions {
|
|
|
66
66
|
clientJsDisabled: boolean;
|
|
67
67
|
headHtml: string;
|
|
68
68
|
deferSuspenseFor: number;
|
|
69
|
+
/** Tier 2 global-error.tsx file, if present in app/. */
|
|
70
|
+
globalError?: GlobalErrorFile;
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
/**
|
|
@@ -99,6 +101,7 @@ export async function renderSsrResponse(opts: SsrRenderOptions): Promise<Respons
|
|
|
99
101
|
clientJsDisabled,
|
|
100
102
|
headHtml,
|
|
101
103
|
deferSuspenseFor,
|
|
104
|
+
globalError,
|
|
102
105
|
} = opts;
|
|
103
106
|
|
|
104
107
|
// Tee the RSC stream — one copy goes to SSR for HTML rendering,
|
|
@@ -176,7 +179,8 @@ export async function renderSsrResponse(opts: SsrRenderOptions): Promise<Respons
|
|
|
176
179
|
req,
|
|
177
180
|
match,
|
|
178
181
|
responseHeaders,
|
|
179
|
-
clientBootstrap
|
|
182
|
+
clientBootstrap,
|
|
183
|
+
globalError
|
|
180
184
|
);
|
|
181
185
|
}
|
|
182
186
|
return null;
|
|
@@ -298,7 +302,8 @@ export async function renderSsrResponse(opts: SsrRenderOptions): Promise<Respons
|
|
|
298
302
|
req,
|
|
299
303
|
match,
|
|
300
304
|
responseHeaders,
|
|
301
|
-
clientBootstrap
|
|
305
|
+
clientBootstrap,
|
|
306
|
+
globalError
|
|
302
307
|
);
|
|
303
308
|
}
|
|
304
309
|
// No captured signal — unhandled error in the RSC stream.
|
|
@@ -312,7 +317,8 @@ export async function renderSsrResponse(opts: SsrRenderOptions): Promise<Respons
|
|
|
312
317
|
req,
|
|
313
318
|
match,
|
|
314
319
|
responseHeaders,
|
|
315
|
-
clientBootstrap
|
|
320
|
+
clientBootstrap,
|
|
321
|
+
globalError
|
|
316
322
|
);
|
|
317
323
|
}
|
|
318
324
|
|