@remix-run/cli 0.2.0 → 0.3.1

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 (87) hide show
  1. package/dist/lib/bootstrap-project.d.ts.map +1 -1
  2. package/dist/lib/bootstrap-project.js +32 -9
  3. package/dist/lib/cli.d.ts +29 -0
  4. package/dist/lib/cli.d.ts.map +1 -1
  5. package/dist/lib/cli.js +26 -4
  6. package/dist/lib/commands/completion.d.ts.map +1 -1
  7. package/dist/lib/commands/completion.js +5 -1
  8. package/dist/lib/commands/doctor.js +18 -18
  9. package/dist/lib/commands/routes.js +3 -3
  10. package/dist/lib/completion.js +1 -5
  11. package/dist/lib/controller-files.d.ts +0 -1
  12. package/dist/lib/controller-files.d.ts.map +1 -1
  13. package/dist/lib/controller-files.js +3 -5
  14. package/dist/lib/controller-ownership.d.ts +9 -9
  15. package/dist/lib/controller-ownership.d.ts.map +1 -1
  16. package/dist/lib/controller-ownership.js +56 -91
  17. package/dist/lib/doctor/controller-findings.d.ts +1 -1
  18. package/dist/lib/doctor/controller-findings.d.ts.map +1 -1
  19. package/dist/lib/doctor/controller-findings.js +15 -87
  20. package/dist/lib/doctor/controller-fix-plans.d.ts.map +1 -1
  21. package/dist/lib/doctor/controller-fix-plans.js +13 -24
  22. package/dist/lib/doctor/controller-placeholders.d.ts +2 -3
  23. package/dist/lib/doctor/controller-placeholders.d.ts.map +1 -1
  24. package/dist/lib/doctor/controller-placeholders.js +18 -149
  25. package/dist/lib/doctor/controllers.js +1 -1
  26. package/dist/lib/doctor/project.js +60 -52
  27. package/dist/lib/doctor/types.d.ts +2 -2
  28. package/dist/lib/doctor/types.d.ts.map +1 -1
  29. package/dist/lib/help-text.js +1 -1
  30. package/dist/lib/load-route-map-worker.js +17 -9
  31. package/dist/lib/route-map.d.ts +1 -1
  32. package/dist/lib/route-map.d.ts.map +1 -1
  33. package/dist/lib/route-map.js +29 -17
  34. package/package.json +5 -5
  35. package/src/lib/bootstrap-project.ts +39 -13
  36. package/src/lib/cli.ts +39 -4
  37. package/src/lib/commands/completion.ts +6 -1
  38. package/src/lib/commands/doctor.ts +18 -21
  39. package/src/lib/commands/routes.ts +3 -3
  40. package/src/lib/completion.ts +1 -5
  41. package/src/lib/controller-files.ts +4 -8
  42. package/src/lib/controller-ownership.ts +78 -141
  43. package/src/lib/doctor/controller-findings.ts +20 -97
  44. package/src/lib/doctor/controller-fix-plans.ts +13 -29
  45. package/src/lib/doctor/controller-placeholders.ts +17 -189
  46. package/src/lib/doctor/controllers.ts +1 -1
  47. package/src/lib/doctor/project.ts +60 -52
  48. package/src/lib/doctor/types.ts +1 -5
  49. package/src/lib/help-text.ts +3 -2
  50. package/src/lib/load-route-map-worker.ts +19 -10
  51. package/src/lib/route-map.ts +61 -16
  52. package/{bootstrap → template}/.agents/skills/remix/SKILL.md +162 -77
  53. package/{bootstrap → template}/.agents/skills/remix/references/assets-and-browser-modules.md +22 -14
  54. package/{bootstrap → template}/.agents/skills/remix/references/auth-and-sessions.md +41 -18
  55. package/{bootstrap → template}/.agents/skills/remix/references/component-model.md +4 -1
  56. package/{bootstrap → template}/.agents/skills/remix/references/data-and-validation.md +21 -5
  57. package/{bootstrap → template}/.agents/skills/remix/references/middleware-and-server.md +42 -53
  58. package/{bootstrap → template}/.agents/skills/remix/references/routing-and-controllers.md +111 -44
  59. package/{bootstrap → template}/.agents/skills/remix/references/testing-patterns.md +17 -1
  60. package/{bootstrap → template}/AGENTS.md +10 -9
  61. package/template/README.md +29 -0
  62. package/template/app/actions/controller.tsx +18 -0
  63. package/template/app/assets/entry.ts +8 -0
  64. package/{bootstrap/app/ui → template/app/assets}/prompt-button.tsx +2 -1
  65. package/{bootstrap → template}/app/assets.ts +5 -3
  66. package/template/app/middleware/render.tsx +44 -0
  67. package/template/app/router.ts +20 -0
  68. package/{bootstrap → template}/app/routes.ts +1 -2
  69. package/{bootstrap → template}/app/ui/document.tsx +11 -4
  70. package/{bootstrap → template}/app/ui/scaffold-home-page.tsx +39 -38
  71. package/template/gitignore +4 -0
  72. package/{bootstrap → template}/package.json +5 -6
  73. package/template/public/favicon.svg +11 -0
  74. package/template/server.ts +38 -0
  75. package/{bootstrap → template}/tsconfig.json +3 -2
  76. package/bootstrap/README.md +0 -27
  77. package/bootstrap/app/assets/entry.ts +0 -19
  78. package/bootstrap/app/controllers/auth.tsx +0 -21
  79. package/bootstrap/app/controllers/home.tsx +0 -11
  80. package/bootstrap/app/router.ts +0 -16
  81. package/bootstrap/app/ui/layout.tsx +0 -22
  82. package/bootstrap/app/utils/render.tsx +0 -26
  83. package/bootstrap/server.ts +0 -37
  84. /package/{bootstrap → template}/.agents/skills/remix/references/animate-elements.md +0 -0
  85. /package/{bootstrap → template}/.agents/skills/remix/references/create-mixins.md +0 -0
  86. /package/{bootstrap → template}/.agents/skills/remix/references/hydration-frames-navigation.md +0 -0
  87. /package/{bootstrap → template}/.agents/skills/remix/references/mixins-styling-events.md +0 -0
