@timber-js/app 0.2.0-alpha.196 → 0.2.0-alpha.198
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/{actions-CWYtq6ii.js → actions-BS-m5SLv.js} +3 -3
- package/dist/_chunks/{actions-CWYtq6ii.js.map → actions-BS-m5SLv.js.map} +1 -1
- package/dist/_chunks/als-registry-C6kcfprT.js.map +1 -1
- package/dist/_chunks/{build-manifest-DWppEdLB.js → build-manifest-DTmSGLRz.js} +51 -2
- package/dist/_chunks/build-manifest-DTmSGLRz.js.map +1 -0
- package/dist/_chunks/{cache-api-CQeYzA5g.js → cache-api-DqzgTEqk.js} +4 -49
- package/dist/_chunks/cache-api-DqzgTEqk.js.map +1 -0
- package/dist/_chunks/{chains-h7EO-u3n.js → chains-CZG7E5zg.js} +2 -2
- package/dist/_chunks/{chains-h7EO-u3n.js.map → chains-CZG7E5zg.js.map} +1 -1
- package/dist/_chunks/{cli-check-BVthpfLS.js → cli-check-dVDi1GQz.js} +3 -3
- package/dist/_chunks/{cli-check-BVthpfLS.js.map → cli-check-dVDi1GQz.js.map} +1 -1
- package/dist/_chunks/{cli-schema-sync-3Wutm8pH.js → cli-schema-sync-DTy_-Msq.js} +2 -2
- package/dist/_chunks/{cli-schema-sync-3Wutm8pH.js.map → cli-schema-sync-DTy_-Msq.js.map} +1 -1
- package/dist/_chunks/{cloudflare-BKJC3SC_.js → cloudflare-BFb__LYG.js} +2 -2
- package/dist/_chunks/{cloudflare-BKJC3SC_.js.map → cloudflare-BFb__LYG.js.map} +1 -1
- package/dist/_chunks/{convention-lint-DO10_pVl.js → convention-lint-Ph6luW4c.js} +4 -2
- package/dist/_chunks/convention-lint-Ph6luW4c.js.map +1 -0
- package/dist/_chunks/{error-boundary-D-lkwyaD.js → error-boundary-BvRCCmbN.js} +3 -3
- package/dist/_chunks/{error-boundary-D-lkwyaD.js.map → error-boundary-BvRCCmbN.js.map} +1 -1
- package/dist/_chunks/{href-validation-CMc5JRls.js → href-validation-BIrxavIy.js} +74 -2
- package/dist/_chunks/href-validation-BIrxavIy.js.map +1 -0
- package/dist/_chunks/{live-graph-Bx4HodF1.js → live-graph-BXDsdzBv.js} +3 -3
- package/dist/_chunks/{live-graph-Bx4HodF1.js.map → live-graph-BXDsdzBv.js.map} +1 -1
- package/dist/_chunks/{logger-pumCm3Il.js → logger-DDirEsn7.js} +3 -4
- package/dist/_chunks/{logger-pumCm3Il.js.map → logger-DDirEsn7.js.map} +1 -1
- package/dist/_chunks/navigation-root-B00jjGd5.js +233 -0
- package/dist/_chunks/navigation-root-B00jjGd5.js.map +1 -0
- package/dist/_chunks/{segment-context-CjOlyB8Y.js → param-value-C8TNYchQ.js} +2 -33
- package/dist/_chunks/param-value-C8TNYchQ.js.map +1 -0
- package/dist/_chunks/{poison-scan-BAxfTT5L.js → poison-scan-BoDLgbix.js} +2 -2
- package/dist/_chunks/{poison-scan-BAxfTT5L.js.map → poison-scan-BoDLgbix.js.map} +1 -1
- package/dist/_chunks/{router-ref-BzqbPwYC.js → router-ref-8gr8qsxN.js} +2 -2
- package/dist/_chunks/{router-ref-BzqbPwYC.js.map → router-ref-8gr8qsxN.js.map} +1 -1
- package/dist/_chunks/{rsc-cache-key-DD0fl_-s.js → rsc-cache-key-ClUiXQnK.js} +2 -2
- package/dist/_chunks/{rsc-cache-key-DD0fl_-s.js.map → rsc-cache-key-ClUiXQnK.js.map} +1 -1
- package/dist/_chunks/{scanner-BRIOmHE2.js → scanner-tdFPvDYi.js} +174 -7
- package/dist/_chunks/scanner-tdFPvDYi.js.map +1 -0
- package/dist/_chunks/segment-context-D9_89u34.js +34 -0
- package/dist/_chunks/segment-context-D9_89u34.js.map +1 -0
- package/dist/_chunks/singleflight-2lUWfcAk.js +54 -0
- package/dist/_chunks/singleflight-2lUWfcAk.js.map +1 -0
- package/dist/_chunks/{ssr-data-Ya2HJPFp.js → ssr-data-BQGhTPAK.js} +2 -17
- package/dist/_chunks/ssr-data-BQGhTPAK.js.map +1 -0
- package/dist/_chunks/{walkers-BU6z9xRV.js → walkers-DNX05dC0.js} +2 -2
- package/dist/_chunks/{walkers-BU6z9xRV.js.map → walkers-DNX05dC0.js.map} +1 -1
- package/dist/adapters/cloudflare-dev.js +1 -1
- package/dist/adapters/cloudflare-kv-cache.js +1 -1
- package/dist/adapters/cloudflare.js +1 -1
- package/dist/adapters/nitro.d.ts +1 -1
- package/dist/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/analyze/crawl-entry.js +2 -2
- package/dist/analyze/graph-command.js +2 -2
- package/dist/cache/index.js +1 -1
- package/dist/cache/singleflight.d.ts +2 -0
- package/dist/cache/singleflight.d.ts.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/client/browser-entry/hydrate.d.ts +21 -15
- package/dist/client/browser-entry/hydrate.d.ts.map +1 -1
- package/dist/client/browser-entry/index.d.ts +4 -3
- package/dist/client/browser-entry/index.d.ts.map +1 -1
- package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
- package/dist/client/browser-entry/router-init.d.ts +17 -1
- package/dist/client/browser-entry/router-init.d.ts.map +1 -1
- package/dist/client/error-boundary.js +1 -1
- package/dist/client/global-context.d.ts +15 -0
- package/dist/client/global-context.d.ts.map +1 -0
- package/dist/client/index.js +138 -35
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal.d.ts +0 -1
- package/dist/client/internal.d.ts.map +1 -1
- package/dist/client/internal.js +206 -55
- package/dist/client/internal.js.map +1 -1
- package/dist/client/link.d.ts.map +1 -1
- package/dist/client/location-search.d.ts +12 -0
- package/dist/client/location-search.d.ts.map +1 -0
- package/dist/client/navigation-api.d.ts.map +1 -1
- package/dist/client/navigation-commit.d.ts +18 -0
- package/dist/client/navigation-commit.d.ts.map +1 -1
- package/dist/client/navigation-context.d.ts +13 -11
- package/dist/client/navigation-context.d.ts.map +1 -1
- package/dist/client/navigation-root.d.ts +47 -108
- package/dist/client/navigation-root.d.ts.map +1 -1
- package/dist/client/navigation-transition.d.ts +136 -0
- package/dist/client/navigation-transition.d.ts.map +1 -0
- package/dist/client/nuqs-adapter.d.ts.map +1 -1
- package/dist/client/params-context.d.ts +4 -5
- package/dist/client/params-context.d.ts.map +1 -1
- package/dist/client/react-root.d.ts +44 -0
- package/dist/client/react-root.d.ts.map +1 -0
- package/dist/client/router-pipeline.d.ts +2 -2
- package/dist/client/router-pipeline.d.ts.map +1 -1
- package/dist/client/router-types.d.ts +12 -2
- package/dist/client/router-types.d.ts.map +1 -1
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +39 -0
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/segment-context.d.ts.map +1 -1
- package/dist/client/segment-outlet.d.ts +25 -14
- package/dist/client/segment-outlet.d.ts.map +1 -1
- package/dist/client/segment-update-context.d.ts +3 -9
- package/dist/client/segment-update-context.d.ts.map +1 -1
- package/dist/client/slot-content-cache-context.d.ts +35 -0
- package/dist/client/slot-content-cache-context.d.ts.map +1 -0
- package/dist/client/ssr-data.d.ts +8 -2
- package/dist/client/ssr-data.d.ts.map +1 -1
- package/dist/client/state.d.ts +0 -15
- package/dist/client/state.d.ts.map +1 -1
- package/dist/client/use-pathname.d.ts +13 -11
- package/dist/client/use-pathname.d.ts.map +1 -1
- package/dist/client/use-search-params.d.ts +13 -13
- package/dist/client/use-search-params.d.ts.map +1 -1
- package/dist/client/use-segment-params.d.ts +18 -68
- package/dist/client/use-segment-params.d.ts.map +1 -1
- package/dist/config-types.d.ts +17 -0
- package/dist/config-types.d.ts.map +1 -1
- package/dist/config-validation.d.ts.map +1 -1
- package/dist/cookies/index.js +1 -1
- package/dist/dev-tools/holding-server.d.ts +15 -10
- package/dist/dev-tools/holding-server.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -43
- package/dist/index.js.map +1 -1
- package/dist/plugins/dev-server.d.ts.map +1 -1
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/shims.d.ts.map +1 -1
- package/dist/plugins/static-build.d.ts +2 -2
- package/dist/plugins/static-build.d.ts.map +1 -1
- package/dist/routing/codegen-write.d.ts.map +1 -1
- package/dist/routing/index.js +2 -2
- package/dist/routing/interception-overlap.d.ts +35 -0
- package/dist/routing/interception-overlap.d.ts.map +1 -0
- package/dist/routing/interception.d.ts.map +1 -1
- package/dist/rsc-runtime/ssr.d.ts +3 -1
- package/dist/rsc-runtime/ssr.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +6 -0
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/csp-nonce.d.ts +45 -0
- package/dist/server/csp-nonce.d.ts.map +1 -0
- package/dist/server/default-status-page.d.ts.map +1 -1
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/flight-scripts.d.ts +5 -2
- package/dist/server/flight-scripts.d.ts.map +1 -1
- package/dist/server/html-injector-core.d.ts +17 -2
- package/dist/server/html-injector-core.d.ts.map +1 -1
- package/dist/server/html-injectors.d.ts +3 -2
- package/dist/server/html-injectors.d.ts.map +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/internal.js +86 -37
- package/dist/server/internal.js.map +1 -1
- package/dist/server/metadata-render.d.ts.map +1 -1
- package/dist/server/node-stream-transforms.d.ts +3 -17
- package/dist/server/node-stream-transforms.d.ts.map +1 -1
- package/dist/server/nuqs-ssr-provider.d.ts +7 -3
- package/dist/server/nuqs-ssr-provider.d.ts.map +1 -1
- package/dist/server/pipeline-phases.d.ts.map +1 -1
- package/dist/server/prebuilt/key-discipline.d.ts +32 -3
- package/dist/server/prebuilt/key-discipline.d.ts.map +1 -1
- package/dist/server/primitives.d.ts.map +1 -1
- package/dist/server/render-utils.d.ts +4 -3
- package/dist/server/render-utils.d.ts.map +1 -1
- package/dist/server/rsc-entry/action-middleware-runner.d.ts.map +1 -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.map +1 -1
- package/dist/server/ssr-bridge-types.d.ts +22 -2
- package/dist/server/ssr-bridge-types.d.ts.map +1 -1
- package/dist/server/ssr-entry.d.ts.map +1 -1
- package/dist/server/ssr-render.d.ts +5 -1
- package/dist/server/ssr-render.d.ts.map +1 -1
- package/dist/server/ssr-wrappers.d.ts +59 -27
- package/dist/server/ssr-wrappers.d.ts.map +1 -1
- package/dist/server/types.d.ts +10 -0
- package/dist/server/types.d.ts.map +1 -1
- package/dist/shims/navigation-rsc.d.ts +21 -0
- package/dist/shims/navigation-rsc.d.ts.map +1 -0
- package/docs/api/30-api-server.mdx +1 -0
- package/docs/api/35-api-typescript.mdx +4 -83
- package/docs/learn/03-fetching-data.mdx +1 -1
- package/docs/learn/{03b-access-control.mdx → 04-access-control.mdx} +2 -17
- package/docs/learn/05-the-flush-point.mdx +175 -0
- package/docs/learn/{05-typed-params.mdx → 06-typed-params.mdx} +1 -1
- package/docs/learn/07-typed-routes.mdx +25 -49
- package/docs/learn/{08-streaming.mdx → 09-streaming.mdx} +1 -7
- package/docs/learn/{10-middleware.mdx → 11-middleware.mdx} +1 -0
- package/package.json +3 -3
- package/src/adapters/nitro.ts +7 -7
- package/src/cache/singleflight.ts +5 -0
- package/src/client/browser-entry/hydrate.ts +54 -104
- package/src/client/browser-entry/index.ts +16 -6
- package/src/client/browser-entry/post-hydration.ts +3 -2
- package/src/client/browser-entry/router-init.ts +84 -33
- package/src/client/global-context.ts +31 -0
- package/src/client/internal.ts +1 -2
- package/src/client/link.tsx +18 -18
- package/src/client/location-search.ts +15 -0
- package/src/client/navigation-api.ts +4 -2
- package/src/client/navigation-commit.ts +48 -2
- package/src/client/navigation-context.ts +25 -37
- package/src/client/navigation-root.tsx +55 -411
- package/src/client/navigation-transition.ts +278 -0
- package/src/client/nuqs-adapter.tsx +4 -5
- package/src/client/params-context.ts +13 -18
- package/src/client/react-root.ts +72 -0
- package/src/client/router-lifecycle.ts +1 -1
- package/src/client/router-pipeline.ts +96 -22
- package/src/client/router-types.ts +12 -2
- package/src/client/router.ts +48 -36
- package/src/client/segment-cache.ts +70 -2
- package/src/client/segment-context.ts +7 -4
- package/src/client/segment-outlet.tsx +41 -86
- package/src/client/segment-update-context.ts +7 -26
- package/src/client/slot-content-cache-context.ts +43 -0
- package/src/client/ssr-data.ts +8 -2
- package/src/client/state.ts +0 -26
- package/src/client/use-pathname.ts +21 -31
- package/src/client/use-search-params.ts +31 -29
- package/src/client/use-segment-params.ts +27 -126
- package/src/config-types.ts +17 -0
- package/src/config-validation.ts +17 -0
- package/src/dev-tools/holding-server.ts +23 -12
- package/src/index.ts +26 -11
- package/src/plugins/dev-server.ts +9 -12
- package/src/plugins/entries.ts +3 -0
- package/src/plugins/shims.ts +8 -7
- package/src/plugins/static-build.ts +9 -5
- package/src/react-canary.d.ts +2 -0
- package/src/routing/codegen-write.ts +2 -0
- package/src/routing/interception-overlap.ts +141 -0
- package/src/routing/interception.ts +118 -5
- package/src/rsc-runtime/ssr.ts +3 -2
- package/src/server/als-registry.ts +6 -0
- package/src/server/csp-nonce.ts +70 -0
- package/src/server/default-status-page.ts +1 -0
- package/src/server/deny-renderer.ts +7 -3
- package/src/server/flight-scripts.ts +9 -4
- package/src/server/html-injector-core.ts +26 -9
- package/src/server/html-injectors.ts +8 -8
- package/src/server/metadata-render.ts +26 -4
- package/src/server/node-stream-transforms.ts +7 -20
- package/src/server/nuqs-ssr-provider.tsx +8 -7
- package/src/server/pipeline-phases.ts +5 -0
- package/src/server/prebuilt/key-discipline.ts +82 -13
- package/src/server/prebuilt-runtime.ts +2 -2
- package/src/server/primitives.ts +4 -4
- package/src/server/render-utils.ts +8 -4
- package/src/server/rsc-entry/action-middleware-runner.ts +7 -0
- package/src/server/rsc-entry/error-renderer.ts +5 -2
- package/src/server/rsc-entry/index.ts +8 -0
- package/src/server/rsc-entry/ssr-renderer.ts +11 -4
- package/src/server/ssr-bridge-types.ts +22 -2
- package/src/server/ssr-entry.ts +35 -28
- package/src/server/ssr-render.ts +13 -4
- package/src/server/ssr-wrappers.tsx +81 -61
- package/src/server/types.ts +10 -0
- package/src/shared/slot-params.ts +3 -4
- package/src/shims/navigation-rsc.ts +47 -0
- package/dist/_chunks/build-manifest-DWppEdLB.js.map +0 -1
- package/dist/_chunks/cache-api-CQeYzA5g.js.map +0 -1
- package/dist/_chunks/convention-lint-DO10_pVl.js.map +0 -1
- package/dist/_chunks/href-validation-CMc5JRls.js.map +0 -1
- package/dist/_chunks/scanner-BRIOmHE2.js.map +0 -1
- package/dist/_chunks/segment-context-CjOlyB8Y.js.map +0 -1
- package/dist/_chunks/slot-params-BCTmZkQB.js +0 -76
- package/dist/_chunks/slot-params-BCTmZkQB.js.map +0 -1
- package/dist/_chunks/ssr-data-Ya2HJPFp.js.map +0 -1
- package/dist/_chunks/use-segment-params-DzTBpkvj.js +0 -398
- package/dist/_chunks/use-segment-params-DzTBpkvj.js.map +0 -1
- package/docs/learn/04-loading-states.mdx +0 -67
- package/docs/learn/04b-the-flush-point.mdx +0 -115
- package/docs/learn/12-client-navigation.mdx +0 -176
- package/docs/learn/13-configuration.mdx +0 -166
- package/docs/more/01-advanced-routing.mdx +0 -344
- package/docs/more/02-advanced-forms.mdx +0 -137
- package/docs/more/03-coming-from-nextjs.mdx +0 -186
- package/docs/more/04-metadata-and-fonts.mdx +0 -193
- package/docs/more/04b-mdx.mdx +0 -229
- package/docs/more/05-content-collections.mdx +0 -90
- package/docs/more/06-instrumentation.mdx +0 -214
- package/docs/more/07-security.mdx +0 -129
- package/docs/more/08-developer-experience.mdx +0 -134
- package/docs/more/40-why-timber.mdx +0 -50
- package/docs/more/41-timber-vs-nextjs.mdx +0 -81
- package/docs/more/42-timber-vs-others.mdx +0 -68
- package/docs/more/50-ai-agent-instructions.mdx +0 -171
- /package/docs/learn/{06-forms-and-actions.mdx → 08-forms-and-actions.mdx} +0 -0
- /package/docs/learn/{09-caching.mdx → 10-caching.mdx} +0 -0
- /package/docs/learn/{11-error-handling.mdx → 12-error-handling.mdx} +0 -0
- /package/docs/learn/{14-deploying.mdx → 13-deploying.mdx} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: 'API: TypeScript'
|
|
3
|
-
description: 'Generated route types, $segment
|
|
3
|
+
description: 'Generated route types, $segment module exports, context types, and the Routes interface.'
|
|
4
4
|
slug: 'api-typescript'
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -32,95 +32,16 @@ export type AllSegmentParams = { /* union of all route params */ };
|
|
|
32
32
|
|
|
33
33
|
Types reference your `app/schema.ts` codecs directly, so they stay in sync automatically.
|
|
34
34
|
|
|
35
|
-
## `$segment`
|
|
35
|
+
## `$segment` Module
|
|
36
36
|
|
|
37
|
-
Each route directory gets a generated `$segment` module
|
|
38
|
-
|
|
39
|
-
```tsx
|
|
40
|
-
// app/products/[id]/page.tsx
|
|
41
|
-
import { SEGMENT_PATH } from './$segment';
|
|
42
|
-
import { getSegmentParams } from '@timber-js/app/server';
|
|
43
|
-
|
|
44
|
-
export default async function ProductPage() {
|
|
45
|
-
const { id } = getSegmentParams(SEGMENT_PATH);
|
|
46
|
-
// id is typed from your schema codec (e.g., number for codec.integer)
|
|
47
|
-
}
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
### Exports
|
|
37
|
+
Each route directory gets a generated `$segment` module, resolved via TypeScript's `rootDirs` (setup and usage in [Typed Routes](/docs/typed-routes)).
|
|
51
38
|
|
|
52
39
|
| Export | Type | Description |
|
|
53
40
|
| -------------- | ------------------- | ------------------------------------------------------------- |
|
|
54
41
|
| `ROUTE` | String literal | URL route pattern, groups stripped (e.g., `'/products/[id]'`) |
|
|
55
42
|
| `SEGMENT_PATH` | String literal | Full tree path including groups and slots |
|
|
56
43
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
Add `rootDirs` so TypeScript can resolve `$segment` imports:
|
|
60
|
-
|
|
61
|
-
```jsonc
|
|
62
|
-
{
|
|
63
|
-
"compilerOptions": {
|
|
64
|
-
"rootDirs": [".", ".timber/types"]
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## Typed `<Link>`
|
|
70
|
-
|
|
71
|
-
`<Link>` validates `href` against the route map:
|
|
72
|
-
|
|
73
|
-
```tsx
|
|
74
|
-
import { Link } from '@timber-js/app/client';
|
|
75
|
-
|
|
76
|
-
<Link href="/about">About</Link> // ✓
|
|
77
|
-
<Link href="/nonexistent">Bad</Link> // ✗ TypeScript error
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
For dynamic routes, pass `segmentParams`:
|
|
81
|
-
|
|
82
|
-
```tsx
|
|
83
|
-
import { Link } from '@timber-js/app/client';
|
|
84
|
-
|
|
85
|
-
<Link href="/products/[id]" segmentParams={{ id: 42 }}>
|
|
86
|
-
Product
|
|
87
|
-
</Link>
|
|
88
|
-
// → <a href="/products/42">Product</a>
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
## Typed Segment Params
|
|
92
|
-
|
|
93
|
-
With `SEGMENT_PATH` from `$segment`, param types are narrowed from your `app/schema.ts` codecs:
|
|
94
|
-
|
|
95
|
-
```tsx
|
|
96
|
-
// app/products/[id]/page.tsx
|
|
97
|
-
import { SEGMENT_PATH } from './$segment';
|
|
98
|
-
import { getSegmentParams } from '@timber-js/app/server';
|
|
99
|
-
|
|
100
|
-
export default async function ProductPage() {
|
|
101
|
-
const { id } = getSegmentParams(SEGMENT_PATH);
|
|
102
|
-
// id: number (from z.coerce.number().int().positive() in schema.ts)
|
|
103
|
-
}
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
Without `SEGMENT_PATH`, params are `Partial<AllSegmentParams>` — all optional.
|
|
107
|
-
|
|
108
|
-
## Typed Search Params
|
|
109
|
-
|
|
110
|
-
Search params are typed by the definition you create and import — there is no convention file:
|
|
111
|
-
|
|
112
|
-
```ts
|
|
113
|
-
// app/products/search-params.ts — any module you like
|
|
114
|
-
import { defineSearchParams } from '@timber-js/app/search-params';
|
|
115
|
-
import { z } from 'zod/v4';
|
|
116
|
-
|
|
117
|
-
export const searchParams = defineSearchParams({
|
|
118
|
-
page: z.coerce.number().default(1),
|
|
119
|
-
sort: z.enum(['price', 'name']).default('price'),
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
// In page.tsx — searchParams.get() returns { page: number; sort: 'price' | 'name' }
|
|
123
|
-
```
|
|
44
|
+
`<Link>` `href` validation and `segmentParams`/`searchParams` typing are described in [Typed Routes](/docs/typed-routes); the param and search-param definitions they draw from are in [Typed Params](/docs/typed-params).
|
|
124
45
|
|
|
125
46
|
## Context Types
|
|
126
47
|
|
|
@@ -76,7 +76,7 @@ export default async function ProductPage() {
|
|
|
76
76
|
}
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
`deny(404)` sends a real HTTP 404 — not a 200 with an error message. Search engines, CDNs, and `curl -f` all see the correct status code.
|
|
79
|
+
`deny(404)` sends a real HTTP 404 — not a 200 with an error message. Search engines, CDNs, and `curl -f` all see the correct status code. Other status codes, status-code files, and error boundaries are covered in [Error Handling](/docs/error-handling).
|
|
80
80
|
|
|
81
81
|
For typed params (numbers, UUIDs, enums), define codecs in `app/schema.ts`. See [Typed Params](/docs/typed-params).
|
|
82
82
|
|
|
@@ -89,15 +89,7 @@ export default async function access() {
|
|
|
89
89
|
|
|
90
90
|
Access runs root-to-leaf. If the root gate fails, deeper gates never execute.
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
`deny()` produces the correct HTTP status code and renders the matching status file:
|
|
95
|
-
|
|
96
|
-
```ts
|
|
97
|
-
deny(); // 403 Forbidden (default)
|
|
98
|
-
deny(404); // 404 Not Found
|
|
99
|
-
deny(401); // 401 Unauthorized
|
|
100
|
-
```
|
|
92
|
+
`deny()` defaults to `403`; pass a status code (`deny(404)`, `deny(401)`) to change it. How the status code becomes a rendered page is covered in [Error Handling](/docs/error-handling).
|
|
101
93
|
|
|
102
94
|
## Slot Degradation
|
|
103
95
|
|
|
@@ -120,11 +112,4 @@ When access denies, that segment's `layout.tsx` never runs — `denied.tsx` rend
|
|
|
120
112
|
|
|
121
113
|
## Middleware vs Access
|
|
122
114
|
|
|
123
|
-
|
|
124
|
-
| ------------- | -------------------------------------- | --------------------------------- |
|
|
125
|
-
| Runs | Before rendering | Inside React tree |
|
|
126
|
-
| Purpose | Lightweight checks, redirects, headers | Auth, data gating |
|
|
127
|
-
| `React.cache` | Not available | Active — shares scope with layout |
|
|
128
|
-
| Slots | N/A | Graceful degradation |
|
|
129
|
-
|
|
130
|
-
Use `middleware.ts` for headers, redirects, and cache warming. Use `access.ts` for auth — it has access to `React.cache` and supports slot degradation.
|
|
115
|
+
`access.ts` runs inside the React tree, so it has `React.cache` and supports slot degradation. `middleware.ts` runs before rendering, for headers, redirects, and cache warming. The full comparison is in [Middleware](/docs/middleware).
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'The Flush Point'
|
|
3
|
+
description: 'When timber commits the HTTP status code, why there is no loading.tsx, and how Early Hints keep it fast.'
|
|
4
|
+
slug: 'the-flush-point'
|
|
5
|
+
# notAI: true
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# The Flush Point
|
|
9
|
+
|
|
10
|
+
The flush point is when the framework sends the first byte of HTML to the browser and commits the HTTP status code. In a traditional PHP website, this happens all at once.
|
|
11
|
+
|
|
12
|
+
But modern web frameworks, and notably react server components, have embraced a streaming architecture. Allowing you to send content _after_ the flush point.
|
|
13
|
+
|
|
14
|
+
While this is ultimately incredibly powerful, it brings forth confusion because contextually, code can have different side effects depending on _when_ you call it.
|
|
15
|
+
|
|
16
|
+
On a next.js app, if I call `notFound()` inside of a `page.tsx` that has a sibling `loading.tsx` – my status code will return `200`. By obfuscating the flush point, we've actually just made developer clarity murky.
|
|
17
|
+
|
|
18
|
+
So timber works differently. At no point does timber flush early, unless _you_ (the developer), choose to place a `<Suspense>` boundary. Often this will end up being below your `page.tsx` level, so you'll still have access to controlling and sending proper status codes.
|
|
19
|
+
|
|
20
|
+
## You Might Not Need A `loading.tsx`
|
|
21
|
+
|
|
22
|
+
Most React frameworks push you towards building page-level skeletons or loading states. Though it's entirely trivial to build out that experience with timber, we nudge you away from that for several reasons.
|
|
23
|
+
|
|
24
|
+
This is not referring to loading states on things like mutations (like submitting a form) or other actions. This is referring to initial (server) or secondary (client) page loading.
|
|
25
|
+
|
|
26
|
+
This is how we as frontend engineers make browsing the web a less anxiety-inducing experience.
|
|
27
|
+
|
|
28
|
+
### Loading States Are an Extra Class of UI to Maintain
|
|
29
|
+
|
|
30
|
+
You have to design loading states. Decide between spinners and skeletons, and then mimic your UI in the skeleton.
|
|
31
|
+
|
|
32
|
+
### Loading States Cause Content Layout Shift
|
|
33
|
+
|
|
34
|
+
Loading states are fixed and content is most often dynamic both in size (think text length) and count (think number of rows). So even if you work incredibly hard to avoid major CLS, you'll invariably end up with CLS somewhere.
|
|
35
|
+
|
|
36
|
+
### Loading States Are a Flash of UI
|
|
37
|
+
|
|
38
|
+
Loading states are inherently temporary, so this adds flashes of content. This causes anxiety as the page spasms before finally reaching an undetermined final state. The user is left in a daze wondering if the page is finally ready.
|
|
39
|
+
|
|
40
|
+
Every possible "finite state" of UI is another that you and the user have to keep in context. You'll be _surprised_ how much simpler a website feels when a page has one major representation, vs several.
|
|
41
|
+
|
|
42
|
+
### Loading States Hide Prior Context
|
|
43
|
+
|
|
44
|
+
Go to an old PHP site and click a URL. The _browser_ shows a loading state, but the prior page remains visible. The new page only becomes visible once it is ready. There's little advantage to hiding the past context while the user waits for the new context to load.
|
|
45
|
+
|
|
46
|
+
### timber Includes a Global Route-Based Loading State
|
|
47
|
+
|
|
48
|
+
On every route change, timber triggers a global page-level toploader loading state. If you want to add a secondary loading state that dims the prior content, you can easily do so.
|
|
49
|
+
|
|
50
|
+
### Loading States Break JavaScript Disabled Requests
|
|
51
|
+
|
|
52
|
+
`curl` a page with a `loading.tsx` and you'll get back a shell, not the content. Though people downsell the cost, it genuinely does hinder both SEO and agentic requests.
|
|
53
|
+
|
|
54
|
+
Leaning on the web as intended is always better than assuming _all_ consumers have JavaScript enabled. Plus, sticking JavaScript in between your _hot_ rendering path is _inherently_ slower. It's just not necessary when you adopt a comprehensive web architecture like React Server Components.
|
|
55
|
+
|
|
56
|
+
### Loading States Break Status Codes
|
|
57
|
+
|
|
58
|
+
Loading states flush before the content is even fetched. This feels _faster_, but you are flushing before you know if the user is authenticated, the data exists, or any other class of issue. By flushing immediately, you are forced to send a 200 and lean on JavaScript-injected meta tags to signify errors.
|
|
59
|
+
|
|
60
|
+
This breaks the contract of the web for any consumer that isn't a human. Use real status codes, you will gain downstream benefits from doing so.
|
|
61
|
+
|
|
62
|
+
### Most Loading States Paper Over a Bad Data Architecture
|
|
63
|
+
|
|
64
|
+
People often use loading states because their database is poorly optimized, or 50ms away from their rendering server, or otherwise. With a well designed data infrastructure, you can often fetch your data plenty fast to render your entire page in < 100ms.
|
|
65
|
+
|
|
66
|
+
And when you can't, you can stream and flush those rare pages trivially.
|
|
67
|
+
|
|
68
|
+
### Loading States Still Have Value, but More-So as Opt-In Secondary or Tertiary Content
|
|
69
|
+
|
|
70
|
+
To add a loading state as a small sub-section of a page is trivial – just wrap your component in `<Suspense>` and you can defer slower, lower priority content.
|
|
71
|
+
|
|
72
|
+
If you decide you want a full page level loading state, you can wrap your layout or page in `<Suspense>` on a case by case basis. No `loading.tsx` needed.
|
|
73
|
+
|
|
74
|
+
--------
|
|
75
|
+
|
|
76
|
+
A `loading.tsx` forces your flush point up ever so slightly, but you give up all the things above. Instead, timber asks you to place `<Suspense>` boundaries yourself – thereby _explicitly_ opting into the flush point.
|
|
77
|
+
|
|
78
|
+
Now you get a calm, performant website that doesn't have immense content-layout-shift, doesn't hide prior content during loading, and can return proper status codes. The `loading.tsx` seems innocent enough, but its cost both cognitively and architecturally is high.
|
|
79
|
+
|
|
80
|
+
------
|
|
81
|
+
|
|
82
|
+
AI below..
|
|
83
|
+
|
|
84
|
+
## The Problem
|
|
85
|
+
|
|
86
|
+
Most streaming frameworks send a `200 OK` immediately and figure out the real outcome later:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
Request → 200 OK → ... render ... → oh, it's a 404
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
By the time the server discovers the page doesn't exist, the status code is already sent. The client sees `200`. Search engines see `200`. CDNs cache it as `200`.
|
|
93
|
+
|
|
94
|
+
This breaks search engines (deleted pages never deindex), CDNs (404s get cached as successes), monitoring (zero errors while users see broken pages), and `curl`/scripts (`curl -f` won't detect the failure).
|
|
95
|
+
|
|
96
|
+
## timber's Solution
|
|
97
|
+
|
|
98
|
+
timber holds the response until it knows the real outcome:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
Request arrives
|
|
102
|
+
→ Route matched
|
|
103
|
+
→ proxy.ts runs
|
|
104
|
+
→ middleware.ts runs
|
|
105
|
+
→ access.ts runs
|
|
106
|
+
→ React shell renders (onShellReady)
|
|
107
|
+
→ ✓ Status code committed ← flush point
|
|
108
|
+
→ Shell HTML sent to browser
|
|
109
|
+
→ Suspense boundaries stream in
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The status code commits when **all three** conditions are met:
|
|
113
|
+
|
|
114
|
+
1. Middleware completed without returning a response
|
|
115
|
+
2. All access checks passed (or denied with a real status code)
|
|
116
|
+
3. React's `onShellReady` fired — the synchronous shell rendered without error
|
|
117
|
+
|
|
118
|
+
A missing page returns a real `404`. A failed auth check returns a real `403`. A redirect returns a real `302`. The HTTP layer tells the truth.
|
|
119
|
+
|
|
120
|
+
## Before and After the Flush
|
|
121
|
+
|
|
122
|
+
**Before the flush** (blocking):
|
|
123
|
+
|
|
124
|
+
- `proxy.ts` — global request processing
|
|
125
|
+
- `middleware.ts` — route-level request processing
|
|
126
|
+
- `access.ts` — authorization gates
|
|
127
|
+
- Synchronous component rendering (the shell)
|
|
128
|
+
|
|
129
|
+
**After the flush** (streaming):
|
|
130
|
+
|
|
131
|
+
- `<Suspense>` boundaries resolve and stream in
|
|
132
|
+
- Slow data loads complete
|
|
133
|
+
- The page progressively fills in
|
|
134
|
+
|
|
135
|
+
``` leading="none"
|
|
136
|
+
┌─────── flush point
|
|
137
|
+
│
|
|
138
|
+
▼
|
|
139
|
+
├────────┤──────────────────────────┤
|
|
140
|
+
blocking streaming
|
|
141
|
+
(shell) (suspense boundaries)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Everything before the flush determines the status code. Everything after streams progressively. You control the boundary by choosing what goes inside `<Suspense>` and what doesn't — see [Streaming](/docs/streaming) for how to place those boundaries.
|
|
145
|
+
|
|
146
|
+
## Pages Work Without JavaScript
|
|
147
|
+
|
|
148
|
+
Because timber holds the flush until the shell is complete, the browser receives a fully-formed HTML document. Content is visible, links work as standard `<a>` tags, and forms submit as standard POSTs. JavaScript adds client-side navigation, interactive components, and streaming updates — but the page works without it.
|
|
149
|
+
|
|
150
|
+
This isn't a special mode. It's how timber works by default.
|
|
151
|
+
|
|
152
|
+
## Early Hints
|
|
153
|
+
|
|
154
|
+
timber doesn't make you wait for assets while the shell renders. At route-match time — before middleware runs — timber sends `103 Early Hints` with CSS, JS, and font URLs based on the build manifest. The browser starts downloading assets while the server is still working:
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
103 Early Hints
|
|
158
|
+
Link: </styles/main.css>; rel=preload; as=style
|
|
159
|
+
Link: </chunks/page-abc.js>; rel=modulepreload
|
|
160
|
+
|
|
161
|
+
... middleware runs, shell renders ...
|
|
162
|
+
|
|
163
|
+
200 OK
|
|
164
|
+
<html>...
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
You get the correctness of a held flush with the performance of early resource loading.
|
|
168
|
+
|
|
169
|
+
## How to Think About It
|
|
170
|
+
|
|
171
|
+
Primary content — the data that defines whether a page exists, who can see it, and what it contains — should load before the flush. Put it in your components directly.
|
|
172
|
+
|
|
173
|
+
Secondary content — recommendations, activity feeds, analytics widgets — can load after the flush. Wrap it in `<Suspense>`.
|
|
174
|
+
|
|
175
|
+
The flush point is the dividing line between "what the page _is_" and "what the page _also shows_."
|
|
@@ -52,7 +52,7 @@ Every dynamic segment key must be unique across segment types (dynamic, catch-al
|
|
|
52
52
|
|
|
53
53
|
### Type Narrowing with `$segment`
|
|
54
54
|
|
|
55
|
-
Each route directory gets a generated `./$segment`
|
|
55
|
+
Each route directory gets a generated `./$segment` module that exports `SEGMENT_PATH` — a string literal typed to the segment's tree path (how it's generated and the `tsconfig` setup are in [Typed Routes](/docs/typed-routes)). Pass it to `getSegmentParams()` for exact types:
|
|
56
56
|
|
|
57
57
|
```tsx title="app/products/[id]/page.tsx" use="schema"
|
|
58
58
|
import { SEGMENT_PATH } from './$segment';
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: 'Typed Routes'
|
|
3
|
-
description: 'Build-time route types — <Link> validates href,
|
|
3
|
+
description: 'Build-time route types — <Link> validates href, $segment narrows params to the current route.'
|
|
4
4
|
slug: 'typed-routes'
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Typed Routes
|
|
8
8
|
|
|
9
|
-
timber.js generates route types at build time. `<Link>` type-checks `href` against your actual route tree
|
|
9
|
+
timber.js generates route types at build time. `<Link>` type-checks `href` against your actual route tree, and every route directory gets a `$segment` module that narrows params to that route. This page covers what the codegen gives you; defining and reading params is covered in [Typed Params](/docs/typed-params).
|
|
10
10
|
|
|
11
11
|
## How It Works
|
|
12
12
|
|
|
@@ -53,9 +53,26 @@ Or use an interpolated href directly:
|
|
|
53
53
|
|
|
54
54
|
Both forms are type-safe. With the pattern form (`/products/[id]`), `segmentParams` is required and typed. With the interpolated form, `segmentParams` is forbidden.
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
### Search params on `<Link>`
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
Build search params from a `defineSearchParams` definition. `buildSearchParams` type-checks the values and applies the definition's codecs and URL-key aliases:
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import { searchParams } from './search-params';
|
|
62
|
+
|
|
63
|
+
<Link
|
|
64
|
+
href="/products/[id]"
|
|
65
|
+
segmentParams={{ id }}
|
|
66
|
+
searchParams={searchParams.buildSearchParams({ sort: 'newest', q: 'boots' })}
|
|
67
|
+
/>
|
|
68
|
+
// → /products/42?sort=newest&search=boots (q is aliased to search)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
A plain object also works as an untyped escape hatch for one-off params with no definition — but it skips codecs and aliases, so do not use it for keys a definition owns.
|
|
72
|
+
|
|
73
|
+
## `$segment` and `SEGMENT_PATH`
|
|
74
|
+
|
|
75
|
+
Each route directory gets a generated `$segment` module. It exports `SEGMENT_PATH`, a string literal typed to that directory's position in the route tree. Passing it to `getSegmentParams()` / `useSegmentParams()` narrows the result to exactly the params that exist at that depth:
|
|
59
76
|
|
|
60
77
|
```tsx
|
|
61
78
|
// app/products/[id]/page.tsx
|
|
@@ -68,7 +85,9 @@ export default async function ProductPage() {
|
|
|
68
85
|
}
|
|
69
86
|
```
|
|
70
87
|
|
|
71
|
-
Without `SEGMENT_PATH`, params are
|
|
88
|
+
Without `SEGMENT_PATH`, params are `Partial<AllSegmentParams>` — every param optional.
|
|
89
|
+
|
|
90
|
+
`$segment` also exports `ROUTE`, the URL pattern with route groups stripped (e.g. `'/products/[id]'`).
|
|
72
91
|
|
|
73
92
|
### tsconfig.json
|
|
74
93
|
|
|
@@ -82,51 +101,8 @@ Add `rootDirs` so TypeScript can resolve `$segment` imports:
|
|
|
82
101
|
}
|
|
83
102
|
```
|
|
84
103
|
|
|
85
|
-
## Typed Search Params
|
|
86
|
-
|
|
87
|
-
Define search params with `defineSearchParams()` in any module and import it — there is no convention file. Search params are then fully typed throughout the stack:
|
|
88
|
-
|
|
89
|
-
```ts title="app/products/search-params.ts"
|
|
90
|
-
import { defineSearchParams, withDefault, withUrlKey } from '@timber-js/app/search-params';
|
|
91
|
-
import { parseAsInteger, parseAsStringEnum } from 'nuqs';
|
|
92
|
-
import { z } from 'zod/v4';
|
|
93
|
-
|
|
94
|
-
export const searchParams = defineSearchParams({
|
|
95
|
-
page: withDefault(parseAsInteger, 1),
|
|
96
|
-
q: withUrlKey(z.string().nullable().default(null), 'search'),
|
|
97
|
-
category: z.string().nullable().default(null),
|
|
98
|
-
sort: withDefault(parseAsStringEnum(['price-asc', 'price-desc', 'newest']), 'newest'),
|
|
99
|
-
});
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
```tsx title="app/products/page.tsx"
|
|
103
|
-
import { searchParams } from './search-params';
|
|
104
|
-
|
|
105
|
-
export default async function ProductsPage() {
|
|
106
|
-
const { page, q, category, sort } = await searchParams.get();
|
|
107
|
-
// page: number, q: string | null, category: string | null, sort: 'price-asc' | ...
|
|
108
|
-
}
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
Pass them to `<Link>` by building them from the definition. `buildSearchParams` type-checks the values and applies the definition's codecs and URL-key aliases:
|
|
112
|
-
|
|
113
|
-
```tsx
|
|
114
|
-
import { searchParams } from './search-params';
|
|
115
|
-
|
|
116
|
-
<Link
|
|
117
|
-
href="/products/[id]"
|
|
118
|
-
segmentParams={{ id }}
|
|
119
|
-
searchParams={searchParams.buildSearchParams({ sort: 'newest', q: 'boots' })}
|
|
120
|
-
/>
|
|
121
|
-
// → /products/42?sort=newest&search=boots (q is aliased to search)
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
A plain object also works as an untyped escape hatch for one-off params with no definition — but it skips codecs and aliases, so do not use it for keys a definition owns.
|
|
125
|
-
|
|
126
|
-
See [Typed Params](/docs/typed-params) for the full search params API.
|
|
127
|
-
|
|
128
104
|
## Codegen
|
|
129
105
|
|
|
130
106
|
Types are generated during `timber dev` (on startup and when routes change) and `timber build`. The generated `.timber/routes.d.ts` is gitignored — it's a build artifact. Run `timber dev` once to get types for your editor.
|
|
131
107
|
|
|
132
|
-
For the
|
|
108
|
+
For the generated type shapes and context types (`RouteContext`, `MiddlewareContext`, `AccessContext`), see the [TypeScript API Reference](/docs/api-typescript).
|
|
@@ -74,9 +74,7 @@ The product loads before the flush — if it doesn't exist, the server sends a r
|
|
|
74
74
|
|
|
75
75
|
The rule: if missing content means the page shouldn't exist, don't stream it.
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
There is no `loading.tsx` convention. timber does not insert Suspense boundaries for you. If you want a loading state, you place `<Suspense>` explicitly. This keeps you in control of what streams and what blocks.
|
|
77
|
+
There is no `loading.tsx` convention — timber never inserts Suspense boundaries for you. See [The Flush Point](/docs/the-flush-point) for why.
|
|
80
78
|
|
|
81
79
|
## The Layout Suspense Footgun
|
|
82
80
|
|
|
@@ -140,7 +138,3 @@ When `deny()` or `redirect()` fires inside a `<Suspense>` boundary:
|
|
|
140
138
|
`deferSuspenseFor` extends the window where signals can be promoted.
|
|
141
139
|
|
|
142
140
|
**The rule:** don't rely on Suspense-wrapped content to control the status code. If content must drive the status code, fetch it outside `<Suspense>`. Signal promotion is a safety net, not a guarantee.
|
|
143
|
-
|
|
144
|
-
## Early Hints (103)
|
|
145
|
-
|
|
146
|
-
Before the flush, timber sends 103 Early Hints with CSS, fonts, and JS URLs. These fire at route-match time — before middleware runs — based on the build manifest. The browser starts fetching resources while the server is still working.
|
|
@@ -83,6 +83,7 @@ export default async function middleware(ctx: MiddlewareContext) {
|
|
|
83
83
|
ctx.segmentParams; // Route params (already coerced by schema.ts)
|
|
84
84
|
ctx.headers; // Response headers (writable)
|
|
85
85
|
ctx.requestHeaders; // Request headers (injectable)
|
|
86
|
+
ctx.nonce; // Per-request CSP nonce (see Security → Content Security Policy)
|
|
86
87
|
}
|
|
87
88
|
```
|
|
88
89
|
|
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.198",
|
|
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",
|
|
@@ -165,8 +165,8 @@
|
|
|
165
165
|
"@vitejs/plugin-react": "^6.1.0",
|
|
166
166
|
"@vitejs/plugin-rsc": ">=0.5.28",
|
|
167
167
|
"nuqs": "^2.0.0",
|
|
168
|
-
"react": "19.
|
|
169
|
-
"react-dom": "19.
|
|
168
|
+
"react": "19.3.0-canary-f789f203-20260825",
|
|
169
|
+
"react-dom": "19.3.0-canary-f789f203-20260825",
|
|
170
170
|
"satteri": "^0.9.5",
|
|
171
171
|
"vite": "8.2.1",
|
|
172
172
|
"vite-plugin-satteri": "^0.2.15",
|
package/src/adapters/nitro.ts
CHANGED
|
@@ -5,23 +5,23 @@
|
|
|
5
5
|
// compression, graceful shutdown, static file serving, and platform quirks.
|
|
6
6
|
// See design/11-platform.md and design/25-production-deployments.md.
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import type { TimberPlatformAdapter, TimberConfig } from './types.ts';
|
|
11
|
-
import { generateCompressModule } from './compress-module.ts';
|
|
12
|
-
import { adapterBase, IMMUTABLE_CACHE, generateHeadersFile } from './shared.ts';
|
|
8
|
+
import { cp, glob, readFile, writeFile } from 'node:fs/promises';
|
|
9
|
+
import { basename, dirname, join } from 'node:path';
|
|
13
10
|
import {
|
|
14
|
-
runSharedBuildSteps,
|
|
15
11
|
buildStaticDirectoryOutput,
|
|
16
12
|
findUnhashedStaticFiles,
|
|
13
|
+
runSharedBuildSteps,
|
|
17
14
|
writeHeadersFile,
|
|
18
15
|
} from './build-output-helper.ts';
|
|
19
|
-
import {
|
|
16
|
+
import { generateCompressModule } from './compress-module.ts';
|
|
17
|
+
import { LOCALLY_PREVIEWABLE, PRESET_CONFIGS, type NitroPreset } from './nitro-presets.ts';
|
|
20
18
|
import {
|
|
21
19
|
generatePreviewScript,
|
|
22
20
|
generateSendResponseModule,
|
|
23
21
|
spawnNitroPreview,
|
|
24
22
|
} from './nitro-preview.ts';
|
|
23
|
+
import { IMMUTABLE_CACHE } from './shared.ts';
|
|
24
|
+
import type { TimberConfig, TimberPlatformAdapter } from './types.ts';
|
|
25
25
|
|
|
26
26
|
// NitroPreset is part of the public adapter API (NitroAdapterOptions.preset),
|
|
27
27
|
// so it stays importable from this module.
|
|
@@ -18,6 +18,8 @@ export interface SingleflightOptions {
|
|
|
18
18
|
|
|
19
19
|
export interface Singleflight {
|
|
20
20
|
do<T>(key: string, fn: (signal: AbortSignal) => Promise<T>): Promise<T>;
|
|
21
|
+
/** Return the in-flight promise for a key, or undefined if none. */
|
|
22
|
+
get(key: string): Promise<unknown> | undefined;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
/**
|
|
@@ -80,5 +82,8 @@ export function createSingleflight(opts?: SingleflightOptions): Singleflight {
|
|
|
80
82
|
inflight.set(key, tracked);
|
|
81
83
|
return tracked as Promise<T>;
|
|
82
84
|
},
|
|
85
|
+
get(key: string): Promise<unknown> | undefined {
|
|
86
|
+
return inflight.get(key);
|
|
87
|
+
},
|
|
83
88
|
};
|
|
84
89
|
}
|