@timber-js/app 0.2.0-alpha.195 → 0.2.0-alpha.197
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-bE3H5Bjr.js} +3 -3
- package/dist/_chunks/{cli-check-BVthpfLS.js.map → cli-check-bE3H5Bjr.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-n3RJLgww.js} +2 -2
- package/dist/_chunks/{convention-lint-DO10_pVl.js.map → convention-lint-n3RJLgww.js.map} +1 -1
- 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 +139 -36
- 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/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 +25 -19
- 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/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/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,115 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: 'The Flush Point'
|
|
3
|
-
description: 'When timber commits the HTTP status code and why it matters.'
|
|
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
|
-
------
|
|
21
|
-
|
|
22
|
-
AI below..
|
|
23
|
-
|
|
24
|
-
## The Problem
|
|
25
|
-
|
|
26
|
-
Most streaming frameworks send a `200 OK` immediately and figure out the real outcome later:
|
|
27
|
-
|
|
28
|
-
```
|
|
29
|
-
Request → 200 OK → ... render ... → oh, it's a 404
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
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`.
|
|
33
|
-
|
|
34
|
-
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).
|
|
35
|
-
|
|
36
|
-
## timber's Solution
|
|
37
|
-
|
|
38
|
-
timber holds the response until it knows the real outcome:
|
|
39
|
-
|
|
40
|
-
```
|
|
41
|
-
Request arrives
|
|
42
|
-
→ Route matched
|
|
43
|
-
→ proxy.ts runs
|
|
44
|
-
→ middleware.ts runs
|
|
45
|
-
→ access.ts runs
|
|
46
|
-
→ React shell renders (onShellReady)
|
|
47
|
-
→ ✓ Status code committed ← flush point
|
|
48
|
-
→ Shell HTML sent to browser
|
|
49
|
-
→ Suspense boundaries stream in
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
The status code commits when **all three** conditions are met:
|
|
53
|
-
|
|
54
|
-
1. Middleware completed without returning a response
|
|
55
|
-
2. All access checks passed (or denied with a real status code)
|
|
56
|
-
3. React's `onShellReady` fired — the synchronous shell rendered without error
|
|
57
|
-
|
|
58
|
-
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.
|
|
59
|
-
|
|
60
|
-
## Before and After the Flush
|
|
61
|
-
|
|
62
|
-
**Before the flush** (blocking):
|
|
63
|
-
|
|
64
|
-
- `proxy.ts` — global request processing
|
|
65
|
-
- `middleware.ts` — route-level request processing
|
|
66
|
-
- `access.ts` — authorization gates
|
|
67
|
-
- Synchronous component rendering (the shell)
|
|
68
|
-
|
|
69
|
-
**After the flush** (streaming):
|
|
70
|
-
|
|
71
|
-
- `<Suspense>` boundaries resolve and stream in
|
|
72
|
-
- Slow data loads complete
|
|
73
|
-
- The page progressively fills in
|
|
74
|
-
|
|
75
|
-
``` leading="none"
|
|
76
|
-
┌─────── flush point
|
|
77
|
-
│
|
|
78
|
-
▼
|
|
79
|
-
├────────┤──────────────────────────┤
|
|
80
|
-
blocking streaming
|
|
81
|
-
(shell) (suspense boundaries)
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
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.
|
|
85
|
-
|
|
86
|
-
## Pages Work Without JavaScript
|
|
87
|
-
|
|
88
|
-
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.
|
|
89
|
-
|
|
90
|
-
This isn't a special mode. It's how timber works by default.
|
|
91
|
-
|
|
92
|
-
## Early Hints
|
|
93
|
-
|
|
94
|
-
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. The browser starts downloading assets while the server is still working:
|
|
95
|
-
|
|
96
|
-
```
|
|
97
|
-
103 Early Hints
|
|
98
|
-
Link: </styles/main.css>; rel=preload; as=style
|
|
99
|
-
Link: </chunks/page-abc.js>; rel=modulepreload
|
|
100
|
-
|
|
101
|
-
... middleware runs, shell renders ...
|
|
102
|
-
|
|
103
|
-
200 OK
|
|
104
|
-
<html>...
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
You get the correctness of a held flush with the performance of early resource loading.
|
|
108
|
-
|
|
109
|
-
## How to Think About It
|
|
110
|
-
|
|
111
|
-
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.
|
|
112
|
-
|
|
113
|
-
Secondary content — recommendations, activity feeds, analytics widgets — can load after the flush. Wrap it in `<Suspense>`.
|
|
114
|
-
|
|
115
|
-
The flush point is the dividing line between "what the page _is_" and "what the page _also shows_."
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: 'Client Navigation'
|
|
3
|
-
description: 'The Link component, useRouter, segment cache, and scroll restoration.'
|
|
4
|
-
slug: 'client-navigation'
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Client Navigation
|
|
8
|
-
|
|
9
|
-
timber.js supports client-side navigation via the `<Link>` component. Clicking a link fetches an RSC payload from the server and reconciles the DOM without a full page reload.
|
|
10
|
-
|
|
11
|
-
## `<Link>`
|
|
12
|
-
|
|
13
|
-
```tsx
|
|
14
|
-
import { Link } from '@timber-js/app/client';
|
|
15
|
-
|
|
16
|
-
<Link href="/about">About</Link>
|
|
17
|
-
<Link href="/products/123">Product</Link>
|
|
18
|
-
<Link href="/dashboard" replace>Dashboard</Link>
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
`<Link>` type-checks `href` against the generated route map. Invalid routes produce a TypeScript error.
|
|
22
|
-
|
|
23
|
-
## `useRouter`
|
|
24
|
-
|
|
25
|
-
For programmatic navigation:
|
|
26
|
-
|
|
27
|
-
```tsx title="app/components/logout-button.tsx"
|
|
28
|
-
'use client';
|
|
29
|
-
|
|
30
|
-
import { useRouter } from '@timber-js/app/client';
|
|
31
|
-
|
|
32
|
-
declare function logout(): Promise<void>;
|
|
33
|
-
|
|
34
|
-
export function LogoutButton() {
|
|
35
|
-
const router = useRouter();
|
|
36
|
-
|
|
37
|
-
async function handleLogout() {
|
|
38
|
-
await logout();
|
|
39
|
-
router.push('/login');
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return <button onClick={handleLogout}>Log out</button>;
|
|
43
|
-
}
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
| Method | Description |
|
|
47
|
-
| ---------------------- | ---------------------------------------- |
|
|
48
|
-
| `router.push(href)` | Navigate to a new URL |
|
|
49
|
-
| `router.replace(href)` | Navigate without adding a history entry |
|
|
50
|
-
| `router.refresh()` | Re-fetch the current route's RSC payload |
|
|
51
|
-
| `router.back()` | Go back in history |
|
|
52
|
-
| `router.forward()` | Go forward in history |
|
|
53
|
-
|
|
54
|
-
## `usePathname`
|
|
55
|
-
|
|
56
|
-
Returns the current pathname:
|
|
57
|
-
|
|
58
|
-
```tsx
|
|
59
|
-
'use client';
|
|
60
|
-
import { usePathname, Link } from '@timber-js/app/client';
|
|
61
|
-
|
|
62
|
-
export function NavLink({ href, children }: { href: string; children: React.ReactNode }) {
|
|
63
|
-
const pathname = usePathname();
|
|
64
|
-
const isActive = pathname === href;
|
|
65
|
-
return (
|
|
66
|
-
<Link href={href} className={isActive ? 'font-bold' : ''}>
|
|
67
|
-
{children}
|
|
68
|
-
</Link>
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
## `useSelectedLayoutSegment`
|
|
74
|
-
|
|
75
|
-
Returns the active segment within a layout — useful for highlighting nav items:
|
|
76
|
-
|
|
77
|
-
```tsx
|
|
78
|
-
'use client';
|
|
79
|
-
import { useSelectedLayoutSegment } from '@timber-js/app/client';
|
|
80
|
-
|
|
81
|
-
export function DashboardNav() {
|
|
82
|
-
const segment = useSelectedLayoutSegment();
|
|
83
|
-
// segment is "settings", "projects", etc.
|
|
84
|
-
}
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
## `useLinkStatus`
|
|
88
|
-
|
|
89
|
-
Track per-link pending state during navigation:
|
|
90
|
-
|
|
91
|
-
```tsx
|
|
92
|
-
'use client';
|
|
93
|
-
import { Link, useLinkStatus } from '@timber-js/app/client';
|
|
94
|
-
|
|
95
|
-
function NavItemInner({ children }: { children: React.ReactNode }) {
|
|
96
|
-
const { isPending } = useLinkStatus();
|
|
97
|
-
return <span className={isPending ? 'opacity-50' : ''}>{children}</span>;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export function NavItem({ href, children }: { href: string; children: React.ReactNode }) {
|
|
101
|
-
return (
|
|
102
|
-
<Link href={href}>
|
|
103
|
-
<NavItemInner>{children}</NavItemInner>
|
|
104
|
-
</Link>
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
## Segment Cache
|
|
110
|
-
|
|
111
|
-
Opt-in via `clientSegmentCache: true` in `timber.config.ts`. When enabled, the client maintains a mirror of the server's segment tree. On navigation, only changed segments are re-fetched:
|
|
112
|
-
|
|
113
|
-
```ts title="timber.config.ts"
|
|
114
|
-
export default {
|
|
115
|
-
clientSegmentCache: true,
|
|
116
|
-
};
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
```
|
|
120
|
-
/dashboard/settings → /dashboard/team
|
|
121
|
-
|
|
122
|
-
Root Layout ← sync, mounted → skip
|
|
123
|
-
Auth Layout ← sync, mounted → skip
|
|
124
|
-
Dashboard Layout ← sync, mounted → skip
|
|
125
|
-
Team Page ← new → fetch from server
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
Sync layouts stay cached while mounted. Async layouts always re-render. Pages always re-render. Client component state in shared layouts is preserved — counters, form inputs, scroll positions survive navigation.
|
|
129
|
-
|
|
130
|
-
When disabled (the default), every client navigation gets a full RSC payload from the server. This is simpler and avoids edge cases with stale cached layouts, at the cost of slightly larger payloads on navigation.
|
|
131
|
-
|
|
132
|
-
Back/forward navigation replays cached RSC payloads instantly — no server roundtrip.
|
|
133
|
-
|
|
134
|
-
## Scroll Restoration
|
|
135
|
-
|
|
136
|
-
- **Forward navigation** scrolls to the top of the page.
|
|
137
|
-
- **Back/forward** restores the saved scroll position.
|
|
138
|
-
- **Hash fragments** — `<Link href="/docs/api#install">` commits the full URL (including the `#fragment`) to the address bar and scrolls to the matching element after render, just like a plain `<a>` without JavaScript. If no element matches, navigation falls back to scroll-to-top.
|
|
139
|
-
|
|
140
|
-
Pass `scroll={false}` to `<Link>` or `router.push` to preserve the current scroll position:
|
|
141
|
-
|
|
142
|
-
```tsx
|
|
143
|
-
<Link href="/dashboard/settings" scroll={false}>Settings</Link>
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
```tsx
|
|
147
|
-
router.push('/dashboard/settings', { scroll: false });
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
### Custom Scroll Containers
|
|
151
|
-
|
|
152
|
-
If your layout uses a custom scrollable container, add `data-timber-scroll-restoration`:
|
|
153
|
-
|
|
154
|
-
```tsx
|
|
155
|
-
<main className="overflow-y-auto h-screen" data-timber-scroll-restoration>
|
|
156
|
-
{children}
|
|
157
|
-
</main>
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
## `Link.onNavigate`
|
|
161
|
-
|
|
162
|
-
Intercept navigation for view transitions:
|
|
163
|
-
|
|
164
|
-
```tsx
|
|
165
|
-
<Link
|
|
166
|
-
href="/gallery"
|
|
167
|
-
onNavigate={(e) => {
|
|
168
|
-
if (document.startViewTransition) {
|
|
169
|
-
e.preventDefault();
|
|
170
|
-
document.startViewTransition(() => e.navigate());
|
|
171
|
-
}
|
|
172
|
-
}}
|
|
173
|
-
>
|
|
174
|
-
Gallery
|
|
175
|
-
</Link>
|
|
176
|
-
```
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: 'Configuration'
|
|
3
|
-
description: 'timber.config.ts — output mode, adapters, caching, and the two-phase model.'
|
|
4
|
-
slug: 'configuration'
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Configuration
|
|
8
|
-
|
|
9
|
-
timber.js is configured through `timber.config.ts` at the project root. Vite configuration lives separately in `vite.config.ts`.
|
|
10
|
-
|
|
11
|
-
## Minimal Config
|
|
12
|
-
|
|
13
|
-
```ts title="timber.config.ts"
|
|
14
|
-
export default {
|
|
15
|
-
output: 'server',
|
|
16
|
-
};
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
That's it for most projects. The defaults are sensible.
|
|
20
|
-
|
|
21
|
-
## How the Config Is Loaded
|
|
22
|
-
|
|
23
|
-
`timber.config.ts` is loaded by Node.js itself using built-in type stripping — it is not bundled by Vite. This requires Node.js 22.18 or newer, and the file must stick to erasable TypeScript syntax: type annotations, interfaces, and `satisfies` are fine, but enums, namespaces, and constructor parameter properties will fail to load. Your `package.json` should declare `"type": "module"` (the default for new timber projects) so the `export default` syntax parses correctly.
|
|
24
|
-
|
|
25
|
-
If the config file fails to load for any reason, every command — `timber dev`, `timber build`, and `timber preview` — stops with an error naming the file. A broken config never silently falls back to defaults.
|
|
26
|
-
|
|
27
|
-
## Output Modes
|
|
28
|
-
|
|
29
|
-
| Mode | Server required | Client JS | Server Actions |
|
|
30
|
-
| -------------------------------------- | --------------- | ------------------------- | -------------- |
|
|
31
|
-
| `'server'` | Yes | Yes | Yes |
|
|
32
|
-
| `'static'` | No | Yes (hydration + SPA nav) | Via adapter |
|
|
33
|
-
| `'static'` + `clientJavascript: false` | No | None | Build error |
|
|
34
|
-
|
|
35
|
-
```ts title="timber.config.ts"
|
|
36
|
-
// Static site with no JavaScript
|
|
37
|
-
export default {
|
|
38
|
-
output: 'static',
|
|
39
|
-
clientJavascript: false,
|
|
40
|
-
};
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## Build Time vs Request Time
|
|
44
|
-
|
|
45
|
-
The `output` field shifts work between two phases:
|
|
46
|
-
|
|
47
|
-
**Build time** happens once when you run `timber build`. There's no request, no user, no cookies. Route scanning, client boundary discovery, server action extraction, font downloading, and asset bundling all happen here.
|
|
48
|
-
|
|
49
|
-
**Request time** happens per incoming HTTP request. Middleware, access checks, server components, caching, cookies, and server actions all run here.
|
|
50
|
-
|
|
51
|
-
| `output` | `middleware.ts` | Server components | Server actions |
|
|
52
|
-
| ---------- | --------------------- | ------------------------------ | ------------------------------------------- |
|
|
53
|
-
| `'server'` | Request time | Request time | Request time |
|
|
54
|
-
| `'static'` | **Build time only** | **Build time** (once, to HTML) | **Request time**, split-deployed as endpoints |
|
|
55
|
-
|
|
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.
|
|
61
|
-
|
|
62
|
-
## Adapters
|
|
63
|
-
|
|
64
|
-
Adapters transform the build output for your deployment platform:
|
|
65
|
-
|
|
66
|
-
```ts title="timber.config.ts"
|
|
67
|
-
import { cloudflare } from '@timber-js/app/adapters/cloudflare';
|
|
68
|
-
|
|
69
|
-
export default {
|
|
70
|
-
output: 'server',
|
|
71
|
-
adapter: cloudflare(),
|
|
72
|
-
};
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
```ts title="timber.config.ts"
|
|
76
|
-
import { nitro } from '@timber-js/app/adapters/nitro';
|
|
77
|
-
|
|
78
|
-
export default {
|
|
79
|
-
output: 'server',
|
|
80
|
-
adapter: nitro({ preset: 'node-server' }),
|
|
81
|
-
};
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
`cloudflare()` for Cloudflare Workers. `nitro({ preset })` for everything else — Node.js, Vercel, Netlify, AWS Lambda, Deno, Bun, Azure.
|
|
85
|
-
|
|
86
|
-
## Cache Handler
|
|
87
|
-
|
|
88
|
-
Cache handler configuration lives in a separate `timber.cache.ts` file (not `timber.config.ts`). This keeps runtime cache instances separate from build-time config, preventing build dependencies from leaking into the server bundle.
|
|
89
|
-
|
|
90
|
-
```ts title="timber.cache.ts"
|
|
91
|
-
import { MemoryCacheHandler } from '@timber-js/app/cache';
|
|
92
|
-
|
|
93
|
-
export default new MemoryCacheHandler();
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
The default export is the `CacheHandler` instance. Export `cdnPurge` as a named export for CDN purge handlers. The default handler is in-memory. See [Caching](/docs/caching) for Redis, KV, and custom handlers.
|
|
97
|
-
|
|
98
|
-
## `clientJavascript`
|
|
99
|
-
|
|
100
|
-
Control whether client-side JavaScript is included in the build. Useful for static content sites, documentation, and marketing pages that don't need interactivity.
|
|
101
|
-
|
|
102
|
-
```ts title="timber.config.ts"
|
|
103
|
-
// Disable all client JS
|
|
104
|
-
export default {
|
|
105
|
-
output: 'static',
|
|
106
|
-
clientJavascript: false,
|
|
107
|
-
};
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
```ts title="timber.config.ts"
|
|
111
|
-
// No client JS in production, but keep HMR in dev
|
|
112
|
-
export default {
|
|
113
|
-
output: 'static',
|
|
114
|
-
clientJavascript: { disabled: true, enableHMRInDev: true },
|
|
115
|
-
};
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
| Value | Behavior |
|
|
119
|
-
| ------------------------------------------ | ----------------------------------------------------------- |
|
|
120
|
-
| `true` (default) | Client JS enabled — hydration, SPA navigation, etc. |
|
|
121
|
-
| `false` | All client JS disabled. No hydration, no SPA navigation. |
|
|
122
|
-
| `{ disabled: true, enableHMRInDev: true }` | No client JS in production, but HMR still works in dev. |
|
|
123
|
-
|
|
124
|
-
Server actions still work — HTML forms submit natively via POST without JavaScript.
|
|
125
|
-
|
|
126
|
-
## All Options
|
|
127
|
-
|
|
128
|
-
| Option | Type | Default | Description |
|
|
129
|
-
| ------------------- | --------------------------- | ---------------------------- | ---------------------------------------- |
|
|
130
|
-
| `output` | `'server' \| 'static'` | `'server'` | Output mode |
|
|
131
|
-
| `debug` | `boolean` | `false` | Enable timber debug logging in prod |
|
|
132
|
-
| `buildDir` | `string` | `'.timber/dist'` | Build output directory |
|
|
133
|
-
| `clientJavascript` | `boolean \| object` | `true` | Control client-side JS |
|
|
134
|
-
| `adapter` | `TimberPlatformAdapter` | — | Deployment adapter |
|
|
135
|
-
| `serverTiming` | `'detailed' \| 'total' \| false` | `'detailed'` / `'total'` | Server-Timing header |
|
|
136
|
-
| `allowedOrigins` | `string[]` | — | CORS / CSRF allowed origins |
|
|
137
|
-
| `csrf` | `boolean` | `true` | CSRF protection |
|
|
138
|
-
| `limits` | `object` | — | Request body size limits |
|
|
139
|
-
| `actions` | `object` | — | Server action behavior |
|
|
140
|
-
| `forms` | `object` | — | Form handling (sensitive field stripping) |
|
|
141
|
-
| `pageExtensions` | `string[]` | `['tsx', 'ts', 'jsx', 'js']` | File extensions for pages |
|
|
142
|
-
| `slowRequestMs` | `number` | `3000` | Slow request warning threshold (ms) |
|
|
143
|
-
| `renderTimeoutMs` | `number` | `30000` | Render abort timeout (ms) |
|
|
144
|
-
| `devBrowserLogs` | `string` | `'warn'` | Forward browser console to server in dev |
|
|
145
|
-
| `dev` | `object` | — | Dev-mode options |
|
|
146
|
-
| `budget` | `object` | — | Build-time performance budgets |
|
|
147
|
-
| `appDir` | `string` | auto-detected | Override app directory location |
|
|
148
|
-
| `mdx` | `object` | — | MDX remark/rehype plugins |
|
|
149
|
-
| `actionEncryption` | `object` | — | Server action bound args encryption |
|
|
150
|
-
| `reactCompiler` | `boolean \| object` | `true` | React Compiler auto-memoization |
|
|
151
|
-
| `sitemap` | `object` | — | Auto-generated sitemap.xml |
|
|
152
|
-
| `clientSegmentCache`| `boolean` | `false` | Opt-in client segment cache for partial nav |
|
|
153
|
-
| `topLoader` | `object` | enabled | Navigation progress bar |
|
|
154
|
-
|
|
155
|
-
For the full type definition, see the [Config API Reference](/docs/api-config).
|
|
156
|
-
|
|
157
|
-
## .gitignore
|
|
158
|
-
|
|
159
|
-
Add these to your `.gitignore` — they're generated at build/dev time and should not be committed:
|
|
160
|
-
|
|
161
|
-
```txt title=".gitignore"
|
|
162
|
-
.timber
|
|
163
|
-
.content-collections
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
`.timber` contains the build output (default `.timber/dist`). `.content-collections` contains generated modules from [content collections](/docs/content-collections). `create-timber-app` adds both automatically.
|