@@ -5,9 +5,22 @@ description: Build and review Remix 3 applications using the `remix` npm package
5
5
 
6
6
  # Build a Remix App
7
7
 
8
- Use this skill for end-to-end Remix app work. It should help the agent choose the right layer
8
+ Use this skill for end-to-end Remix app work. This skill helps you choose the right layer
9
9
  first, reach for the right package, and avoid the most common Remix-specific mistakes.
10
10
 
11
+ ## Full Package Documentation
12
+
13
+ This skill is the quick guide. When you need fuller API documentation, examples, or package-specific
14
+ details for a `remix/*` subpath, first look for a README next to the relevant generated source file
15
+ in the published `remix` package:
16
+ `node_modules/remix/src/<subpath>/README.md`. If that README does not exist, look for the nearest
17
+ parent README because some subpaths share their parent package documentation.
18
+
19
+ Examples:
20
+
21
+ - `remix/router` -> `node_modules/remix/src/fetch-router/README.md`
22
+ - `remix/ui/button` -> `node_modules/remix/src/ui/button/README.md`
23
+
11
24
  ## What Remix Is
12
25
 
13
26
  Remix 3 is a server-first web framework built on Web APIs such as `Request`, `Response`, `URL`,
@@ -17,11 +30,11 @@ subpath. There is no top-level `remix` import.
17
30
  A Remix app has four main pieces:
18
31
 
19
32
  - **Routes** in `app/routes.ts` define the typed URL contract and power `href()` generation.
20
- - **Controllers and actions** implement that contract and return `Response` objects.
33
+ - **Controllers** in `app/actions` implement that contract and return `Response` objects.
21
34
  - **Middleware** composes request lifecycle behavior and populates typed context via
22
35
  `context.set(Key, value)`.
23
36
  - **Components** render UI with `remix/ui`. This is not React. A component receives a
24
- `handle`, reads current props from `handle.props`, and returns a render function.
37
+ `handle`, reads current props from `handle.props`, and returns a zero-argument render function.
25
38
 
26
39
  ## When To Use This Skill
27
40
 
@@ -101,7 +114,8 @@ Use these root directories consistently:
101
114
  Inside `app/`, organize by responsibility:
102
115
 
103
116
  - `assets/` for client entrypoints and client-owned browser behavior
104
- - `controllers/` for route-owned handlers and route-local UI
117
+ - `actions/` for controller-owned route handlers, route-local response rendering, and route-local
118
+ UI/helpers that are not shared across route areas
105
119
  - `data/` for schema, queries, persistence setup, migrations, and runtime data initialization
106
120
  - `middleware/` for request lifecycle concerns such as auth, sessions, uploads, and database
107
121
  injection
@@ -123,31 +137,56 @@ When code could live in multiple places:
123
137
 
124
138
  ### Route Ownership
125
139
 
