@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.
Files changed (42) 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/docs/api/30-api-server.mdx +233 -0
  7. package/docs/api/31-api-client.mdx +161 -0
  8. package/docs/api/32-api-cache.mdx +193 -0
  9. package/docs/api/33-api-search-params.mdx +141 -0
  10. package/docs/api/34-api-config.mdx +189 -0
  11. package/docs/api/35-api-typescript.mdx +170 -0
  12. package/docs/api/36-cli.mdx +143 -0
  13. package/docs/learn/00-introduction.mdx +81 -0
  14. package/docs/learn/01-quick-start.mdx +94 -0
  15. package/docs/learn/02-pages-and-layouts.mdx +138 -0
  16. package/docs/learn/03-fetching-data.mdx +211 -0
  17. package/docs/learn/03b-access-control.mdx +130 -0
  18. package/docs/learn/04-loading-states.mdx +67 -0
  19. package/docs/learn/04b-the-flush-point.mdx +115 -0
  20. package/docs/learn/05-typed-params.mdx +275 -0
  21. package/docs/learn/06-forms-and-actions.mdx +150 -0
  22. package/docs/learn/08-streaming.mdx +146 -0
  23. package/docs/learn/09-caching.mdx +121 -0
  24. package/docs/learn/10-middleware.mdx +156 -0
  25. package/docs/learn/11-error-handling.mdx +147 -0
  26. package/docs/learn/12-client-navigation.mdx +168 -0
  27. package/docs/learn/13-configuration.mdx +117 -0
  28. package/docs/learn/14-deploying.mdx +206 -0
  29. package/docs/more/01-advanced-routing.mdx +139 -0
  30. package/docs/more/02-advanced-forms.mdx +137 -0
  31. package/docs/more/03-coming-from-nextjs.mdx +186 -0
  32. package/docs/more/04-metadata-and-fonts.mdx +193 -0
  33. package/docs/more/04b-mdx.mdx +209 -0
  34. package/docs/more/05-content-collections.mdx +90 -0
  35. package/docs/more/06-instrumentation.mdx +188 -0
  36. package/docs/more/07-security.mdx +123 -0
  37. package/docs/more/40-why-timber.mdx +50 -0
  38. package/docs/more/41-timber-vs-nextjs.mdx +81 -0
  39. package/docs/more/42-timber-vs-others.mdx +68 -0
  40. package/llms.txt +55 -0
  41. package/package.json +6 -2
  42. package/src/cli.ts +62 -3
