@timber-js/app 0.2.0-alpha.153 → 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.
Files changed (52) hide show
  1. package/agent-skill.md +344 -0
  2. package/dist/cli.d.ts +9 -1
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +41 -4
  5. package/dist/cli.js.map +1 -1
  6. package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
  7. package/dist/server/internal.js +6 -2
  8. package/dist/server/internal.js.map +1 -1
  9. package/dist/server/pipeline-metadata.d.ts.map +1 -1
  10. package/dist/server/pipeline-phases.d.ts.map +1 -1
  11. package/dist/server/sitemap-handler.d.ts.map +1 -1
  12. package/docs/api/30-api-server.mdx +233 -0
  13. package/docs/api/31-api-client.mdx +161 -0
  14. package/docs/api/32-api-cache.mdx +193 -0
  15. package/docs/api/33-api-search-params.mdx +141 -0
  16. package/docs/api/34-api-config.mdx +189 -0
  17. package/docs/api/35-api-typescript.mdx +170 -0
  18. package/docs/api/36-cli.mdx +143 -0
  19. package/docs/learn/00-introduction.mdx +81 -0
  20. package/docs/learn/01-quick-start.mdx +94 -0
  21. package/docs/learn/02-pages-and-layouts.mdx +138 -0
  22. package/docs/learn/03-fetching-data.mdx +211 -0
  23. package/docs/learn/03b-access-control.mdx +130 -0
  24. package/docs/learn/04-loading-states.mdx +67 -0
  25. package/docs/learn/04b-the-flush-point.mdx +115 -0
  26. package/docs/learn/05-typed-params.mdx +275 -0
  27. package/docs/learn/06-forms-and-actions.mdx +150 -0
  28. package/docs/learn/08-streaming.mdx +146 -0
  29. package/docs/learn/09-caching.mdx +121 -0
  30. package/docs/learn/10-middleware.mdx +156 -0
  31. package/docs/learn/11-error-handling.mdx +147 -0
  32. package/docs/learn/12-client-navigation.mdx +168 -0
  33. package/docs/learn/13-configuration.mdx +117 -0
  34. package/docs/learn/14-deploying.mdx +206 -0
  35. package/docs/more/01-advanced-routing.mdx +139 -0
  36. package/docs/more/02-advanced-forms.mdx +137 -0
  37. package/docs/more/03-coming-from-nextjs.mdx +186 -0
  38. package/docs/more/04-metadata-and-fonts.mdx +193 -0
  39. package/docs/more/04b-mdx.mdx +209 -0
  40. package/docs/more/05-content-collections.mdx +90 -0
  41. package/docs/more/06-instrumentation.mdx +188 -0
  42. package/docs/more/07-security.mdx +123 -0
  43. package/docs/more/40-why-timber.mdx +50 -0
  44. package/docs/more/41-timber-vs-nextjs.mdx +81 -0
  45. package/docs/more/42-timber-vs-others.mdx +68 -0
  46. package/llms.txt +55 -0
  47. package/package.json +6 -2
  48. package/src/cli.ts +62 -3
  49. package/src/client/browser-entry/action-dispatch.ts +14 -0
  50. package/src/server/pipeline-metadata.ts +1 -0
  51. package/src/server/pipeline-phases.ts +4 -1
  52. package/src/server/sitemap-handler.ts +12 -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