126
- - Use a flat file in `app/controllers/` for a simple leaf action, such as `app/controllers/home.tsx`
127
- - Use a folder with `controller.tsx` when a route owns nested routes or multiple actions, such as
128
- `app/controllers/account/controller.tsx`
129
- - Mirror nested route structure on disk, such as `app/controllers/auth/login/controller.tsx`
130
- - Keep route-local UI next to its owner, such as `app/controllers/contact/page.tsx`
131
- - Move shared UI to `app/ui/`
132
- - If a flat leaf grows child routes or multiple actions, promote it to a controller folder
140
+ - Put top-level leaf actions in `app/actions/controller.tsx`
141
+ - A controller's `actions` object contains only direct leaf route keys from the route map passed to
142
+ `router.map(...)`
143
+ - Add `app/actions/<route-key>/controller.tsx` for each nested route map that needs actions or
144
+ middleware, and map it explicitly with `router.map(routes.<routeKey>, controller)`
145
+ - Name directories under `app/actions/` after route-map keys, not URL path segments
146
+ - Keep route-local UI and helpers next to the controller that owns them
147
+ - Move shared cross-route UI to `app/ui/`
148
+ - If a top-level leaf grows into a route map, move its handler into the nested route-key
149
+ controller and update `app/router.ts` to map that route map explicitly
150
+
151
+ ### Response Rendering And Utilities
152
+
153
+ - Treat response rendering as action-layer code: modules that return `Response`, choose HTTP status
154
+ or headers, call `redirect(...)`, or call the local `render(...)` helper belong in `app/actions`
155
+ - Keep `app/actions/render.tsx` small; it should adapt `remix/ui/server` output to
156
+ `createHtmlResponse(...)`. Route-specific response assembly can live in flat action modules, but
157
+ directories under `app/actions/` must still match route-map keys
158
+ - Put pure support code in focused `app/utils/<topic>.ts` modules. Formatting, MIME
159
+ classification, path parsing, sorting, and normalization should be testable without a router,
160
+ request context, or `Response`, and should not import from `app/actions`, `remix/ui/server`, or
161
+ `remix/response/*`
162
+ - Do not introduce page-data intermediary shapes only to keep route-specific renderers away from
163
+ `render(...)`; keep response assembly in actions and extract only the pure helpers
133
164
 
134
165
  ### Layout Anti-Patterns
135
166
 
136
167
  - Do not create `app/lib/` as a generic dumping ground
137
168
  - Do not create `app/components/` as a second shared UI bucket when `app/ui/` already owns that
138
169
  role
139
- - Do not put shared cross-route UI in `app/controllers/`
170
+ - Do not create `app/controllers/`; Remix app route handlers live under `app/actions/`
171
+ - Do not put shared cross-route UI in `app/actions/`
172
+ - Do not create standalone root action files; put root route actions in `app/actions/controller.tsx`
173
+ - Do not put nested route-map keys in a controller's `actions`
174
+ - Do not register normal app leaf routes directly in `app/router.ts` when they belong in a
175
+ controller
176
+ - Do not rely on middleware from one controller to protect another controller; map middleware
177
+ explicitly in each controller that needs it
140
178
  - Do not put middleware or persistence helpers in `app/utils/` when they have a clearer home
141
- - Do not create folders for simple leaf actions unless they are real controllers
142
179
 
143
180
  ## Core Remix Rules
144
181
 
145
182
  - Import from `remix/<subpath>`, never `import { ... } from 'remix'`
146
183
  - Treat `app/routes.ts` as the source of truth for URLs. Use `routes.<name>.href(...)` for
147
184
  redirects, links, tests, and internal URL construction
148
- - Controllers and actions should return explicit `Response` objects, including redirects, 404s, and
185
+ - Controllers should return explicit `Response` objects, including redirects, 404s, and
149
186
  validation failures. At the route boundary, prefer returning a `Response` for expected outcomes
150
187
  (validation errors, conflicts, not found) over throwing for control flow
188
+ - `router.map(routes, controller)` maps only the direct leaf routes in `routes`; nested route maps
189
+ must be mapped with their own explicit controllers
151
190
  - Model HTTP behavior explicitly. Status codes, headers, redirects, cache rules, and content types
152
191
  are part of the route contract
153
192
  - Make the server route correct first. A POST should already return the right HTML, redirect, or
@@ -159,9 +198,9 @@ When code could live in multiple places:
159
198
  need the harness
160
199
  - Outside actions and controllers, only use `getContext()` when `asyncContext()` is in the
161
200
  middleware stack
162
- - Remix Component is not React: read props from `handle.props`, keep state in setup-scope
163
- variables, call `handle.update()` explicitly, and do DOM-sensitive work in event handlers or
164
- `queueTask(...)`, not in render
201
+ - Remix Component is not React: write `function Name(handle: Handle<Props>) { return () => ... }`,
202
+ read props from `handle.props`, keep state in setup-scope variables, call `handle.update()`
203
+ explicitly, and do DOM-sensitive work in event handlers or `queueTask(...)`, not in render
165
204
  - Prefer host-element mixins via `mix={mixin(...)}` for behavior and styling instead of inventing
166
205
  custom host prop conventions. Use `mix={[...]}` only when composing multiple mixins
167
206
  - Hydrated `clientEntry(...)` props must be serializable. Do not pass functions, class instances, or
@@ -185,11 +224,16 @@ When code could live in multiple places:
185
224
 
