@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,214 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: 'Instrumentation & Tracing'
|
|
3
|
-
description: 'Server instrumentation, OTEL tracing, custom logging, and Server-Timing headers.'
|
|
4
|
-
slug: 'instrumentation'
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Instrumentation & Tracing
|
|
8
|
-
|
|
9
|
-
timber.js provides structured observability out of the box: per-request trace IDs, OTEL span integration, pluggable logging, and Server-Timing headers.
|
|
10
|
-
|
|
11
|
-
## `instrumentation.ts`
|
|
12
|
-
|
|
13
|
-
Create an `instrumentation.ts` file at your project root. It runs once at server startup, before the first request is handled.
|
|
14
|
-
|
|
15
|
-
```ts
|
|
16
|
-
// instrumentation.ts
|
|
17
|
-
|
|
18
|
-
// Called once at startup. Initialize your OTEL SDK, database pools, etc.
|
|
19
|
-
export async function register() {
|
|
20
|
-
// e.g. initialize OpenTelemetry
|
|
21
|
-
const { NodeSDK } = await import('@opentelemetry/sdk-node');
|
|
22
|
-
const sdk = new NodeSDK({
|
|
23
|
-
/* ... */
|
|
24
|
-
});
|
|
25
|
-
sdk.start();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Called on every unhandled server error.
|
|
29
|
-
export function onRequestError(error, request, context) {
|
|
30
|
-
// Send to Sentry, Datadog, etc.
|
|
31
|
-
console.error(`[${context.phase}] ${request.method} ${request.path}`, error);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Optional: replace the default logger.
|
|
35
|
-
export { logger } from './lib/logger';
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
### `register()`
|
|
39
|
-
|
|
40
|
-
Called once before the server starts accepting requests. Use it to:
|
|
41
|
-
|
|
42
|
-
- Initialize an OpenTelemetry SDK
|
|
43
|
-
- Set up database connection pools
|
|
44
|
-
- Configure external services
|
|
45
|
-
|
|
46
|
-
The server blocks until `register()` resolves.
|
|
47
|
-
|
|
48
|
-
### `onRequestError(error, request, context)`
|
|
49
|
-
|
|
50
|
-
Called for every unhandled error in the server pipeline. The handler receives:
|
|
51
|
-
|
|
52
|
-
| Parameter | Type | Description |
|
|
53
|
-
| ------------------- | ------------------------ | ----------------------------------------------------- |
|
|
54
|
-
| `error` | `unknown` | The thrown error |
|
|
55
|
-
| `request.method` | `string` | HTTP method (`'GET'`, `'POST'`, etc.) |
|
|
56
|
-
| `request.path` | `string` | Request path (`'/dashboard/projects/123'`) |
|
|
57
|
-
| `request.headers` | `Record<string, string>` | Request headers |
|
|
58
|
-
| `context.phase` | `string` | Pipeline phase: `'proxy'`, `'handler'`, or `'render'` |
|
|
59
|
-
| `context.routePath` | `string` | Request pathname (`'/dashboard/projects/123'`) |
|
|
60
|
-
| `context.routeType` | `string` | Currently always `'page'` |
|
|
61
|
-
| `context.traceId` | `string` | 32-char hex trace ID for correlation |
|
|
62
|
-
|
|
63
|
-
The handler must not affect the response. Errors thrown by the handler are caught and logged.
|
|
64
|
-
|
|
65
|
-
### `logger`
|
|
66
|
-
|
|
67
|
-
Export a logger object to replace the default logger:
|
|
68
|
-
|
|
69
|
-
```ts
|
|
70
|
-
// instrumentation.ts
|
|
71
|
-
import pino from 'pino';
|
|
72
|
-
|
|
73
|
-
export const logger = pino({ level: 'info' });
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
Any object with `info`, `warn`, `error`, and `debug` methods works:
|
|
77
|
-
|
|
78
|
-
```ts
|
|
79
|
-
interface TimberLogger {
|
|
80
|
-
info(msg: string, data?: Record<string, unknown>): void;
|
|
81
|
-
warn(msg: string, data?: Record<string, unknown>): void;
|
|
82
|
-
error(msg: string, data?: Record<string, unknown>): void;
|
|
83
|
-
debug(msg: string, data?: Record<string, unknown>): void;
|
|
84
|
-
}
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
The default logger writes human-readable lines to stderr with automatic trace ID injection.
|
|
88
|
-
|
|
89
|
-
## Tracing
|
|
90
|
-
|
|
91
|
-
### `getTraceId()`
|
|
92
|
-
|
|
93
|
-
Returns the current request's trace ID — always a 32-char lowercase hex string. Available in middleware, access checks, server components, and server actions.
|
|
94
|
-
|
|
95
|
-
```ts
|
|
96
|
-
import { getTraceId } from '@timber-js/app/server';
|
|
97
|
-
|
|
98
|
-
const id = getTraceId(); // "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
- **With OTEL**: returns the real OTEL trace ID (matches Jaeger, Honeycomb, Datadog)
|
|
102
|
-
- **Without OTEL**: returns a `crypto.randomUUID()`-derived fallback in the same format
|
|
103
|
-
|
|
104
|
-
Use `getTraceId()` to correlate logs, errors, and external service calls for a single request.
|
|
105
|
-
|
|
106
|
-
### `getSpanId()`
|
|
107
|
-
|
|
108
|
-
Returns the current OTEL span ID if available, `undefined` otherwise.
|
|
109
|
-
|
|
110
|
-
```ts
|
|
111
|
-
import { getSpanId } from '@timber-js/app/server';
|
|
112
|
-
|
|
113
|
-
const sid = getSpanId(); // "1234567890abcdef" or undefined
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
### `withSpan(name, attributes, fn)`
|
|
117
|
-
|
|
118
|
-
Run a function within a span. Emits an OTEL span when an SDK is active, and a native platform span on Cloudflare (see [Cloudflare Native Traces](#cloudflare-native-traces)). With neither, the function runs directly with zero overhead.
|
|
119
|
-
|
|
120
|
-
```ts
|
|
121
|
-
import { withSpan } from '@timber-js/app/server';
|
|
122
|
-
|
|
123
|
-
declare const userId: string;
|
|
124
|
-
declare const db: { users: { findUnique(opts: { where: { id: string } }): Promise<{ id: string; name: string }> } };
|
|
125
|
-
|
|
126
|
-
const user = await withSpan('db.getUser', { userId }, async () => {
|
|
127
|
-
return db.users.findUnique({ where: { id: userId } });
|
|
128
|
-
});
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
The span automatically:
|
|
132
|
-
|
|
133
|
-
- Creates as a child of the current active span
|
|
134
|
-
- Records exceptions on error
|
|
135
|
-
- Ends when the function completes
|
|
136
|
-
|
|
137
|
-
### `addSpanEvent(name, attributes?)`
|
|
138
|
-
|
|
139
|
-
Add an event to the current active span. Used for recording cache hits/misses, checkpoints, etc.
|
|
140
|
-
|
|
141
|
-
```ts
|
|
142
|
-
import { addSpanEvent } from '@timber-js/app/server';
|
|
143
|
-
|
|
144
|
-
await addSpanEvent('cache.hit', { key: 'user:123' });
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
## Cloudflare Native Traces
|
|
148
|
-
|
|
149
|
-
When you deploy with the Cloudflare adapter, timber's pipeline spans (`timber.proxy`, `timber.middleware`, `timber.access`, `timber.render`, `timber.action`, …) appear **natively in the Cloudflare Observability dashboard** — no OTEL SDK required. They nest alongside Cloudflare's auto-instrumented D1, KV, and `fetch` spans with the same attributes timber emits over OTEL (`http.request.method`, `url.path`, `http.route`, `http.response.status_code`, `timber.result`, …).
|
|
150
|
-
|
|
151
|
-
This works out of the box:
|
|
152
|
-
|
|
153
|
-
- The generated `_worker.js` registers timber's spans with the Workers custom spans API (`tracing.enterSpan()` from `cloudflare:workers`).
|
|
154
|
-
- The generated `wrangler.jsonc` enables the trace destination (`observability.traces.enabled`).
|
|
155
|
-
|
|
156
|
-
To turn native traces off, use the `wrangler` escape hatch:
|
|
157
|
-
|
|
158
|
-
```ts
|
|
159
|
-
// timber.config.ts
|
|
160
|
-
import { cloudflare } from '@timber-js/app/adapters/cloudflare';
|
|
161
|
-
|
|
162
|
-
export default {
|
|
163
|
-
adapter: cloudflare({
|
|
164
|
-
wrangler: { observability: { traces: { enabled: false } } },
|
|
165
|
-
}),
|
|
166
|
-
};
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
Native emission is additive — if you also initialize an OTEL SDK in `register()`, spans are emitted on both channels, so external collectors (Honeycomb, Jaeger, Datadog) keep working. `timber.cache` HIT/MISS **span events** are OTEL-only; the Workers span API has no event equivalent.
|
|
170
|
-
|
|
171
|
-
On runtimes without the custom spans API (older compatibility dates or an outdated local `wrangler`), the worker silently falls back to OTEL-only emission.
|
|
172
|
-
|
|
173
|
-
## Server-Timing
|
|
174
|
-
|
|
175
|
-
timber.js can emit `Server-Timing` headers for browser DevTools performance inspection.
|
|
176
|
-
|
|
177
|
-
```ts
|
|
178
|
-
// timber.config.ts
|
|
179
|
-
export default {
|
|
180
|
-
serverTiming: 'detailed', // 'detailed' | 'total' | false
|
|
181
|
-
};
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
| Value | Description |
|
|
185
|
-
| ------------ | -------------------------------------------------------------------------- |
|
|
186
|
-
| `'detailed'` | Per-phase timing (proxy, middleware, access, render). Default in dev mode. |
|
|
187
|
-
| `'total'` | Total request duration only. Default in production. |
|
|
188
|
-
| `false` | No Server-Timing header. |
|
|
189
|
-
|
|
190
|
-
View the timings in Chrome DevTools under Network > Timing.
|
|
191
|
-
|
|
192
|
-
## Log–Trace Correlation
|
|
193
|
-
|
|
194
|
-
All framework log messages automatically include `trace_id` and `span_id` (when OTEL is active). Custom loggers receive these in the `data` parameter:
|
|
195
|
-
|
|
196
|
-
```json
|
|
197
|
-
{
|
|
198
|
-
"msg": "request completed",
|
|
199
|
-
"method": "GET",
|
|
200
|
-
"path": "/dashboard",
|
|
201
|
-
"status": 200,
|
|
202
|
-
"durationMs": 42,
|
|
203
|
-
"trace_id": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
|
|
204
|
-
"span_id": "1234567890abcdef"
|
|
205
|
-
}
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
This enables filtering logs by trace ID in your logging backend to see all events for a single request.
|
|
209
|
-
|
|
210
|
-
## Dev Mode
|
|
211
|
-
|
|
212
|
-
In development, timber automatically initializes a minimal OTEL SDK with a `DevSpanProcessor` that outputs span information to the dev log. No configuration needed — just run `pnpm dev`.
|
|
213
|
-
|
|
214
|
-
In production, OTEL spans are only emitted when you initialize an SDK in `register()`.
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: 'Security'
|
|
3
|
-
description: 'Built-in CSRF protection, action encryption, redirect safety, and header immutability.'
|
|
4
|
-
slug: 'security'
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Security
|
|
8
|
-
|
|
9
|
-
timber.js ships with several security protections enabled by default. You don't need to configure most of them — they're structural, built into how the framework handles requests.
|
|
10
|
-
|
|
11
|
-
## CSRF Protection
|
|
12
|
-
|
|
13
|
-
All server actions validate the `Origin` header automatically. The `Origin` is compared against the request's full origin (scheme + host + port) — not just the hostname. Behind a reverse proxy, the scheme is derived from `X-Forwarded-Proto`. If the origin doesn't match, the action is rejected with 403. No configuration required.
|
|
14
|
-
|
|
15
|
-
This protects against cross-site request forgery attacks where a malicious page submits a form to your server. The protection works for both JavaScript-enhanced actions and plain HTML form submissions.
|
|
16
|
-
|
|
17
|
-
## Server Action Encryption
|
|
18
|
-
|
|
19
|
-
When a server action captures variables from its closure (bound args), those values are serialized into the RSC payload sent to the client. timber encrypts them with AES-256-GCM before they leave the server.
|
|
20
|
-
|
|
21
|
-
```tsx
|
|
22
|
-
import { SEGMENT_PATH } from './$segment';
|
|
23
|
-
import { getSegmentParams } from '@timber-js/app/server';
|
|
24
|
-
|
|
25
|
-
declare const db: {
|
|
26
|
-
products: { find(id: string): Promise<{ id: string; name: string }> };
|
|
27
|
-
cart: { add(productId: string): Promise<void> };
|
|
28
|
-
};
|
|
29
|
-
declare function AddToCartButton(props: { action: () => Promise<void> }): React.ReactElement;
|
|
30
|
-
|
|
31
|
-
export default async function ProductPage() {
|
|
32
|
-
const { id } = getSegmentParams(SEGMENT_PATH);
|
|
33
|
-
const product = await db.products.find(id);
|
|
34
|
-
|
|
35
|
-
async function addToCart() {
|
|
36
|
-
'use server';
|
|
37
|
-
// `product.id` is a bound arg — encrypted in the client payload
|
|
38
|
-
await db.cart.add(product.id);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return <AddToCartButton action={addToCart} />;
|
|
42
|
-
}
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
The client receives an opaque, encrypted blob. It can't read or tamper with the bound args — the GCM authentication tag ensures integrity.
|
|
46
|
-
|
|
47
|
-
### Encryption Key for Multi-Deploy
|
|
48
|
-
|
|
49
|
-
By default, timber generates a random encryption key per build. This means server actions created by one build can't be decrypted by another. For rolling or blue-green deployments where two builds serve traffic simultaneously, set a shared key:
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
TIMBER_ACTIONS_ENCRYPTION_KEY=<base64-encoded-32-byte-key>
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
Generate a key:
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
## Redirect Safety
|
|
62
|
-
|
|
63
|
-
Open redirect vulnerabilities let an attacker craft a URL on your domain that redirects users to a malicious site (e.g., `/login?next=https://evil.com`). timber prevents this structurally — `redirect()` cannot produce an external redirect.
|
|
64
|
-
|
|
65
|
-
### `redirect()` is relative-only
|
|
66
|
-
|
|
67
|
-
The `redirect()` function only accepts relative paths. Absolute URLs and protocol-relative URLs are rejected:
|
|
68
|
-
|
|
69
|
-
```ts
|
|
70
|
-
import { redirect } from '@timber-js/app/server';
|
|
71
|
-
|
|
72
|
-
redirect('/dashboard'); // ✅ relative path
|
|
73
|
-
redirect('https://evil.com'); // ❌ throws — absolute URL
|
|
74
|
-
redirect('//evil.com'); // ❌ throws — protocol-relative
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
This applies everywhere — middleware, access checks, server actions, and server components. There is no code path where `redirect()` can send users off-site.
|
|
78
|
-
|
|
79
|
-
### `redirectExternal()` with allow-list
|
|
80
|
-
|
|
81
|
-
When you legitimately need to redirect to an external URL (e.g., an OAuth provider), use `redirectExternal()` with an explicit origin allow-list. Only `http:` and `https:` schemes are permitted:
|
|
82
|
-
|
|
83
|
-
```ts
|
|
84
|
-
import { redirectExternal } from '@timber-js/app/server';
|
|
85
|
-
|
|
86
|
-
redirectExternal('https://auth.example.com/login', ['https://auth.example.com']);
|
|
87
|
-
|
|
88
|
-
// With a custom status code
|
|
89
|
-
redirectExternal('https://docs.example.com/guide', ['https://docs.example.com'], 301);
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
If the target origin isn't in the allow-list, the scheme isn't http(s), or the URL is invalid, the call throws. This gives you external redirects without the open redirect risk.
|
|
93
|
-
|
|
94
|
-
<Callout variant="tip" title="vs. Next.js">
|
|
95
|
-
Next.js `redirect()` accepts absolute URLs by default. In timber, external redirects require an explicit allow-list — open redirect bugs are structurally impossible.
|
|
96
|
-
</Callout>
|
|
97
|
-
|
|
98
|
-
## Header Immutability
|
|
99
|
-
|
|
100
|
-
Request headers are immutable after the request enters the pipeline:
|
|
101
|
-
|
|
102
|
-
- `getHeaders()` returns a frozen, read-only `Headers` object — calling `.set()` or `.delete()` throws
|
|
103
|
-
- Middleware can add headers via `ctx.requestHeaders` (an additive overlay), but cannot delete or modify the original request headers
|
|
104
|
-
- The original `Request` object is never mutated
|
|
105
|
-
|
|
106
|
-
This prevents a class of middleware bypass attacks where a malicious or buggy middleware could delete authentication headers before they reach access checks.
|
|
107
|
-
|
|
108
|
-
## Link Safety
|
|
109
|
-
|
|
110
|
-
The `<Link>` component rejects dangerous URL schemes. Links with `javascript:`, `data:`, or `vbscript:` protocols are blocked at render time, preventing XSS via URL injection.
|
|
111
|
-
|
|
112
|
-
## Error Information
|
|
113
|
-
|
|
114
|
-
Unexpected server errors return `{ code: 'INTERNAL_ERROR' }` to the client with no stack trace or internal details. To send structured error data across the boundary, use `deny()` with explicit opt-in:
|
|
115
|
-
|
|
116
|
-
```ts
|
|
117
|
-
import { deny } from '@timber-js/app/server';
|
|
118
|
-
|
|
119
|
-
// Only the data you explicitly pass crosses the boundary
|
|
120
|
-
deny({ status: 404, data: { productId: id } });
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
In development, error details are shown in the browser error overlay. In production, they're suppressed.
|
|
124
|
-
|
|
125
|
-
## What's Next
|
|
126
|
-
|
|
127
|
-
- [Authorization](/docs/access-control) — per-segment access control with `access.ts`
|
|
128
|
-
- [Forms & Server Actions](/docs/forms-and-actions) — action clients, validation, and progressive enhancement
|
|
129
|
-
- [Error Handling](/docs/error-handling) — status codes, deny(), and error boundaries
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: 'Developer Experience'
|
|
3
|
-
description: 'Dev logging, hydration diffs, Server-Timing headers, build reports, and more.'
|
|
4
|
-
slug: 'developer-experience'
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Developer Experience
|
|
8
|
-
|
|
9
|
-
timber.js includes a suite of development tools that surface what your app is doing — where time is spent, what's cached, where errors come from — without reaching for external profiling tools.
|
|
10
|
-
|
|
11
|
-
## Dev Logging
|
|
12
|
-
|
|
13
|
-
Every request in development emits a structured tree to `stderr` showing the full execution pipeline with timing:
|
|
14
|
-
|
|
15
|
-
```
|
|
16
|
-
POST /dashboard/projects/123 trace_id: 4bf92f3577b34da6a3ce929d0e0e4736
|
|
17
|
-
├─ [proxy] proxy.ts 0ms → 2ms
|
|
18
|
-
├─ [rsc] middleware.ts 2ms → 4ms
|
|
19
|
-
│ ├── fired: requireUser() (timber.cache prefetch)
|
|
20
|
-
│ ├── fired: getProject("123") (timber.cache prefetch)
|
|
21
|
-
│ └── fired: getTaskCounts("123") (timber.cache prefetch)
|
|
22
|
-
├─ [rsc] render 4ms
|
|
23
|
-
│ ├─ [rsc] AccessGate (authenticated) 4ms → 5ms
|
|
24
|
-
│ │ └── requireUser() timber.cache HIT <1ms
|
|
25
|
-
│ ├─ [rsc] ProjectPage 8ms → 12ms
|
|
26
|
-
│ │ └── getTaskCounts("123") timber.cache HIT <1ms
|
|
27
|
-
│ └── onShellReady 12ms
|
|
28
|
-
├─ [ssr] hydration render 13ms → 18ms
|
|
29
|
-
└─ ✓ 200 OK total 18ms
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
The tree mirrors the execution structure. `[rsc]`/`[ssr]`/`[client]` labels show which Vite environment each phase runs in. Cache hits and misses are annotated inline.
|
|
33
|
-
|
|
34
|
-
### Fetch Instrumentation
|
|
35
|
-
|
|
36
|
-
`fetch()` calls from server components appear as children of the component that made them:
|
|
37
|
-
|
|
38
|
-
```
|
|
39
|
-
├─ [rsc] page / 6ms → 101ms
|
|
40
|
-
│ ├─ fetch GET https://api.example.com/products 12ms → 89ms (77ms)
|
|
41
|
-
│ └─ fetch GET https://api.example.com/user 12ms → 45ms (33ms) [cdn: HIT]
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
Start times reveal whether fetches ran concurrently or sequentially. Cache status from `X-Cache` or `CF-Cache-Status` headers is surfaced automatically. Fetch instrumentation is dev-only — `globalThis.fetch` is not patched in production.
|
|
45
|
-
|
|
46
|
-
### Log Modes
|
|
47
|
-
|
|
48
|
-
Control the verbosity with `TIMBER_DEV_LOG`:
|
|
49
|
-
|
|
50
|
-
| Mode | Output |
|
|
51
|
-
| --------- | ---------------------------------------------------------- |
|
|
52
|
-
| `tree` | Full indented tree per request (default) |
|
|
53
|
-
| `verbose` | Detailed tree showing every component render |
|
|
54
|
-
| `summary` | One line per request: `POST /path → 200 OK 18ms` |
|
|
55
|
-
| `json` | Chronological NDJSON of all spans with full attributes |
|
|
56
|
-
|
|
57
|
-
Suppress all dev logging with `TIMBER_DEV_QUIET=1`.
|
|
58
|
-
|
|
59
|
-
### Slow Phase Warnings
|
|
60
|
-
|
|
61
|
-
Phases that exceed a configurable threshold are highlighted in the tree output. The default is 200ms:
|
|
62
|
-
|
|
63
|
-
```ts title="timber.config.ts"
|
|
64
|
-
export default {
|
|
65
|
-
dev: {
|
|
66
|
-
slowPhaseMs: 200,
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
This surfaces performance bottlenecks during development without requiring explicit profiling.
|
|
72
|
-
|
|
73
|
-
## Server-Timing Header
|
|
74
|
-
|
|
75
|
-
timber.js emits a `Server-Timing` HTTP header for every response. The level of detail is configurable:
|
|
76
|
-
|
|
77
|
-
| Value | Output | Default in |
|
|
78
|
-
| ------------ | -------------------------------------------------------- | ----------- |
|
|
79
|
-
| `'detailed'` | Per-phase breakdown: proxy, middleware, render, SSR | Development |
|
|
80
|
-
| `'total'` | Single `total;dur=N` entry | Production |
|
|
81
|
-
| `false` | No header | — |
|
|
82
|
-
|
|
83
|
-
```ts title="timber.config.ts"
|
|
84
|
-
export default {
|
|
85
|
-
serverTiming: 'detailed',
|
|
86
|
-
};
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
In production, you can opt into `'detailed'` for APM integration — browser DevTools and monitoring tools parse `Server-Timing` automatically.
|
|
90
|
-
|
|
91
|
-
## Hydration Mismatch Diff
|
|
92
|
-
|
|
93
|
-
When React 19 detects a hydration mismatch, timber parses the diff and renders it with color-coded lines:
|
|
94
|
-
|
|
95
|
-
- Green (`+`) lines show the client-side markup
|
|
96
|
-
- Red (`-`) lines show the server-side markup
|
|
97
|
-
|
|
98
|
-
This replaces Vite's plain-text rendering of hydration errors, making mismatches easy to spot and fix. Multiple hydration errors accumulate in the same overlay.
|
|
99
|
-
|
|
100
|
-
## Error Overlay
|
|
101
|
-
|
|
102
|
-
Errors during development are shown in a browser overlay with:
|
|
103
|
-
|
|
104
|
-
- **Component stacks** — the React component hierarchy leading to the error
|
|
105
|
-
- **Phase labels** — whether the error occurred in middleware, access check, RSC render, SSR, or a client component
|
|
106
|
-
- **Source-mapped stack traces** — pointing to your original source files
|
|
107
|
-
|
|
108
|
-
Client-side errors are forwarded to the overlay via Vite's HMR channel, so they get the same treatment as server errors.
|
|
109
|
-
|
|
110
|
-
## Compiling Indicator
|
|
111
|
-
|
|
112
|
-
A small "Compiling…" indicator appears in the bottom-left corner during HMR updates. It has a 200ms debounce — fast updates never show it. It fades out when the update completes.
|
|
113
|
-
|
|
114
|
-
## Build Report
|
|
115
|
-
|
|
116
|
-
After `timber build`, a route table shows every route with its bundle size, route type, and first-load JS:
|
|
117
|
-
|
|
118
|
-
| Symbol | Meaning |
|
|
119
|
-
| ------ | -------------- |
|
|
120
|
-
| ○ | Static page |
|
|
121
|
-
| λ | Dynamic (SSR) |
|
|
122
|
-
| ƒ | API route |
|
|
123
|
-
|
|
124
|
-
This helps identify large bundles and verify route classification.
|
|
125
|
-
|
|
126
|
-
## HTTPS / HTTP-2 Dev Server
|
|
127
|
-
|
|
128
|
-
timber supports `server.https` in your Vite config for local HTTPS development. The request scheme is derived from the TLS socket (not hardcoded), so CSRF protection works correctly under HTTPS. HTTP/2 pseudo-headers are handled automatically.
|
|
129
|
-
|
|
130
|
-
## What's Next
|
|
131
|
-
|
|
132
|
-
- [Instrumentation](/docs/instrumentation) — production tracing with OpenTelemetry
|
|
133
|
-
- [Configuration](/docs/configuration) — all config options
|
|
134
|
-
- [Streaming](/docs/streaming) — Suspense placement and `deferSuspenseFor`
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: 'Why timber.js?'
|
|
3
|
-
description: 'The design decisions behind timber.js and why they matter.'
|
|
4
|
-
slug: 'why-timber'
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Why timber.js?
|
|
8
|
-
|
|
9
|
-
timber.js exists because we think the current generation of React frameworks made a wrong turn on streaming.
|
|
10
|
-
|
|
11
|
-
## The Premise
|
|
12
|
-
|
|
13
|
-
Most React frameworks stream HTML as fast as possible. The moment the server starts rendering, bytes go to the browser. This sounds great in theory — faster Time to First Byte, progressive rendering, the user sees _something_ sooner.
|
|
14
|
-
|
|
15
|
-
The cost is real:
|
|
16
|
-
|
|
17
|
-
- **Every page returns HTTP 200.** A 404? That's a 200 with an error boundary. A redirect? That's a 200 with client-side navigation. A 500? Also 200. Once you start streaming, you've committed the status code.
|
|
18
|
-
- **Pages don't work without JavaScript.** The client needs JS to resolve suspense boundaries, handle error states, and execute redirects that the server couldn't express through HTTP.
|
|
19
|
-
- **Loading states everywhere.** `loading.tsx` exists because the framework sends the shell before data is ready. Now you're designing skeleton states for every route, managing layout shift, and adding perceived complexity.
|
|
20
|
-
|
|
21
|
-
## What timber.js Does Differently
|
|
22
|
-
|
|
23
|
-
timber.js holds the response until the shell is ready. That's the content outside `<Suspense>` boundaries — the stuff that actually determines the page's HTTP status, headers, and primary content.
|
|
24
|
-
|
|
25
|
-
The trade-off is roughly 20ms of server-side buffering before the first byte. In exchange:
|
|
26
|
-
|
|
27
|
-
- **Real status codes.** `deny(404)` sends a genuine HTTP 404. CDNs cache it correctly. Search engines deindex it. `curl` sees it. APM tools report it.
|
|
28
|
-
- **Pages work without JavaScript.** The initial render arrives complete. No JS needed to show the primary content.
|
|
29
|
-
- **No implicit loading states.** There's no `loading.tsx`. If you want a loading state, you place a `<Suspense>` boundary explicitly around the slow content — and you choose where.
|
|
30
|
-
|
|
31
|
-
Secondary content (reviews on a product page, comments on a post) can still stream via `<Suspense>`. You decide where the flush boundary sits. The framework doesn't decide for you.
|
|
32
|
-
|
|
33
|
-
## What timber.js is Not
|
|
34
|
-
|
|
35
|
-
This is not a framework for every use case. It's opinionated about a few things:
|
|
36
|
-
|
|
37
|
-
- **Server-first rendering.** If you're building a single-page app with no server, this isn't the right tool.
|
|
38
|
-
- **Explicit over implicit.** There's no magic caching, no implicit data fetching, no hidden loading states. You opt into each behavior.
|
|
39
|
-
- **Smaller API surface.** We'd rather have fewer features that work correctly than many features with edge cases.
|
|
40
|
-
|
|
41
|
-
Next.js is more mature, more battle-tested, and supports more use cases. If you're happy with it, there's no reason to switch. timber.js is for people who've run into the limitations of streaming-first and want HTTP semantics that work.
|
|
42
|
-
|
|
43
|
-
## Built on Vite
|
|
44
|
-
|
|
45
|
-
timber.js is a Vite plugin, not a standalone build system. You get Vite's ecosystem: sub-second HMR, the plugin ecosystem, native ESM, and Rolldown for production builds. Your `vite.config.ts` stays normal — timber.js adds to it, it doesn't replace it.
|
|
46
|
-
|
|
47
|
-
## What's Next
|
|
48
|
-
|
|
49
|
-
- [Getting Started](/docs/quick-start) — create a project in under five minutes
|
|
50
|
-
- [timber.js vs Next.js](/docs/timber-vs-nextjs) — an honest comparison
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: 'timber.js vs Next.js'
|
|
3
|
-
description: 'An honest comparison of timber.js and Next.js — where they differ and why.'
|
|
4
|
-
slug: 'timber-vs-nextjs'
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# timber.js vs Next.js
|
|
8
|
-
|
|
9
|
-
Next.js is the most widely used React framework. It's mature, well-documented, and backed by Vercel. timber.js would not exist without it — the app directory routing model, server components, and many API patterns are directly inspired by Next.js.
|
|
10
|
-
|
|
11
|
-
That said, we made different design decisions in a few areas. Here's an honest comparison.
|
|
12
|
-
|
|
13
|
-
## HTTP Semantics
|
|
14
|
-
|
|
15
|
-
| Behavior | Next.js | timber.js |
|
|
16
|
-
| ------------------ | ------------------------------------------------ | ------------------------------------------------ |
|
|
17
|
-
| Status codes | Always 200 (streaming commits early) | Real status codes (flush held until shell ready) |
|
|
18
|
-
| 404 pages | 200 + `not-found.tsx` error boundary | HTTP 404 + `404.tsx` |
|
|
19
|
-
| Redirects in pages | Client-side via `redirect()` after 200 | HTTP 302/301 before any bytes sent |
|
|
20
|
-
| Works without JS | Partially (primary content yes, interactions no) | Fully (forms, navigation, status codes all work) |
|
|
21
|
-
|
|
22
|
-
This is the core philosophical difference. Next.js optimizes for earliest possible TTFB. timber.js optimizes for correct HTTP responses, at the cost of ~20ms additional server buffering.
|
|
23
|
-
|
|
24
|
-
## Streaming
|
|
25
|
-
|
|
26
|
-
Both frameworks support React Suspense streaming. The difference is when it starts:
|
|
27
|
-
|
|
28
|
-
- **Next.js:** Streams immediately. `loading.tsx` renders while data loads. Status code is already committed.
|
|
29
|
-
- **timber.js:** Holds until the shell is ready (everything outside `<Suspense>`). Then streams Suspense content. No `loading.tsx` — `<Suspense>` is opt-in only.
|
|
30
|
-
|
|
31
|
-
## Routing
|
|
32
|
-
|
|
33
|
-
Both use file-system routing in an `app/` directory. The models are similar:
|
|
34
|
-
|
|
35
|
-
- Layouts, pages, dynamic segments, catch-all routes — same patterns
|
|
36
|
-
- Parallel routes (slots) — both support `@sidebar`, `@modal` patterns
|
|
37
|
-
- Route groups — both support `(group)` directories
|
|
38
|
-
|
|
39
|
-
**Differences:**
|
|
40
|
-
|
|
41
|
-
- **Middleware:** Next.js has a single global `middleware.ts`. timber.js has per-segment `middleware.ts` files.
|
|
42
|
-
- **Authorization:** timber.js has `access.ts` files for per-segment access control with `deny()`. Next.js uses middleware or in-component checks.
|
|
43
|
-
- **Typed routes:** timber.js generates route types at build time — `<Link>` type-checks `href` and params.
|
|
44
|
-
|
|
45
|
-
## Caching
|
|
46
|
-
|
|
47
|
-
| Behavior | Next.js | timber.js |
|
|
48
|
-
| ----------------- | ---------------------------------------------------- | ----------------------------------------------------- |
|
|
49
|
-
| `fetch()` caching | Cached by default (opt out with `cache: 'no-store'`) | Never cached (explicit is better) |
|
|
50
|
-
| Cache API | `unstable_cache` / `'use cache'` | `timber.cache()` with TTL, tags, staleWhileRevalidate |
|
|
51
|
-
| Revalidation | `revalidateTag()`, `revalidatePath()` | `revalidateTag()` |
|
|
52
|
-
|
|
53
|
-
timber.js takes the position that implicit caching causes more bugs than it prevents. Nothing is cached unless you wrap it in `timber.cache()`.
|
|
54
|
-
|
|
55
|
-
## Build System
|
|
56
|
-
|
|
57
|
-
| | Next.js | timber.js |
|
|
58
|
-
| ---------------- | ------------------- | ------------------------------------- |
|
|
59
|
-
| Bundler | Turbopack / Webpack | Vite 7 (Rolldown) |
|
|
60
|
-
| Config | `next.config.js` | `vite.config.ts` + `timber.config.ts` |
|
|
61
|
-
| Plugin ecosystem | Next.js-specific | Full Vite plugin ecosystem |
|
|
62
|
-
|
|
63
|
-
## Platform Support
|
|
64
|
-
|
|
65
|
-
Next.js is optimized for Vercel and works on other platforms through community adapters. timber.js ships a first-party Cloudflare Workers adapter and a Nitro adapter that covers Node.js, Vercel, Bun, Netlify, AWS Lambda, and more.
|
|
66
|
-
|
|
67
|
-
## When to Use Next.js
|
|
68
|
-
|
|
69
|
-
- You need the largest ecosystem and community support
|
|
70
|
-
- You're deploying to Vercel
|
|
71
|
-
- You need features timber.js hasn't built yet (image optimization, ISR, etc.)
|
|
72
|
-
- Your team is already productive with Next.js
|
|
73
|
-
|
|
74
|
-
## When to Use timber.js
|
|
75
|
-
|
|
76
|
-
- You care about correct HTTP status codes and headers
|
|
77
|
-
- You want pages that work fully without JavaScript
|
|
78
|
-
- You're deploying to Cloudflare Workers
|
|
79
|
-
- You prefer explicit caching over implicit
|
|
80
|
-
- You want per-segment middleware and authorization
|
|
81
|
-
- You want Vite's build speed and plugin ecosystem
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: 'timber.js vs Other Frameworks'
|
|
3
|
-
description: 'How timber.js compares to Vinext, Remix, and other React server frameworks.'
|
|
4
|
-
slug: 'timber-vs-others'
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# timber.js vs Other Frameworks
|
|
8
|
-
|
|
9
|
-
Beyond Next.js, there are several React frameworks worth comparing against. Each makes different trade-offs.
|
|
10
|
-
|
|
11
|
-
## Vinext (Cloudflare)
|
|
12
|
-
|
|
13
|
-
Vinext is Cloudflare's implementation of Next.js on Vite. It aims for Next.js API compatibility running natively on Cloudflare Workers.
|
|
14
|
-
|
|
15
|
-
**Shared ground:** Both timber.js and Vinext target Cloudflare Workers, use Vite, and support React Server Components.
|
|
16
|
-
|
|
17
|
-
**Key differences:**
|
|
18
|
-
|
|
19
|
-
| | Vinext | timber.js |
|
|
20
|
-
| ------------ | -------------------------------------- | ---------------------------------------- |
|
|
21
|
-
| Goal | Next.js compatibility on Vite | Independent design, different trade-offs |
|
|
22
|
-
| Streaming | Streams immediately (Next.js behavior) | Holds until shell ready |
|
|
23
|
-
| Status codes | 200 for everything (Next.js behavior) | Real HTTP status codes |
|
|
24
|
-
| API surface | Next.js-compatible | Similar but divergent where we disagree |
|
|
25
|
-
| Caching | Next.js caching model | Explicit-only caching |
|
|
26
|
-
|
|
27
|
-
If you want Next.js on Cloudflare with minimal code changes, Vinext is the right choice. If you want a different rendering model with correct HTTP semantics, that's timber.js.
|
|
28
|
-
|
|
29
|
-
## Remix / React Router
|
|
30
|
-
|
|
31
|
-
Remix (now React Router v7) pioneered many of the ideas timber.js agrees with: progressive enhancement, forms that work without JavaScript, and server-first rendering.
|
|
32
|
-
|
|
33
|
-
**Shared philosophy:**
|
|
34
|
-
|
|
35
|
-
- Forms should work without JS
|
|
36
|
-
- Server rendering is the default
|
|
37
|
-
- Progressive enhancement over client-side-first
|
|
38
|
-
|
|
39
|
-
**Key differences:**
|
|
40
|
-
|
|
41
|
-
| | Remix / React Router | timber.js |
|
|
42
|
-
| ------------- | ------------------------------------ | ---------------------------------------- |
|
|
43
|
-
| Routing model | Flat route config or file convention | Nested `app/` directory (Next.js-style) |
|
|
44
|
-
| Data loading | `loader` / `action` functions | Async server components + server actions |
|
|
45
|
-
| RSC support | Not yet (planned) | Built on RSC from day one |
|
|
46
|
-
| Streaming | Supports `defer()` for streaming | `<Suspense>` with explicit flush point |
|
|
47
|
-
| Build system | Vite (React Router v7) | Vite |
|
|
48
|
-
|
|
49
|
-
Remix's `loader`/`action` model is elegant and well-understood. timber.js bets on RSC as the data loading primitive — your components are your data layer.
|
|
50
|
-
|
|
51
|
-
## TanStack Start
|
|
52
|
-
|
|
53
|
-
TanStack Start is a full-stack React framework from the TanStack Router team.
|
|
54
|
-
|
|
55
|
-
**Shared ground:** Both use Vite, both aim for good TypeScript support, both support server-side rendering.
|
|
56
|
-
|
|
57
|
-
**Key differences:**
|
|
58
|
-
|
|
59
|
-
| | TanStack Start | timber.js |
|
|
60
|
-
| -------------- | --------------------------------------- | ------------------------------------------ |
|
|
61
|
-
| Routing | TanStack Router (code-based, type-safe) | File-system routing with generated types |
|
|
62
|
-
| Data loading | TanStack Query integration | Async server components + `timber.cache()` |
|
|
63
|
-
| RSC support | Experimental | Core architecture |
|
|
64
|
-
| Platform focus | General-purpose | Cloudflare-first, works everywhere |
|
|
65
|
-
|
|
66
|
-
## Summary
|
|
67
|
-
|
|
68
|
-
Every framework makes trade-offs. timber.js trades streaming speed for HTTP correctness and trades API surface size for explicitness. If those trade-offs align with how you think about web development, it's worth trying. If they don't, the frameworks above are all excellent choices.
|