@timber-js/app 0.2.0-alpha.154 → 0.2.0-alpha.155
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/agent-skill.md +344 -0
- package/dist/cli.d.ts +9 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +41 -4
- package/dist/cli.js.map +1 -1
- package/docs/api/30-api-server.mdx +233 -0
- package/docs/api/31-api-client.mdx +161 -0
- package/docs/api/32-api-cache.mdx +193 -0
- package/docs/api/33-api-search-params.mdx +141 -0
- package/docs/api/34-api-config.mdx +189 -0
- package/docs/api/35-api-typescript.mdx +170 -0
- package/docs/api/36-cli.mdx +143 -0
- package/docs/learn/00-introduction.mdx +81 -0
- package/docs/learn/01-quick-start.mdx +94 -0
- package/docs/learn/02-pages-and-layouts.mdx +138 -0
- package/docs/learn/03-fetching-data.mdx +211 -0
- package/docs/learn/03b-access-control.mdx +130 -0
- package/docs/learn/04-loading-states.mdx +67 -0
- package/docs/learn/04b-the-flush-point.mdx +115 -0
- package/docs/learn/05-typed-params.mdx +275 -0
- package/docs/learn/06-forms-and-actions.mdx +150 -0
- package/docs/learn/08-streaming.mdx +146 -0
- package/docs/learn/09-caching.mdx +121 -0
- package/docs/learn/10-middleware.mdx +156 -0
- package/docs/learn/11-error-handling.mdx +147 -0
- package/docs/learn/12-client-navigation.mdx +168 -0
- package/docs/learn/13-configuration.mdx +117 -0
- package/docs/learn/14-deploying.mdx +206 -0
- package/docs/more/01-advanced-routing.mdx +139 -0
- package/docs/more/02-advanced-forms.mdx +137 -0
- package/docs/more/03-coming-from-nextjs.mdx +186 -0
- package/docs/more/04-metadata-and-fonts.mdx +193 -0
- package/docs/more/04b-mdx.mdx +209 -0
- package/docs/more/05-content-collections.mdx +90 -0
- package/docs/more/06-instrumentation.mdx +188 -0
- package/docs/more/07-security.mdx +123 -0
- package/docs/more/40-why-timber.mdx +50 -0
- package/docs/more/41-timber-vs-nextjs.mdx +81 -0
- package/docs/more/42-timber-vs-others.mdx +68 -0
- package/llms.txt +55 -0
- package/package.json +6 -2
- package/src/cli.ts +62 -3
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'API: @timber-js/app/search-params & Segment Params'
|
|
3
|
+
description: 'Params exports — defineSchema, getSegmentParams, defineSearchParams, codecs, and utilities.'
|
|
4
|
+
slug: 'api-params'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# @timber-js/app/search-params
|
|
8
|
+
|
|
9
|
+
Typed search parameter definitions.
|
|
10
|
+
|
|
11
|
+
## `defineSearchParams(codecs)`
|
|
12
|
+
|
|
13
|
+
Create a typed search params definition for a route.
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { defineSearchParams } from '@timber-js/app/search-params';
|
|
17
|
+
import { z } from 'zod/v4';
|
|
18
|
+
|
|
19
|
+
export const searchParams = defineSearchParams({
|
|
20
|
+
page: z.coerce.number().default(1),
|
|
21
|
+
sort: z.enum(['price', 'name', 'date']).default('date'),
|
|
22
|
+
q: z.string().optional(),
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Returns: `SearchParamsDefinition<T>`
|
|
27
|
+
|
|
28
|
+
| Method / Property | Description |
|
|
29
|
+
| ------------------------- | ------------------------------------------------------ |
|
|
30
|
+
| `.get()` | Read from ALS and parse (server-only) |
|
|
31
|
+
| `.parse(searchParams)` | Parse and validate from `URLSearchParams` or `Promise` |
|
|
32
|
+
| `.useQueryStates(opts?)` | Client hook — read and update typed search params |
|
|
33
|
+
| `.extend(codecs)` | Add additional params |
|
|
34
|
+
| `.pick(...keys)` | Create a subset |
|
|
35
|
+
| `.serialize(values)` | Convert typed values to URL search string |
|
|
36
|
+
| `.href(base, values)` | Build a full URL with params |
|
|
37
|
+
| `.toSearchParams(values)` | Convert to `URLSearchParams` |
|
|
38
|
+
| `.codecs` | Read-only codec map |
|
|
39
|
+
| `.urlKeys` | Read-only URL key alias map |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
# Segment Params
|
|
44
|
+
|
|
45
|
+
Segment params are defined globally in `app/schema.ts` via `defineSchema`. Access them with `getSegmentParams()` from `@timber-js/app/server` or `useSegmentParams()` from `@timber-js/app/client`.
|
|
46
|
+
|
|
47
|
+
## `defineSchema(config)`
|
|
48
|
+
|
|
49
|
+
Define segment param codecs in a single file. Exported from `@timber-js/app`.
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { defineSchema } from '@timber-js/app/params';
|
|
53
|
+
import { codec } from '@timber-js/app/codec';
|
|
54
|
+
import { z } from 'zod/v4';
|
|
55
|
+
|
|
56
|
+
export default defineSchema({
|
|
57
|
+
segmentParams: {
|
|
58
|
+
'[id]': z.coerce.number().int().positive(),
|
|
59
|
+
'[artistSlug]': codec.string,
|
|
60
|
+
'[year]': codec.integer,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Each key uses the bracket syntax from the filesystem. Parse failure → 404.
|
|
66
|
+
|
|
67
|
+
## `getSegmentParams(segmentPath?)`
|
|
68
|
+
|
|
69
|
+
Read segment params on the server. ALS-backed. Exported from `@timber-js/app/server`.
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { SEGMENT_PATH } from './$segment';
|
|
73
|
+
import { getSegmentParams } from '@timber-js/app/server';
|
|
74
|
+
|
|
75
|
+
const { id } = getSegmentParams(SEGMENT_PATH); // Exact types from schema
|
|
76
|
+
const params = getSegmentParams(); // Partial<AllSegmentParams>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## `useSegmentParams(segmentPath?)`
|
|
80
|
+
|
|
81
|
+
Read segment params on the client. Context-backed. Exported from `@timber-js/app/client`.
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { SEGMENT_PATH } from './$segment';
|
|
85
|
+
import { useSegmentParams } from '@timber-js/app/client';
|
|
86
|
+
|
|
87
|
+
const { id } = useSegmentParams(SEGMENT_PATH); // Exact types from schema
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
See [Typed Params](/docs/typed-params) for the full guide.
|
|
91
|
+
|
|
92
|
+
## `SearchParamCodec<T>`
|
|
93
|
+
|
|
94
|
+
The codec protocol shared across search params, segment params, and cookies. See [Codecs](/docs/typed-params) for the full guide.
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
interface SearchParamCodec<T> {
|
|
98
|
+
parse(value: string | string[] | undefined): T;
|
|
99
|
+
serialize(value: T): string | null;
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Compatible codecs:
|
|
104
|
+
|
|
105
|
+
- **Zod/Valibot/ArkType schemas** — auto-detected via Standard Schema
|
|
106
|
+
- **nuqs parsers** — `parseAsInteger`, `parseAsString`, `parseAsBoolean`
|
|
107
|
+
- **Custom** — any object implementing the interface
|
|
108
|
+
|
|
109
|
+
## Built-in Codecs
|
|
110
|
+
|
|
111
|
+
| Export | Description |
|
|
112
|
+
| ------------------------------ | --------------------------- |
|
|
113
|
+
| `parseAsString` | `string \| null` |
|
|
114
|
+
| `parseAsInteger` | `number \| null` |
|
|
115
|
+
| `parseAsFloat` | `number \| null` |
|
|
116
|
+
| `parseAsBoolean` | `boolean \| null` |
|
|
117
|
+
| `parseAsStringEnum(values)` | Enum `T \| null` |
|
|
118
|
+
| `parseAsStringLiteral(values)` | Literal `T[number] \| null` |
|
|
119
|
+
|
|
120
|
+
## Codec Wrappers
|
|
121
|
+
|
|
122
|
+
### `withDefault(codec, value)`
|
|
123
|
+
|
|
124
|
+
Wrap a codec to replace `null` with a default value.
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { withDefault } from '@timber-js/app/search-params';
|
|
128
|
+
import { parseAsInteger } from 'nuqs';
|
|
129
|
+
const page = withDefault(parseAsInteger, 1); // number (never null)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### `withUrlKey(codec, key)`
|
|
133
|
+
|
|
134
|
+
Attach a URL key alias to a codec.
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
import { withUrlKey } from '@timber-js/app/search-params';
|
|
138
|
+
import { parseAsString } from 'nuqs';
|
|
139
|
+
const query = withUrlKey(parseAsString, 'q');
|
|
140
|
+
// ?q=shoes → { query: 'shoes' }
|
|
141
|
+
```
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'API: timber.config.ts'
|
|
3
|
+
description: 'All configuration options for timber.config.ts.'
|
|
4
|
+
slug: 'api-config'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# timber.config.ts
|
|
8
|
+
|
|
9
|
+
Project-wide configuration. Export a default object from `timber.config.ts` at the project root.
|
|
10
|
+
|
|
11
|
+
## Full Type
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
interface TimberUserConfig {
|
|
15
|
+
output?: 'server' | 'static';
|
|
16
|
+
buildDir?: string;
|
|
17
|
+
clientJavascript?: boolean | ClientJavascriptConfig;
|
|
18
|
+
adapter?: TimberPlatformAdapter;
|
|
19
|
+
cacheHandler?: CacheHandler;
|
|
20
|
+
serverTiming?: 'detailed' | 'total' | false;
|
|
21
|
+
allowedOrigins?: string[];
|
|
22
|
+
csrf?: boolean;
|
|
23
|
+
limits?: {
|
|
24
|
+
actionBodySize?: string;
|
|
25
|
+
uploadBodySize?: string;
|
|
26
|
+
maxFields?: number;
|
|
27
|
+
};
|
|
28
|
+
actions?: {
|
|
29
|
+
runMiddleware?: boolean;
|
|
30
|
+
};
|
|
31
|
+
forms?: {
|
|
32
|
+
stripSensitiveFields?: boolean | string[];
|
|
33
|
+
};
|
|
34
|
+
pageExtensions?: string[];
|
|
35
|
+
dev?: {
|
|
36
|
+
slowPhaseMs?: number;
|
|
37
|
+
};
|
|
38
|
+
mdx?: {
|
|
39
|
+
remarkPlugins?: any[];
|
|
40
|
+
rehypePlugins?: any[];
|
|
41
|
+
recmaPlugins?: any[];
|
|
42
|
+
remarkRehypeOptions?: any;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Options
|
|
48
|
+
|
|
49
|
+
### `output`
|
|
50
|
+
|
|
51
|
+
- **Type:** `'server' | 'static'`
|
|
52
|
+
- **Default:** `'server'`
|
|
53
|
+
|
|
54
|
+
Server mode runs on every request. Static mode pre-renders at build time.
|
|
55
|
+
|
|
56
|
+
### `buildDir`
|
|
57
|
+
|
|
58
|
+
- **Type:** `string`
|
|
59
|
+
- **Default:** `'.timber/dist'`
|
|
60
|
+
|
|
61
|
+
Override the build output directory. Relative to the project root. Timber config takes precedence over Vite's `build.outDir`. If neither is set, defaults to `.timber/dist`.
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
buildDir: 'build'; // outputs to <root>/build/
|
|
65
|
+
buildDir: '.output'; // outputs to <root>/.output/
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### `clientJavascript`
|
|
69
|
+
|
|
70
|
+
- **Type:** `boolean | { disabled: boolean; enableHMRInDev?: boolean }`
|
|
71
|
+
- **Default:** `true`
|
|
72
|
+
|
|
73
|
+
Control client-side JavaScript output. Set to `false` to disable all client JS. Use the object form to disable client JS in production while keeping HMR in development:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
clientJavascript: { disabled: true, enableHMRInDev: true }
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### `adapter`
|
|
80
|
+
|
|
81
|
+
- **Type:** `TimberPlatformAdapter`
|
|
82
|
+
- **Default:** none
|
|
83
|
+
|
|
84
|
+
Deployment adapter. See [Deployment](/docs/deploying).
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { cloudflare } from '@timber-js/app/adapters/cloudflare';
|
|
88
|
+
import { nitro } from '@timber-js/app/adapters/nitro';
|
|
89
|
+
|
|
90
|
+
adapter: cloudflare();
|
|
91
|
+
adapter: nitro({ preset: 'node-server' });
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### `cacheHandler`
|
|
95
|
+
|
|
96
|
+
- **Type:** `CacheHandler`
|
|
97
|
+
- **Default:** `MemoryCacheHandler`
|
|
98
|
+
|
|
99
|
+
Cache backend for `timber.cache()`. See [Cache API](/docs/api-cache).
|
|
100
|
+
|
|
101
|
+
### `serverTiming`
|
|
102
|
+
|
|
103
|
+
- **Type:** `'detailed' | 'total' | false`
|
|
104
|
+
- **Default:** `'detailed'` in dev, `'total'` in production
|
|
105
|
+
|
|
106
|
+
Controls the `Server-Timing` response header. See [Instrumentation & Tracing](/docs/instrumentation).
|
|
107
|
+
|
|
108
|
+
### `allowedOrigins`
|
|
109
|
+
|
|
110
|
+
- **Type:** `string[]`
|
|
111
|
+
- **Default:** none
|
|
112
|
+
|
|
113
|
+
Origins allowed for CORS and CSRF validation.
|
|
114
|
+
|
|
115
|
+
### `csrf`
|
|
116
|
+
|
|
117
|
+
- **Type:** `boolean`
|
|
118
|
+
- **Default:** `true` (in server mode)
|
|
119
|
+
|
|
120
|
+
Enable CSRF protection for server actions and form submissions.
|
|
121
|
+
|
|
122
|
+
### `limits`
|
|
123
|
+
|
|
124
|
+
Control request body size limits:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
limits: {
|
|
128
|
+
actionBodySize: '1mb', // Server action body limit
|
|
129
|
+
uploadBodySize: '10mb', // File upload body limit
|
|
130
|
+
maxFields: 1000, // Maximum FormData fields
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### `actions.runMiddleware`
|
|
135
|
+
|
|
136
|
+
- **Type:** `boolean`
|
|
137
|
+
- **Default:** `true`
|
|
138
|
+
|
|
139
|
+
Run `middleware.ts` on server action requests before dispatching. Keeps authentication, rate limiting, and other cross-cutting checks applied uniformly to page renders, route handlers, and server actions. Set to `false` only if your middleware would corrupt action submissions (e.g. it rewrites POST bodies) — you then own placing those checks inside every action.
|
|
140
|
+
|
|
141
|
+
### `forms.stripSensitiveFields`
|
|
142
|
+
|
|
143
|
+
- **Type:** `boolean | string[]`
|
|
144
|
+
- **Default:** `true`
|
|
145
|
+
|
|
146
|
+
Strip sensitive fields (passwords, tokens, CVV, SSN, etc.) from the `submittedValues` echoed back on validation failure. `true` uses the built-in deny-list; an array adds extra field names to the built-in list; `false` disables stripping (never do this in production). See [Forms & Server Actions](/docs/forms-and-actions) for the full pattern list.
|
|
147
|
+
|
|
148
|
+
### `pageExtensions`
|
|
149
|
+
|
|
150
|
+
- **Type:** `string[]`
|
|
151
|
+
- **Default:** `['tsx', 'ts', 'jsx', 'js']`
|
|
152
|
+
|
|
153
|
+
File extensions recognized for rendering conventions (`page.*`, `layout.*`, `error.*`, `default.*`, `denied.*`, status files). Add `'mdx'` for MDX pages.
|
|
154
|
+
|
|
155
|
+
Code-only conventions (`middleware.*`, `access.*`, `route.*`, `params.*`) always use `.ts`/`.tsx`/`.js`/`.jsx` regardless of this setting. `.mts`/`.cts`/`.mjs`/`.cjs` are not recognized for any convention.
|
|
156
|
+
|
|
157
|
+
Two files claiming the same convention in one directory (e.g. `page.tsx` + `page.jsx`) are a build error — filesystem order is platform-dependent, so timber refuses to pick one silently.
|
|
158
|
+
|
|
159
|
+
### `dev.slowPhaseMs`
|
|
160
|
+
|
|
161
|
+
- **Type:** `number`
|
|
162
|
+
- **Default:** framework default
|
|
163
|
+
|
|
164
|
+
Threshold in milliseconds for slow-phase warnings in dev mode.
|
|
165
|
+
|
|
166
|
+
### `mdx`
|
|
167
|
+
|
|
168
|
+
MDX compilation options — remark, rehype, and recma plugins:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
mdx: {
|
|
172
|
+
remarkPlugins: [remarkGfm],
|
|
173
|
+
rehypePlugins: [rehypePrism],
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## `TimberPlatformAdapter`
|
|
178
|
+
|
|
179
|
+
The adapter interface for community adapters:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
interface TimberPlatformAdapter {
|
|
183
|
+
name: string;
|
|
184
|
+
buildOutput(config: TimberConfig, buildDir: string): Promise<void>;
|
|
185
|
+
preview?(config: TimberConfig, buildDir: string): Promise<void>;
|
|
186
|
+
waitUntil?(promise: Promise<unknown>): void;
|
|
187
|
+
sendEarlyHints?(links: string[]): void;
|
|
188
|
+
}
|
|
189
|
+
```
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'API: TypeScript'
|
|
3
|
+
description: 'Generated route types, $segment modules, typed Link, typed params, and the Routes interface.'
|
|
4
|
+
slug: 'api-typescript'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# TypeScript
|
|
8
|
+
|
|
9
|
+
timber.js generates route types at build time. Links, params, and search params are type-checked against the actual route tree.
|
|
10
|
+
|
|
11
|
+
## Generated Route Map
|
|
12
|
+
|
|
13
|
+
The build system scans your `app/` directory and generates `.timber/routes.d.ts`:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
// .timber/routes.d.ts (generated, gitignored)
|
|
17
|
+
import type schema from '../app/schema';
|
|
18
|
+
import type { InferCodecType } from '@timber-js/app/params';
|
|
19
|
+
|
|
20
|
+
export type TimberRoute = keyof TimberSegmentParams;
|
|
21
|
+
|
|
22
|
+
export interface TimberSegmentParams {
|
|
23
|
+
'/': {};
|
|
24
|
+
'/products/[id]': {
|
|
25
|
+
id: InferCodecType<typeof schema.segmentParams['[id]']>;
|
|
26
|
+
};
|
|
27
|
+
'/blog/[...slug]': { slug: string[] };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type AllSegmentParams = { /* union of all route params */ };
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Types reference your `app/schema.ts` codecs directly, so they stay in sync automatically.
|
|
34
|
+
|
|
35
|
+
## `$segment` — Per-Route Type Narrowing
|
|
36
|
+
|
|
37
|
+
Each route directory gets a generated `$segment` module (via TypeScript's `rootDirs`). Import `SEGMENT_PATH` for type-safe param access:
|
|
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
|
|
51
|
+
|
|
52
|
+
| Export | Type | Description |
|
|
53
|
+
| -------------- | ------------------- | ------------------------------------------------------------- |
|
|
54
|
+
| `ROUTE` | String literal | URL route pattern, groups stripped (e.g., `'/products/[id]'`) |
|
|
55
|
+
| `SEGMENT_PATH` | String literal | Full tree path including groups and slots |
|
|
56
|
+
|
|
57
|
+
### tsconfig.json
|
|
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
|
+
When a `params.ts` file exports `searchParams`, search params are fully typed:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
// app/products/params.ts
|
|
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
|
+
```
|
|
124
|
+
|
|
125
|
+
## Context Types
|
|
126
|
+
|
|
127
|
+
### `RouteContext`
|
|
128
|
+
|
|
129
|
+
Passed to API route handlers (`route.ts`):
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
export async function GET(ctx: RouteContext) {
|
|
133
|
+
ctx.req; // Request
|
|
134
|
+
ctx.headers; // Headers (response, writable)
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Segment params and query params are read via `segmentParams.get()` / `searchParams.get()` from the colocated `params.ts` / `search-params.ts` — the same accessors pages use. For the raw query string, use `new URL(ctx.req.url).searchParams`.
|
|
139
|
+
|
|
140
|
+
### `MiddlewareContext`
|
|
141
|
+
|
|
142
|
+
Passed to `middleware.ts`:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
export default async function middleware(ctx: MiddlewareContext) {
|
|
146
|
+
ctx.req; // Request
|
|
147
|
+
ctx.segmentParams; // Record<string, string | string[]>
|
|
148
|
+
ctx.headers; // Headers (response, writable)
|
|
149
|
+
ctx.requestHeaders; // Headers (request, injectable)
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### `AccessContext`
|
|
154
|
+
|
|
155
|
+
Empty interface — `access.ts` takes no arguments. Use ALS-backed imports:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
import { SEGMENT_PATH } from './$segment';
|
|
159
|
+
import { getSegmentParams } from '@timber-js/app/server';
|
|
160
|
+
|
|
161
|
+
export default async function access() {
|
|
162
|
+
const params = getSegmentParams(SEGMENT_PATH);
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Augmenting Routes
|
|
167
|
+
|
|
168
|
+
The `Routes` interface is generated by the build system and written to `.timber/routes.d.ts`. It's module-augmented onto `@timber-js/app` — you don't need to import it manually.
|
|
169
|
+
|
|
170
|
+
Run `timber build` or `timber dev` to regenerate types after adding or renaming routes.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'CLI'
|
|
3
|
+
description: 'timber dev, build, preview, check, and init commands.'
|
|
4
|
+
slug: 'cli'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# CLI
|
|
8
|
+
|
|
9
|
+
timber.js provides four commands. All are thin wrappers around Vite with timber-specific behavior.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
timber < command > [--config<path>];
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Commands
|
|
16
|
+
|
|
17
|
+
### `timber dev`
|
|
18
|
+
|
|
19
|
+
Start the development server with HMR and React Fast Refresh.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
timber dev
|
|
23
|
+
timber dev --config ./custom-vite.config.ts
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Starts a Vite dev server with timber's routing, RSC pipeline, and dev tooling active. File changes trigger instant HMR. Route changes are detected automatically.
|
|
27
|
+
|
|
28
|
+
The server binds the port immediately on startup and shows a loading page while Vite initializes. This eliminates connection refused errors during cold start.
|
|
29
|
+
|
|
30
|
+
#### Port selection
|
|
31
|
+
|
|
32
|
+
The default port is **`3000`** for both `timber dev` and `timber preview` (matching Next.js and most Node web frameworks). If `3000` is already in use, timber automatically bumps to `3001`, then `3002`, and so on until it finds a free port:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
[timber] Port 3000 in use, using 3001
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
To override the default, use any of the following (highest precedence first):
|
|
39
|
+
|
|
40
|
+
1. `server.port` in `vite.config.ts`
|
|
41
|
+
2. `PORT=4000 timber dev` — the `PORT` environment variable
|
|
42
|
+
|
|
43
|
+
> **Note:** `timber dev --port 4000` is an error, not a silent no-op. The timber CLI only accepts `--config` — use `PORT` or `server.port` to change the port.
|
|
44
|
+
|
|
45
|
+
When you set the port explicitly, timber **does not** auto-bump — a conflict produces an `EADDRINUSE` error so you find out immediately rather than connecting to the wrong port. This same behavior applies to `timber preview` and the production Node preview server.
|
|
46
|
+
|
|
47
|
+
### `timber build`
|
|
48
|
+
|
|
49
|
+
Run the production build.
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
timber build
|
|
53
|
+
timber build --config ./custom-vite.config.ts
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Uses Vite's `createBuilder` + `buildApp()` to run a multi-environment build (RSC, SSR, client). The output goes to `.timber/dist/` by default (configurable via `buildDir` in `timber.config.ts`) and is shaped by your adapter (Cloudflare, Nitro, static).
|
|
57
|
+
|
|
58
|
+
After the build completes, a route table is printed showing each route's type and bundle size:
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
Route Type Size
|
|
62
|
+
─────────────────────────────────────────────
|
|
63
|
+
/ ○ 2.1 kB
|
|
64
|
+
/about ○ 1.8 kB
|
|
65
|
+
/blog/[slug] λ 3.4 kB
|
|
66
|
+
/api/contact ƒ 0.5 kB
|
|
67
|
+
|
|
68
|
+
○ Static λ Dynamic ƒ Function
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### `timber preview`
|
|
72
|
+
|
|
73
|
+
Serve the production build locally for testing.
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
timber preview
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
If your adapter provides a `preview()` method (e.g. Cloudflare's Miniflare-based preview), that takes priority. Otherwise falls back to Vite's built-in preview server.
|
|
80
|
+
|
|
81
|
+
A `timber.config.ts` that exists but fails to load is a hard error naming the file — preview never silently falls back to the plain Vite preview server with your adapter ignored.
|
|
82
|
+
|
|
83
|
+
Always run `timber build` before `timber preview`.
|
|
84
|
+
|
|
85
|
+
### `timber check`
|
|
86
|
+
|
|
87
|
+
Validate types and routes without producing build output.
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
timber check
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Runs TypeScript type checking via `tsgo --noEmit`. Useful in CI pipelines and pre-commit hooks to catch type errors without a full build.
|
|
94
|
+
|
|
95
|
+
### `timber init`
|
|
96
|
+
|
|
97
|
+
Write `AGENTS.md` and `CLAUDE.md` to the project root for AI coding agent discovery.
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
timber init
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The content comes from the installed `@timber-js/app` package, so it's always in sync with your installed version. If either file already exists, it's skipped — delete it first to regenerate.
|
|
104
|
+
|
|
105
|
+
The generated `AGENTS.md` includes project structure, key imports, common patterns, and pointers to the full documentation in `node_modules/@timber-js/app/docs/`. `CLAUDE.md` is a one-line pointer to `AGENTS.md`.
|
|
106
|
+
|
|
107
|
+
## Options
|
|
108
|
+
|
|
109
|
+
| Flag | Short | Description |
|
|
110
|
+
| ----------------- | ----- | --------------------------------------------------------------------------------------------------------- |
|
|
111
|
+
| `--config <path>` | `-c` | Path to a custom Vite config file (for `dev`, `build`, `preview`) or a `tsconfig.json` path (for `check`) |
|
|
112
|
+
|
|
113
|
+
> **Note:** For `timber check`, `--config` is passed to `tsgo --project` and should point to a TypeScript config file, not a Vite config.
|
|
114
|
+
|
|
115
|
+
`--config` is the only flag. Any other flag produces an error rather than being silently ignored, with a hint at the supported alternative (e.g. `--port` points you to the `PORT` env var or `server.port` in `vite.config.ts`).
|
|
116
|
+
|
|
117
|
+
## Package Manager Scripts
|
|
118
|
+
|
|
119
|
+
Most projects run timber through package.json scripts:
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"scripts": {
|
|
124
|
+
"dev": "timber dev",
|
|
125
|
+
"build": "timber build",
|
|
126
|
+
"preview": "timber preview",
|
|
127
|
+
"check": "timber check"
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
pnpm dev
|
|
134
|
+
pnpm build
|
|
135
|
+
pnpm preview
|
|
136
|
+
pnpm run check
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## What's Next
|
|
140
|
+
|
|
141
|
+
- [Getting Started](/docs/quick-start) — create a project and run `timber dev`
|
|
142
|
+
- [Configuration](/docs/api-config) — `timber.config.ts` reference
|
|
143
|
+
- [Deployment](/docs/deploying) — build and deploy to production
|