186
225
  - Prefer server and router tests first. Drive the app with `router.fetch(new Request(...))` and
187
226
  assert on the returned `Response`
227
+ - Keep controller tests shaped like controllers: root route behavior belongs in
228
+ `app/actions/controller.test.ts(x)`, and nested route-map behavior belongs beside that route-key
229
+ controller
188
230
  - Build a fresh router per test or per suite so sessions, in-memory storage, and database state
189
231
  stay isolated
190
232
  - Use `routes.<name>.href(...)` in tests so URLs stay coupled to the route contract
191
233
  - For auth or session scenarios, use a test cookie and `createMemorySessionStorage()` instead of
192
234
  production storage
235
+ - Co-locate tests for pure `app/utils` helpers beside their modules. Test response behavior through
236
+ router or controller tests
193
237
  - Use component tests only for interactive or DOM-specific behavior. Render with `createRoot(...)`,
194
238
  interact with the real DOM, and call `root.flush()` between steps
195
239
  - Prefer one representative behavior test over many repetitive assertion variants
@@ -216,6 +260,13 @@ When code could live in multiple places:
216
260
  - Assuming authentication is enough without per-resource authorization checks
217
261
  - Dropping shared code into vague buckets like `utils.ts`, `helpers.ts`, or `common.ts` when
218
262
  ownership is known
263
+ - Recreating the old `app/controllers` or standalone root action file layout instead of using
264
+ controllers under `app/actions`
265
+ - Putting nested route-map keys inside a controller `actions` object. Map nested route maps
266
+ explicitly in `app/router.ts`
267
+ - Treating direct `router.get(...)`/`router.post(...)` registrations as the default app structure
268
+ instead of using controllers
269
+ - Assuming controller middleware applies to controllers registered for nested route maps
219
270
  - Writing only component tests for a feature whose main behavior is really an HTTP route concern
220
271
 
221
272
  ## Package Map
@@ -225,17 +276,20 @@ what it exports. Open the linked reference file when you need full examples.
225
276
 
226
277
  ### Routing, Server, and Responses
227
278
 
228
- - `remix/fetch-router` — the router itself. Use for `createRouter`, controller and middleware
279
+ - `remix/router` — the router itself. Use for `createRouter`, controller and middleware
229
280
  types, and registering routes
230
- - `remix/fetch-router/routes` — declarative route builders. Use for `route`, `get`, `post`, `put`,
231
- `del`, `form`, `resources` when defining `app/routes.ts`
232
- - `remix/node-fetch-server` — adapter from Node's `http` module to a Fetch-style router. Use for
233
- `createRequestListener` in `server.ts`
281
+ - `remix/routes` — declarative route builders. Use for `route`, `get`, `post`, `put`, `del`,
282
+ `form`, `resources` when defining `app/routes.ts`
283
+ - `remix/node-fetch-server` — default Node adapter for new apps. Use `createRequestListener` with
284
+ `node:http`, `node:https`, or `node:http2` in `server.ts` when booting the template-style app
234
285
  - `remix/assets` — browser asset server. Use for `createAssetServer` when serving compiled
235
- scripts and styles, getting public hrefs, and emitting preloads. Shared compiler options such as
236
- `target`, `sourceMaps`, `sourceMapSourcePaths`, and `minify` live at the top level
237
- - `remix/headers` — typed header parsers and builders. Use when reading `Accept`, `Cookie`, or
238
- setting `CacheControl`, `Vary`, etc., instead of hand-formatting strings
286
+ scripts and styles, getting public hrefs, and emitting preloads. Configure a `basePath`, and
287
+ keep `fileMap` URL patterns relative to it. Shared compiler options such as `target`,
288
+ `sourceMaps`, `sourceMapSourcePaths`, and `minify` live at the top level
289
+ - `remix/headers` `SuperHeaders` plus typed header parsers and builders. Use the default export
290
+ when you want a `Headers` subclass with typed accessors like `headers.contentType`,
291
+ `headers.cacheControl`, and `headers.setCookie`; use named classes such as `CacheControl`,
292
+ `ContentDisposition`, and `Vary` when working with individual header values
239
293
  - `remix/response/redirect` — `redirect(href, status?)`. Use for the canonical "POST then redirect"
240
294
  pattern and other location changes
241
295
  - `remix/response/html` — `createHtmlResponse`. Use when you need an HTML `Response` from a string
@@ -244,10 +298,14 @@ what it exports. Open the linked reference file when you need full examples.
244
298
  the global `compression()` middleware
245
299
  - `remix/response/file` — file-download responses. Use for `Content-Disposition: attachment`
246
300
  responses