@@ -0,0 +1,168 @@
1
+ ---
2
+ title: 'Client Navigation'
3
+ description: 'The Link component, useRouter, segment cache, and scroll restoration.'
4
+ slug: 'client-navigation'
5
+ ---
6
+
7
+ # Client Navigation
8
+
9
+ timber.js supports client-side navigation via the `<Link>` component. Clicking a link fetches an RSC payload from the server and reconciles the DOM without a full page reload.
10
+
11
+ ## `<Link>`
12
+
13
+ ```tsx
14
+ import { Link } from '@timber-js/app/client';
15
+
16
+ <Link href="/about">About</Link>
17
+ <Link href="/products/123">Product</Link>
18
+ <Link href="/dashboard" replace>Dashboard</Link>
19
+ ```
20
+
21
+ `<Link>` type-checks `href` against the generated route map. Invalid routes produce a TypeScript error.
22
+
23
+ ## `useRouter`
24
+
25
+ For programmatic navigation:
26
+
27
+ ```tsx title="app/components/logout-button.tsx"
28
+ 'use client';
29
+
30
+ import { useRouter } from '@timber-js/app/client';
31
+
32
+ declare function logout(): Promise<void>;
33
+
34
+ export function LogoutButton() {
35
+ const router = useRouter();
36
+
37
+ async function handleLogout() {
38
+ await logout();
39
+ router.push('/login');
40
+ }
41
+
42
+ return <button onClick={handleLogout}>Log out</button>;
43
+ }
44
+ ```
45
+
46
+ | Method | Description |
47
+ | ---------------------- | ---------------------------------------- |
48
+ | `router.push(href)` | Navigate to a new URL |
49
+ | `router.replace(href)` | Navigate without adding a history entry |
50
+ | `router.refresh()` | Re-fetch the current route's RSC payload |
51
+ | `router.back()` | Go back in history |
52
+ | `router.forward()` | Go forward in history |
53
+
54
+ ## `usePathname`
55
+
56
+ Returns the current pathname:
57
+
58
+ ```tsx
59
+ 'use client';
60
+ import { usePathname, Link } from '@timber-js/app/client';
61
+
62
+ export function NavLink({ href, children }: { href: string; children: React.ReactNode }) {
63
+ const pathname = usePathname();
64
+ const isActive = pathname === href;
65
+ return (
66
+ <Link href={href} className={isActive ? 'font-bold' : ''}>
67
+ {children}
68
+ </Link>
69
+ );
70
+ }
71
+ ```
72
+
73
+ ## `useSelectedLayoutSegment`
74
+
75
+ Returns the active segment within a layout — useful for highlighting nav items:
76
+
77
+ ```tsx
78
+ 'use client';
79
+ import { useSelectedLayoutSegment } from '@timber-js/app/client';
80
+
81
+ export function DashboardNav() {
82
+ const segment = useSelectedLayoutSegment();
83
+ // segment is "settings", "projects", etc.
84
+ }
85
+ ```
86
+
87
+ ## `useLinkStatus`
88
+
89
+ Track per-link pending state during navigation:
90
+
91
+ ```tsx
92
+ 'use client';
93
+ import { Link, useLinkStatus } from '@timber-js/app/client';
94
+
95
+ function NavItemInner({ children }: { children: React.ReactNode }) {
96
+ const { pending } = useLinkStatus();
97
+ return <span className={pending ? 'opacity-50' : ''}>{children}</span>;
98
+ }
99
+
100
+ export function NavItem({ href, children }: { href: string; children: React.ReactNode }) {
101
+ return (
102
+ <Link href={href}>
103
+ <NavItemInner>{children}</NavItemInner>
104
+ </Link>
105
+ );
106
+ }
107
+ ```
108
+
109
+ ## Segment Cache
110
+
111
+ The client maintains a mirror of the server's segment tree. On navigation, only changed segments are re-fetched:
112
+
113
+ ```
114
+ /dashboard/settings → /dashboard/team
115
+
116
+ Root Layout ← sync, mounted → skip
117
+ Auth Layout ← sync, mounted → skip
118
+ Dashboard Layout ← sync, mounted → skip
119
+ Team Page ← new → fetch from server
120
+ ```
121
+
122
+ Sync layouts stay cached while mounted. Async layouts always re-render. Pages always re-render. Client component state in shared layouts is preserved — counters, form inputs, scroll positions survive navigation.
123
+
124
+ Back/forward navigation replays cached RSC payloads instantly — no server roundtrip.
125
+
126
+ ## Scroll Restoration
127
+
128
+ - **Forward navigation** scrolls to the top of the page.
129
+ - **Back/forward** restores the saved scroll position.
130
+ - **Hash fragments** — `<Link href="/docs/api#install">` commits the full URL (including the `#fragment`) to the address bar and scrolls to the matching element after render, just like a plain `<a>` without JavaScript. If no element matches, navigation falls back to scroll-to-top.
131
+
132
+ Pass `scroll={false}` to `<Link>` or `router.push` to preserve the current scroll position:
133
+
134
+ ```tsx
135
+ <Link href="/dashboard/settings" scroll={false}>Settings</Link>
136
+ ```
137
+
138
+ ```tsx
139
+ router.push('/dashboard/settings', { scroll: false });
140
+ ```
141
+
142
+ ### Custom Scroll Containers
143
+
144
+ If your layout uses a custom scrollable container, add `data-timber-scroll-restoration`:
145
+
146
+ ```tsx
147
+ <main className="overflow-y-auto h-screen" data-timber-scroll-restoration>
148
+ {children}
149
+ </main>
150
+ ```
151
+
152
+ ## `Link.onNavigate`
153
+
154
+ Intercept navigation for view transitions:
155
+
156
+ ```tsx
157
+ <Link
158
+ href="/gallery"
159
+ onNavigate={(e) => {
160
+ if (document.startViewTransition) {
161
+ e.preventDefault();
162
+ document.startViewTransition(() => e.navigate());
163
+ }
164
+ }}
165
+ >
166
+ Gallery
167
+ </Link>
168
+ ```
@@ -0,0 +1,117 @@
1
+ ---
2
+ title: 'Configuration'
3
+ description: 'timber.config.ts — output mode, adapters, caching, and the two-phase model.'
4
+ slug: 'configuration'
5
+ ---
6
+
7
+ # Configuration
8
+
9
+ timber.js is configured through `timber.config.ts` at the project root. Vite configuration lives separately in `vite.config.ts`.
10
+
11
+ ## Minimal Config
12
+
13
+ ```ts title="timber.config.ts"
14
+ export default {
15
+ output: 'server',
16
+ };
17
+ ```
18
+
19
+ That's it for most projects. The defaults are sensible.
20
+
21
+ ## How the Config Is Loaded
22
+
23
+ `timber.config.ts` is loaded by Node.js itself using built-in type stripping — it is not bundled by Vite. This requires Node.js 22.18 or newer, and the file must stick to erasable TypeScript syntax: type annotations, interfaces, and `satisfies` are fine, but enums, namespaces, and constructor parameter properties will fail to load. Your `package.json` should declare `"type": "module"` (the default for new timber projects) so the `export default` syntax parses correctly.
24
+
25
+ If the config file fails to load for any reason, every command — `timber dev`, `timber build`, and `timber preview` — stops with an error naming the file. A broken config never silently falls back to defaults.
26
+
27
+ ## Output Modes
28
+
29
+ | Mode | Server required | Client JS | Server Actions |
30
+ | -------------------------------------- | --------------- | ------------------------- | -------------- |
31
+ | `'server'` | Yes | Yes | Yes |
32
+ | `'static'` | No | Yes (hydration + SPA nav) | Via adapter |
33
+ | `'static'` + `clientJavascript: false` | No | None | Build error |
34
+
35
+ ```ts title="timber.config.ts"
36
+ // Static site with no JavaScript
37
+ export default {
38
+ output: 'static',
39
+ clientJavascript: false,
40
+ };
41
+ ```
42
+
43
+ ## Build Time vs Request Time
44
+
45
+ The `output` field shifts work between two phases:
46
+
47
+ **Build time** happens once when you run `timber build`. There's no request, no user, no cookies. Route scanning, client boundary discovery, server action extraction, font downloading, and asset bundling all happen here.
48
+
49
+ **Request time** happens per incoming HTTP request. Middleware, access checks, server components, caching, cookies, and server actions all run here.
50
+
51
+ | `output` | `middleware.ts` | Server components | Server actions |
52
+ | ---------- | --------------------- | ------------------------------ | ------------------------------------------- |
53
+ | `'server'` | Request time | Request time | Request time |
54
+ | `'static'` | **Build time only** | **Build time** (once, to HTML) | **Request time**, split-deployed as endpoints |
55
+
56
+ This is why `cookies()` and `headers()` raise warnings during a static build — there's no request at the moment components render.
57
+
58
+ ## Adapters
59
+
60
+ Adapters transform the build output for your deployment platform:
61
+
62
+ ```ts title="timber.config.ts"
63
+ import { cloudflare } from '@timber-js/app/adapters/cloudflare';
64
+
65
+ export default {
66
+ output: 'server',
67
+ adapter: cloudflare(),
68
+ };
69
+ ```
70
+
71
+ ```ts title="timber.config.ts"
72
+ import { nitro } from '@timber-js/app/adapters/nitro';
73
+
74
+ export default {
75
+ output: 'server',
76
+ adapter: nitro({ preset: 'node-server' }),
77
+ };
78
+ ```
79
+
80
+ `cloudflare()` for Cloudflare Workers. `nitro({ preset })` for everything else — Node.js, Vercel, Netlify, AWS Lambda, Deno, Bun, Azure.
81
+
82
+ ## Cache Handler
83
+
84
+ ```ts title="timber.config.ts"
85
+ import { MemoryCacheHandler } from '@timber-js/app/cache';
86
+
87
+ export default {
88
+ output: 'server',
89
+ cacheHandler: new MemoryCacheHandler(),
90
+ };
91
+ ```
92
+
93
+ The default is in-memory. See [Caching](/docs/caching) for Redis, KV, and custom handlers.
94
+
95
+ ## All Options
96
+
97
+ | Option | Type | Default | Description |
98
+ | ------------------ | ----------------------- | ---------------------------- | ------------------------- |
99
+ | `output` | `'server' \| 'static'` | `'server'` | Output mode |
100
+ | `adapter` | `TimberPlatformAdapter` | — | Deployment adapter |
101
+ | `cacheHandler` | `CacheHandler` | `MemoryCacheHandler` | Cache backend |
102
+ | `clientJavascript` | `boolean \| object` | `true` | Control client-side JS |
103
+ | `pageExtensions` | `string[]` | `['tsx', 'ts', 'jsx', 'js']` | File extensions for pages |
104
+ | `mdx` | `object` | — | MDX remark/rehype plugins |
105
+
106
+ For the full type definition, see the [Config API Reference](/docs/api-config).
107
+
108
+ ## .gitignore
109
+
110
+ Add these to your `.gitignore` — they're generated at build/dev time and should not be committed:
111
+
112
+ ```txt title=".gitignore"
113
+ .timber
114
+ .content-collections
115
+ ```
116
+
117
+ `.timber` contains the build output (default `.timber/dist`). `.content-collections` contains generated modules from [content collections](/docs/content-collections). `create-timber-app` adds both automatically.
@@ -0,0 +1,206 @@
1
+ ---
2
+ title: 'Deploying'
3
+ description: 'Deploy timber.js to Cloudflare Workers, Node.js, Vercel, Netlify, AWS Lambda, and more.'
4
+ slug: 'deploying'
5
+ ---
6
+
7
+ # Deploying
8
+
9
+ timber.js uses **adapters** to transform the build output for your platform. There are exactly two first-party adapters.
10
+
11
+ ## The Two Adapters
12
+
13
+ **`cloudflare()`** — Cloudflare Workers and Pages. Workers are architecturally different from Node.js (no file system, no `node:http`, different lifecycle model), so they get a dedicated adapter with direct binding access and native `waitUntil()`.
14
+
15
+ **`nitro({ preset })`** — Everything else. Pick the preset that matches your platform:
16
+
17
+ | Preset | Platform | `waitUntil()` |
18
+ | ----------------- | ---------------------- | ------------- |
19
+ | `node-server` | Node.js, Docker, VPS | ✅ |
20
+ | `bun` | Bun.serve() | ✅ |
21
+ | `vercel` | Vercel Serverless | ✅ |
22
+ | `vercel-edge` | Vercel Edge Functions | ✅ |
23
+ | `netlify` | Netlify Functions | ❌ |
24
+ | `netlify-edge` | Netlify Edge Functions | ✅ |
25
+ | `aws-lambda` | AWS Lambda | ❌ |
26
+ | `deno-deploy` | Deno Deploy | ✅ |
27
+ | `azure-functions` | Azure Functions | ❌ |
28
+
29
+ ## Build Commands
30
+
31
+ ```bash
32
+ timber build # Production build
33
+ timber preview # Local preview of the build output
34
+ ```
35
+
36
+ ## Cloudflare Workers
37
+
38
+ ```ts title="timber.config.ts"
39
+ import { cloudflare } from '@timber-js/app/adapters/cloudflare';
40
+
41
+ export default {
42
+ output: 'server',
43
+ adapter: cloudflare(),
44
+ };
45
+ ```
46
+
47
+ ```bash
48
+ timber build
49
+ wrangler deploy
50
+ ```
51
+
52
+ The adapter generates a `wrangler.jsonc` with sensible defaults. Access KV, D1, R2, and Queues bindings via `getCloudflareBindings()`:
53
+
54
+ ```ts
55
+ import { getCloudflareBindings } from '@timber-js/app/adapters/cloudflare';
56
+
57
+ export default async function Page() {
58
+ const { MY_KV, MY_DB } = getCloudflareBindings();
59
+ const data = await MY_KV.get('key');
60
+ // ...
61
+ }
62
+ ```
63
+
64
+ For caching, use `CloudflareKVCacheHandler`:
65
+
66
+ ```ts title="timber.config.ts"
67
+ import { cloudflare } from '@timber-js/app/adapters/cloudflare';
68
+ import { CloudflareKVCacheHandler } from '@timber-js/app/adapters/cloudflare/cache';
69
+
70
+ export default {
71
+ output: 'server',
72
+ adapter: cloudflare(),
73
+ cacheHandler: new CloudflareKVCacheHandler({ bindingName: 'TIMBER_CACHE' }),
74
+ };
75
+ ```
76
+
77
+ **Constraints:** no file system, 10ms/30s CPU time limits, 10MB/25MB bundle size.
78
+
79
+ ## Node.js & Docker
80
+
81
+ ```ts title="timber.config.ts"
82
+ import { nitro } from '@timber-js/app/adapters/nitro';
83
+
84
+ export default {
85
+ output: 'server',
86
+ adapter: nitro({ preset: 'node-server' }),
87
+ };
88
+ ```
89
+
90
+ ```bash
91
+ timber build
92
+ node .timber/dist/nitro/.output/server/index.mjs
93
+ ```
94
+
95
+ A dedicated server is timber's sweet spot: no cold starts, warm in-process cache, and the ability to co-locate with your database. Five sequential fetches that take 1000ms from serverless take 10ms co-located.
96
+
97
+ ### Docker
98
+
99
+ ```dockerfile
100
+ FROM node:24-slim AS build
101
+ RUN corepack enable
102
+ WORKDIR /app
103
+ COPY package.json pnpm-lock.yaml ./
104
+ RUN pnpm install --frozen-lockfile
105
+ COPY . .
106
+ RUN pnpm run build
107
+
108
+ FROM node:24-slim AS runtime
109
+ RUN corepack enable
110
+ RUN groupadd --system timber && useradd --system --gid timber --create-home timber
111
+ WORKDIR /app
112
+ COPY --from=build --chown=timber:timber /app/.timber/dist/nitro/.output ./.output
113
+ COPY --from=build --chown=timber:timber /app/package.json /app/pnpm-lock.yaml ./
114
+ RUN pnpm install --prod --frozen-lockfile
115
+ USER timber
116
+ ENV NODE_ENV=production PORT=3000 HOST=0.0.0.0
117
+ EXPOSE 3000
118
+ CMD ["node", ".output/server/index.mjs"]
119
+ ```
120
+
121
+ For multiple replicas, switch from `MemoryCacheHandler` to `RedisCacheHandler`.
122
+
123
+ ## Vercel
124
+
125
+ ```ts title="timber.config.ts"
126
+ import { nitro } from '@timber-js/app/adapters/nitro';
127
+
128
+ export default {
129
+ output: 'server',
130
+ adapter: nitro({ preset: 'vercel' }),
131
+ };
132
+ ```
133
+
134
+ ```bash
135
+ timber build
136
+ vercel deploy --prebuilt
137
+ ```
138
+
139
+ Or connect your git repository — Vercel deploys automatically on push. Use `vercel-edge` preset for Edge Functions. Serverless functions are ephemeral — always use an external cache handler.
140
+
141
+ ## Netlify
142
+
143
+ ```ts title="timber.config.ts"
144
+ import { nitro } from '@timber-js/app/adapters/nitro';
145
+
146
+ export default {
147
+ output: 'server',
148
+ adapter: nitro({ preset: 'netlify' }),
149
+ };
150
+ ```
151
+
152
+ ```bash
153
+ timber build
154
+ netlify deploy --prod
155
+ ```
156
+
157
+ Netlify Functions (Lambda-based) don't support `waitUntil()`. Use `netlify-edge` for Deno-based edge execution with `waitUntil()` support.
158
+
159
+ ## AWS Lambda
160
+
161
+ ```ts title="timber.config.ts"
162
+ import { nitro } from '@timber-js/app/adapters/nitro';
163
+
164
+ export default {
165
+ output: 'server',
166
+ adapter: nitro({ preset: 'aws-lambda' }),
167
+ };
168
+ ```
169
+
170
+ Deploy with CDK, SAM, Serverless Framework, or Terraform. Lambda doesn't support `waitUntil()` — use SQS or EventBridge for background work. Use provisioned concurrency to mitigate cold starts.
171
+
172
+ ## Deno, Azure & Bun
173
+
174
+ ```ts title="timber.config.ts"
175
+ import { nitro } from '@timber-js/app/adapters/nitro';
176
+
177
+ // Deno Deploy
178
+ export default { output: 'server', adapter: nitro({ preset: 'deno-deploy' }) };
179
+
180
+ // Azure Functions
181
+ export default { output: 'server', adapter: nitro({ preset: 'azure-functions' }) };
182
+
183
+ // Bun
184
+ export default { output: 'server', adapter: nitro({ preset: 'bun' }) };
185
+ ```
186
+
187
+ ## Static Export
188
+
189
+ For sites that don't need a server:
190
+
191
+ ```ts title="timber.config.ts"
192
+ export default {
193
+ output: 'static',
194
+ };
195
+ ```
196
+
197
+ Pre-renders every route to HTML at build time. Add `clientJavascript: false` for zero-JavaScript output. Deploy to any static file host — S3, CloudFront, Cloudflare Pages, GitHub Pages, Netlify, or any HTTP server.
198
+
199
+ ## Cache Handler Selection
200
+
201
+ | Deployment | Recommended Handler | Why |
202
+ | --------------------------- | -------------------- | --------------------------------------------- |
203
+ | Single server (VPS, Docker) | `MemoryCacheHandler` | In-process LRU, no external dependency |
204
+ | Multi-instance (k8s, ECS) | `RedisCacheHandler` | Shared store, tag invalidation across replicas |
205
+ | Cloudflare Workers | `KVCacheHandler` | Workers KV, global edge distribution |
206
+ | Serverless (Vercel, Lambda) | `RedisCacheHandler` | Ephemeral functions need an external store |
@@ -0,0 +1,139 @@
1
+ ---
2
+ title: 'Advanced Routing'
3
+ description: 'Route groups, parallel routes (slots), intercepting routes, catch-all segments, and API routes.'
4
+ slug: 'advanced-routing'
5
+ ---
6
+
7
+ # Advanced Routing
8
+
9
+ This page covers routing patterns beyond the basics in [Pages and Layouts](/docs/pages-and-layouts). It assumes you understand `page.tsx`, `layout.tsx`, and nesting.
10
+
11
+ ## Dynamic Segments
12
+
13
+ Wrap a directory name in brackets to capture a URL parameter:
14
+
15
+ ```
16
+ app/
17
+ products/
18
+ [id]/
19
+ page.tsx # /products/42, /products/abc
20
+ ```
21
+
22
+ Access the param with `getSegmentParams(SEGMENT_PATH)` using the `$segment` module — see [Typed Params](/docs/typed-params) for the full codec system.
23
+
24
+ ## Catch-All Segments
25
+
26
+ ```
27
+ app/docs/[...slug]/page.tsx # /docs/a, /docs/a/b, /docs/a/b/c
28
+ app/docs/[[...slug]]/page.tsx # Same, plus /docs (zero segments)
29
+ ```
30
+
31
+ With optional catch-all segments (`[[...slug]]`), the param is `undefined` when no segments match — not an empty array:
32
+
33
+ | URL | `slug` value |
34
+ | ------------- | ----------------- |
35
+ | `/docs` | `undefined` |
36
+ | `/docs/intro` | `['intro']` |
37
+ | `/docs/a/b/c` | `['a', 'b', 'c']` |
38
+
39
+ ### Not Supported: Optional Single Segments
40
+
41
+ timber does not support `[[foo]]` (optional single dynamic segment). Use `[[...foo]]` and check `slug?.[0]`, or create two separate pages:
42
+
43
+ ```
44
+ app/blog/page.tsx # /blog (index)
45
+ app/blog/[year]/page.tsx # /blog/2024 (filtered)
46
+ ```
47
+
48
+ ## Parallel Routes (Slots)
49
+
50
+ Directories prefixed with `@` are slots — they render simultaneously within a parent layout:
51
+
52
+ ```
53
+ app/
54
+ dashboard/
55
+ layout.tsx # Receives { children, sidebar }
56
+ page.tsx # Renders as children
57
+ @sidebar/
58
+ page.tsx # Renders as sidebar prop
59
+ default.tsx # Fallback when no match
60
+ ```
61
+
62
+ ```tsx title="app/dashboard/layout.tsx"
63
+ export default function DashboardLayout({
64
+ children,
65
+ sidebar,
66
+ }: {
67
+ children: React.ReactNode;
68
+ sidebar: React.ReactNode;
69
+ }) {
70
+ return (
71
+ <div className="flex">
72
+ <aside className="w-64">{sidebar}</aside>
73
+ <main className="flex-1">{children}</main>
74
+ </div>
75
+ );
76
+ }
77
+ ```
78
+
79
+ Slots don't add URL depth. Each slot independently matches the current URL and can have its own `access.ts`, `error.tsx`, and layouts. If a slot's access check fails, the slot degrades gracefully — the rest of the page is unaffected (see [Access Control](/docs/access-control)).
80
+
81
+ Each slot is also its own collision space: a slot page sharing a URL with the parent page is the point of parallel routes, but two route groups inside one slot producing pages at the same URL (`@side/(a)/x/page.tsx` + `@side/(b)/x/page.tsx`) fail the build, just like the same collision outside a slot.
82
+
83
+ ## Intercepting Routes
84
+
85
+ Intercepting routes show a different component on soft navigation (client-side link click) vs hard navigation (direct URL). This enables the modal pattern:
86
+
87
+ | Marker | Meaning |
88
+ | ---------- | ------------- |
89
+ | `(.)` | Same level |
90
+ | `(..)` | One level up |
91
+ | `(..)(..)` | Two levels up |
92
+ | `(...)` | Root level |
93
+
94
+ ## API Routes
95
+
96
+ Export HTTP method handlers from `route.ts`:
97
+
98
+ ```ts title="app/api/hello/route.ts"
99
+ export async function GET(ctx: RouteContext) {
100
+ return Response.json({ message: 'Hello' });
101
+ }
102
+
103
+ export async function POST(ctx: RouteContext) {
104
+ const body = await ctx.request.json();
105
+ return Response.json({ received: body });
106
+ }
107
+ ```
108
+
109
+ ## The Segment Tree
110
+
111
+ Every directory in your `app/` directory is a **segment**. Segments nest into a tree that connects your URL structure, component structure, and HTML structure:
112
+
113
+ | Concern | Segment mapping |
114
+ | ----------------- | ------------------------------------------------------------- |
115
+ | URL matching | Each segment consumes URL segments (except groups and slots) |
116
+ | Params | Dynamic segments extract params, accumulate top-down |
117
+ | Authorization | `access.ts` per segment, top-down, shallowest failure wins |
118
+ | Layouts | `layout.tsx` per segment, persist across navigation |
119
+ | Streaming | `<Suspense>` placement controls flush boundary per segment |
120
+ | Error handling | `error.tsx` / status files per segment, leaf-to-root fallback |
121
+ | Client navigation | Segment cache mirrors the tree, skips mounted sync layouts |
122
+ | Slot isolation | `@slot` branches render independently, fail gracefully |
123
+
124
+ ## The Request Lifecycle
125
+
126
+ ``` leading="none"
127
+ Request
128
+ ├── URL canonicalization
129
+ ├── proxy.ts (global — every request)
130
+ ├── Route matching
131
+ ├── Param coercion (schema.ts codecs)
132
+ ├── middleware.ts chain (root → leaf)
133
+ └── Single renderToReadableStream call
134
+ Root Segment → AccessGate → Layout
135
+ Child Segment → AccessGate → Layout
136
+ Leaf Segment → Page
137
+ ```
138
+
139
+ The entire route renders in a **single `renderToReadableStream` call**. One React tree, one `React.cache` scope.