247
- - `remix/route-pattern` — low-level URL matching and generation. Use when working with raw
248
- patterns outside the router (custom matchers, scripts)
301
+ - `remix/route-pattern` — low-level URL matching and generation. Use `RoutePattern` or
302
+ `createMatcher` when working with raw patterns outside the router. `href(...)` encodes pathname
303
+ and search params for you, and `match(...)` returns decoded params
304
+ - `remix/route-pattern/specificity` — pattern ranking helpers. Use only when building custom
305
+ matcher or reporting logic outside the normal router/matcher APIs
249
306
  - `remix/fetch-proxy` — Fetch-based HTTP proxying. Use to forward a request to another origin; pass
250
- `xForwardedHeaders` when the upstream needs forwarded proto, host, and port
307
+ `xForwardedHeaders` when the upstream needs forwarded proto, host, and port. It also rewrites
308
+ proxied `Set-Cookie` domain/path attributes by default
251
309
 
252
310
  ### Data, Validation, and Persistence
253
311
 
@@ -260,9 +318,11 @@ what it exports. Open the linked reference file when you need full examples.
260
318
  Use when input arrives as a string but should be a typed value
261
319
  - `remix/data-schema/form-data` — `f.object` and `f.field` for parsing `FormData` directly. Use
262
320
  in actions that read browser forms
321
+ - `remix/data-schema/lazy` — recursive or mutually-referential schemas. Use when a schema needs to
322
+ refer to itself or another schema that is declared later
263
323
  - `remix/data-table` — typed tables and a `Database` interface. Use for `table`, `column`,
264
324
  `createDatabase` when modeling persisted data
265
- - `remix/data-table-sqlite`, `remix/data-table-postgres`, `remix/data-table-mysql` — adapters.
325
+ - `remix/data-table/sqlite`, `remix/data-table/postgres`, `remix/data-table/mysql` — adapters.
266
326
  Use to back `createDatabase` with a real engine. SQLite accepts Node, Bun, and compatible
267
327
  synchronous clients with the shared `prepare`/`exec` surface
268
328
  - `remix/data-table/migrations` — migration authoring and runners. Use for `createMigration`,
@@ -271,27 +331,29 @@ what it exports. Open the linked reference file when you need full examples.
271
331
  apply migrations
272
332
  - `remix/data-table/operators` — query operators such as `inList(...)`. Use when `where` clauses
273
333
  need set or comparison logic
334
+ - `remix/data-table/sql-helpers` — SQL helper utilities for adapter or advanced query work. Avoid
335
+ this in normal app code unless you are intentionally working below the table/query API
274
336
 
275
337
  ### Auth, Sessions, and Cookies
276
338
 
277
339
  - `remix/session` — the `Session` object: `get`, `set`, `flash`, `unset`, `regenerateId`. Use for
278
340
  any per-browser state where tampering would be a bug (login, "I submitted this form already",
279
341
  cart, flash messages)
280
- - `remix/session-middleware` — `session(cookie, storage)`. Use to wire a session cookie and
342
+ - `remix/middleware/session` — `session(cookie, storage)`. Use to wire a session cookie and
281
343
  storage backend into the root middleware stack
282
- - `remix/session/fs-storage`, `remix/session/memory-storage`, `remix/session/cookie-storage` —
344
+ - `remix/session-storage/fs`, `remix/session-storage/memory`, `remix/session-storage/cookie` —
283
345
  storage backends. Use `fs-storage` for single-process apps, `memory-storage` for tests,
284
346
  `cookie-storage` for stateless deployments where data fits in a cookie
285
- - `remix/session-storage-redis` — Redis-backed storage. Use for multi-process or multi-host
347
+ - `remix/session-storage/redis` — Redis-backed storage. Use for multi-process or multi-host
286
348
  deployments
287
- - `remix/session-storage-memcache` — Memcache-backed storage. Same multi-host use case as Redis
349
+ - `remix/session-storage/memcache` — Memcache-backed storage. Same multi-host use case as Redis
288
350
  - `remix/cookie` — `createCookie` for plain signed/unsigned cookies. Use for non-sensitive
289
351
  preferences where the client is allowed to control the value (theme, locale, dismissed banner).
290
352
  For state where tampering matters, prefer `remix/session`
291
353
  - `remix/auth` — credentials, OAuth, OIDC, and Atmosphere providers. Use to define how identity is
292
354
  verified, start/finish external login, and refresh stored OAuth/OIDC token bundles with
293
355
  `refreshExternalAuth(...)`
294
- - `remix/auth-middleware` — `auth({ schemes })`, `requireAuth`, the `Auth` context key. Use to
356
+ - `remix/middleware/auth` — `auth({ schemes })`, `requireAuth`, the `Auth` context key. Use to
295
357
  resolve identity into the request context and to gate routes
296
358
 
297
359
  ### UI, Hydration, and Browser Behavior
@@ -299,62 +361,75 @@ what it exports. Open the linked reference file when you need full examples.
299
361
  - `remix/ui` — the component runtime: components, core mixins, `clientEntry`, `run`, `<Frame>`,
300
362
  navigation helpers, and `createRoot`. Use for app UI behavior
301
363
  - `remix/ui/server` — server rendering: `renderToStream`, `renderToString`. Use in the
302
- `render(...)` helper that returns HTML responses
364
+ `app/actions/render.tsx` helper that returns HTML responses
303
365
  - `remix/ui/animation` — animation APIs: `animateEntrance`, `animateExit`, `animateLayout`,
304
366
  `spring`, `tween`, and `easings`
305
- - `remix/ui/<primitive>` — UI primitives, mixins, glyphs, and theme helpers. Import from
306
- `remix/ui/accordion`, `remix/ui/button`, `remix/ui/select`, etc.
367
+ - `remix/ui/<primitive>` — UI primitives, mixins, glyphs, and theme helpers. Current subpaths
368
+ include `remix/ui/accordion`, `remix/ui/anchor`, `remix/ui/breadcrumbs`, `remix/ui/button`,
369
+ `remix/ui/combobox`, `remix/ui/glyph`, `remix/ui/listbox`, `remix/ui/menu`,
370
+ `remix/ui/popover`, `remix/ui/scroll-lock`, `remix/ui/select`, `remix/ui/separator`, and
371
+ `remix/ui/theme`
307
372
  - `remix/ui/test` — component test rendering helpers such as `render`
308
- - `remix/ui/jsx-runtime` — JSX transform target. Configured in `tsconfig.json`, rarely
309
- imported directly
373
+ - `remix/ui/jsx-runtime` and `remix/ui/jsx-dev-runtime` — JSX transform targets. Configured in
374
+ `tsconfig.json`, rarely imported directly
310
375
  - `remix/html-template` — escaped HTML template literals. Use when generating HTML outside the
311
376
  component system (RSS feeds, email bodies, error pages)
312
377
  - `remix/file-storage` — backend-agnostic `File` storage interface. Use as the type bound for
313
378
  upload destinations
314
- - `remix/file-storage/fs`, `remix/file-storage/memory`, `remix/file-storage-s3` — storage
379
+ - `remix/file-storage/fs`, `remix/file-storage/memory`, `remix/file-storage/s3` — storage
315
380
  backends. Use to implement an upload destination
316
381
 
317
382
  ### Middleware
318
383
 
319
- - `remix/static-middleware` — `staticFiles(dir)`. Use to serve files from `public/` exactly as
384
+ - `remix/middleware/static` — `staticFiles(dir)`. Use to serve files from `public/` exactly as
320
385
  they exist on disk
321
- - `remix/form-data-middleware` — `formData()`. Use to parse `FormData` once and expose it via
386
+ - `remix/middleware/form-data` — `formData()`. Use to parse `FormData` once and expose it via
322
387
  `get(FormData)` instead of calling `await request.formData()` in each action
323
388
  - `remix/form-data-parser` — lower-level `parseFormData`, `FileUpload`. Use when implementing
324
389
  custom upload handlers. Upload handler errors propagate directly
325
390
  - `remix/multipart-parser` and `remix/multipart-parser/node` — low-level multipart stream parsing.
326
391
  `MultipartPart.headers` is a plain object keyed by lower-case header name; read values with
327
392
  bracket notation such as `part.headers['content-type']`
328
- - `remix/compression-middleware` — `compression()`. Use globally for text-like responses
329
- - `remix/logger-middleware` — `logger()`. Use in development for request logs; pass `colors` to
393
+ - `remix/middleware/compression` — `compression()`. Use globally for text-like responses
394
+ - `remix/middleware/logger` — `logger()`. Use in development for request logs; pass `colors` to
330
395
  force terminal color output on or off
331
- - `remix/method-override-middleware` — `methodOverride()`. Use when HTML forms need `PUT`,
396
+ - `remix/middleware/method-override` — `methodOverride()`. Use when HTML forms need `PUT`,
332
397
  `PATCH`, or `DELETE`
333
- - `remix/async-context-middleware` — `asyncContext()`, `getContext()`. Use when helpers outside
398
+ - `remix/middleware/async-context` — `asyncContext()`, `getContext()`. Use when helpers outside
334
399
  actions need request context without threading it through every call
335
- - `remix/cors-middleware` — `cors(opts?)`. Use for endpoints called cross-origin
336
- - `remix/csrf-middleware` — `csrf(opts?)`. Use when session-backed forms mutate state and need
400
+ - `remix/middleware/cors` — `cors(opts?)`. Use for endpoints called cross-origin
401
+ - `remix/middleware/csrf` — `csrf(opts?)`. Use when session-backed forms mutate state and need
337
402
  synchronizer-token CSRF protection
338
- - `remix/cop-middleware` — cross-origin protection. Use to reject unsafe cross-origin browser
403
+ - `remix/middleware/cop` — cross-origin protection. Use to reject unsafe cross-origin browser
339
404
  requests
340
405
 
341
406
  ### Test
342
407
 
343
408
  - `remix/test` — `describe`, `it`, and lifecycle hooks. Use as the test framework
344
409
  - `remix/test/cli` — programmatic test runner APIs such as `runRemixTest`
410
+ - `remix/node-fetch-server/test` — `createTestServer` for end-to-end tests that need a real local
411
+ HTTP server around a Fetch handler
345
412
  - `remix/cli` — programmatic Remix CLI API. Use the `remix` executable for project commands such
346
- as `remix test`, `remix routes`, and `remix doctor`
413
+ as `remix test`, `remix routes`, `remix doctor`, and `remix version`
347
414
  - `remix/assert` — assertion helpers. Use in place of `node:assert` so messages render cleanly
348
415
  in the runner
349
416
  - `remix/terminal` — ANSI styles, color detection, style factories, and testable terminal streams.
350
417
  Use for CLIs and terminal output instead of hand-rolled escape sequences
418
+ - `remix/fs` — small filesystem helpers such as `openLazyFile` and `writeFile`. Use in Node-only
419
+ app or tooling code when you need lazy file responses or safe file writes
420
+ - `remix/lazy-file` — `LazyFile` primitives and byte-range helpers. Use when implementing file or
421
+ range responses below the higher-level response/file helpers
422
+ - `remix/mime` — content-type and MIME detection helpers. Use instead of maintaining app-local
423
+ extension maps
424
+ - `remix/tar-parser` — streaming tar parsing. Use for import/export tooling that consumes tar
425
+ archives
351
426
 
352
427
  ## Canonical Patterns
353
428
 
354
429
  ### Define routes first
355
430
 
356
431
  ```typescript
357
- import { form, get, post, resources, route } from 'remix/fetch-router/routes'
432
+ import { form, get, post, resources, route } from 'remix/routes'
358
433
 
359
434
  export const routes = route({
360
435
  home: '/',
@@ -377,12 +452,11 @@ export const routes = route({
377
452
  ### Type controllers against the route contract
378
453
 
379
454
  ```typescript
380
- import type { Controller } from 'remix/fetch-router'
455
+ import { createController } from 'remix/router'
381
456
 
382
- import type { AppContext } from '../router.ts'
383
457
  import { routes } from '../routes.ts'
384
458
 
385
- export default {
459
+ export default createController(routes.books, {
386
460
  actions: {
387
461
  async index({ get }) {
388
462
  let db = get(Database)
@@ -396,30 +470,38 @@ export default {
396
470
  return render(<BookShowPage book={book} />)
397
471
  },
398
472
  },
399
- } satisfies Controller<typeof routes.books, AppContext>
473
+ })
474
+ ```
475
+
476
+ ### Register Controllers Explicitly
477
+
478
+ ```typescript
479
+ import { createRouter } from 'remix/router'
480
+
481
+ import rootController from './actions/controller.tsx'
482
+ import adminController from './actions/admin/controller.tsx'
483
+ import adminBooksController from './actions/admin/books/controller.tsx'
484
+ import authController from './actions/auth/controller.tsx'
485
+ import authLoginController from './actions/auth/login/controller.tsx'
486
+ import booksController from './actions/books/controller.tsx'
487
+ import contactController from './actions/contact/controller.tsx'
488
+ import { routes } from './routes.ts'
489
+
490
+ export const router = createRouter({ middleware })
491
+
492
+ router.map(routes, rootController)
493
+ router.map(routes.contact, contactController)
494
+ router.map(routes.books, booksController)
495
+ router.map(routes.auth, authController)
496
+ router.map(routes.auth.login, authLoginController)
497
+ router.map(routes.admin, adminController)
498
+ router.map(routes.admin.books, adminBooksController)
400
499
  ```
401
500
 
402
501
  ### Compose middleware deliberately
403
502
 
404
503
  ```typescript
405
- import {
406
- createRouter,
407
- type AnyParams,
408
- type MiddlewareContext,
409
- type WithParams,
410
- } from 'remix/fetch-router'
411
-
412
- export type RootMiddleware = [
413
- ReturnType<typeof formData>,
414
- ReturnType<typeof session>,
415
- ReturnType<typeof loadDatabase>,
416
- ReturnType<typeof loadAuth>,
417
- ]
418
-
419
- export type AppContext<params extends AnyParams = AnyParams> = WithParams<
420
- MiddlewareContext<RootMiddleware>,
421
- params
422
- >
504
+ import { createRouter } from 'remix/router'
423
505
 
424
506
  let middleware = []
425
507
 
@@ -439,21 +521,24 @@ middleware.push(loadAuth())
439
521
  let router = createRouter({ middleware })
440
522
  ```
441
523
 
442
- ### Mutate, validate, and respond
524
+ ### Validate, mutate, and respond
443
525
 
444
526
  ```typescript
527
+ import { createController } from 'remix/router'
445
528
  import { redirect } from 'remix/response/redirect'
446
529
  import * as s from 'remix/data-schema'
447
530
  import * as f from 'remix/data-schema/form-data'
448
531
  import { Session } from 'remix/session'
449
532
  import { Database } from 'remix/data-table'
450
533
 
534
+ import { routes } from '../routes.ts'
535
+
451
536
  let bookSchema = f.object({
452
537
  slug: f.field(s.string()),
453
538
  title: f.field(s.string()),
454
539
  })
455
540
 
456
- export default {
541
+ export default createController(routes.books, {
457
542
  actions: {
458
543
  async create({ get }) {
459
544
  let parsed = s.parseSafe(bookSchema, get(FormData))
@@ -470,7 +555,7 @@ export default {
470
555
  return redirect(routes.books.show.href({ slug: book.slug }))
471
556
  },
472
557
  },
473
- } satisfies Controller<typeof routes.books, AppContext>
558
+ })
474
559
  ```
475
560
 
476
561
  This shape works without JavaScript, returns a `Response` for every outcome, and is ready for
@@ -4,7 +4,8 @@
4
4
 
5
5
  How to serve browser scripts and styles from source. Read this when the task involves:
6
6
 
7
- - Configuring `createAssetServer` (`fileMap`, `allow`, `deny`, fingerprinting, compiler options)
7
+ - Configuring `createAssetServer` (`basePath`, `fileMap`, `allow`, `deny`, fingerprinting,
8
+ compiler options)
8
9
  - Choosing between `staticFiles()` for already-built files and `createAssetServer()` for source
9
10
  assets that need import rewriting, preloads, or fingerprinted URLs
10
11
  - Generating script URLs or `<link rel="modulepreload">` tags for a client entry
@@ -26,18 +27,22 @@ preloads, sourcemaps, or fingerprinted URLs.
26
27
  ## Default Pattern
27
28
 
28
29
  ```typescript
29
- import * as path from 'node:path'
30
-
31
30
  import { createAssetServer } from 'remix/assets'
32
- import { createRouter } from 'remix/fetch-router'
31
+ import { createController } from 'remix/router'
32
+ import { get, route } from 'remix/routes'
33
+
34
+ export const routes = route({
35
+ assets: get('/assets/*path'),
36
+ })
33
37
 
34
38
  let assetServer = createAssetServer({
35
- rootDir: path.resolve(import.meta.dirname, '..'),
39
+ basePath: '/assets',
40
+ rootDir: process.cwd(),
36
41
  fileMap: {
37
- '/assets/app/*path': 'app/*path',
38
- '/assets/packages/*path': '../packages/*path',
42
+ 'app/*path': 'app/*path',
43
+ 'node_modules/*path': 'node_modules/*path',
39
44
  },
40
- allow: ['app/assets/**', '../packages/**'],
45
+ allow: ['app/assets/**', 'node_modules/**'],
41
46
  deny: ['app/**/*.server.*'],
42
47
  target: { es: '2020', chrome: '109', safari: '16.4' },
43
48
  sourceMaps: process.env.NODE_ENV === 'development' ? 'external' : undefined,
@@ -49,10 +54,12 @@ let assetServer = createAssetServer({
49
54
  },
50
55
  })
51
56
 
52
- let router = createRouter()
53
-
54
- router.get('/assets/*path', ({ request }) => {
55
- return assetServer.fetch(request)
57
+ export default createController(routes, {
58
+ actions: {
59
+ async assets({ request }) {
60
+ return (await assetServer.fetch(request)) ?? new Response('Not Found', { status: 404 })
61
+ },
62
+ },
56
63
  })
57
64
  ```
58
65
 
@@ -62,8 +69,9 @@ router.get('/assets/*path', ({ request }) => {
62
69
  - Add a `deny` list for server-only modules such as `*.server.*`, private config, or other files
63
70
  that should never be exposed.
64
71
  - Set `rootDir` explicitly in monorepos so relative paths resolve from the intended project root.
65
- - `fileMap` keys are public URL patterns and values are root-relative file path patterns. They use
66
- `route-pattern` syntax on both sides.
72
+ - `basePath` is the public URL namespace handled by the asset server.
73
+ - `fileMap` keys are URL patterns relative to `basePath`, and values are root-relative file path
74
+ patterns. They use `route-pattern` syntax on both sides.
67
75
  - Keep the same wildcard params on both sides of a `fileMap` entry so import rewriting can map
68
76
  source files back to public URLs.
69
77
  - CSS files are compiled and served alongside scripts. Local CSS `@import` rules are rewritten and