@remix-run/cli 0.2.0 → 0.3.0

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 (84) 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/load-route-map-worker.js +17 -9
  30. package/dist/lib/route-map.d.ts +1 -1
  31. package/dist/lib/route-map.d.ts.map +1 -1
  32. package/dist/lib/route-map.js +29 -17
  33. package/package.json +3 -3
  34. package/src/lib/bootstrap-project.ts +39 -13
  35. package/src/lib/cli.ts +39 -4
  36. package/src/lib/commands/completion.ts +6 -1
  37. package/src/lib/commands/doctor.ts +18 -21
  38. package/src/lib/commands/routes.ts +3 -3
  39. package/src/lib/completion.ts +1 -5
  40. package/src/lib/controller-files.ts +4 -8
  41. package/src/lib/controller-ownership.ts +78 -141
  42. package/src/lib/doctor/controller-findings.ts +20 -97
  43. package/src/lib/doctor/controller-fix-plans.ts +13 -29
  44. package/src/lib/doctor/controller-placeholders.ts +17 -189
  45. package/src/lib/doctor/controllers.ts +1 -1
  46. package/src/lib/doctor/project.ts +60 -52
  47. package/src/lib/doctor/types.ts +1 -5
  48. package/src/lib/load-route-map-worker.ts +19 -10
  49. package/src/lib/route-map.ts +61 -16
  50. package/{bootstrap → template}/.agents/skills/remix/SKILL.md +160 -73
  51. package/{bootstrap → template}/.agents/skills/remix/references/assets-and-browser-modules.md +22 -14
  52. package/{bootstrap → template}/.agents/skills/remix/references/auth-and-sessions.md +41 -18
  53. package/{bootstrap → template}/.agents/skills/remix/references/data-and-validation.md +21 -5
  54. package/{bootstrap → template}/.agents/skills/remix/references/middleware-and-server.md +43 -53
  55. package/{bootstrap → template}/.agents/skills/remix/references/routing-and-controllers.md +111 -44
  56. package/{bootstrap → template}/.agents/skills/remix/references/testing-patterns.md +17 -1
  57. package/{bootstrap → template}/AGENTS.md +10 -9
  58. package/template/README.md +29 -0
  59. package/template/app/actions/controller.tsx +18 -0
  60. package/template/app/assets/entry.ts +8 -0
  61. package/{bootstrap/app/ui → template/app/assets}/prompt-button.tsx +2 -1
  62. package/{bootstrap → template}/app/assets.ts +5 -3
  63. package/template/app/middleware/render.tsx +43 -0
  64. package/template/app/router.ts +20 -0
  65. package/{bootstrap → template}/app/routes.ts +1 -2
  66. package/{bootstrap → template}/app/ui/document.tsx +11 -4
  67. package/{bootstrap → template}/app/ui/scaffold-home-page.tsx +39 -38
  68. package/template/gitignore +4 -0
  69. package/{bootstrap → template}/package.json +5 -6
  70. package/template/public/favicon.svg +11 -0
  71. package/{bootstrap → template}/server.ts +10 -11
  72. package/{bootstrap → template}/tsconfig.json +3 -2
  73. package/bootstrap/README.md +0 -27
  74. package/bootstrap/app/assets/entry.ts +0 -19
  75. package/bootstrap/app/controllers/auth.tsx +0 -21
  76. package/bootstrap/app/controllers/home.tsx +0 -11
  77. package/bootstrap/app/router.ts +0 -16
  78. package/bootstrap/app/ui/layout.tsx +0 -22
  79. package/bootstrap/app/utils/render.tsx +0 -26
  80. /package/{bootstrap → template}/.agents/skills/remix/references/animate-elements.md +0 -0
  81. /package/{bootstrap → template}/.agents/skills/remix/references/component-model.md +0 -0
  82. /package/{bootstrap → template}/.agents/skills/remix/references/create-mixins.md +0 -0
  83. /package/{bootstrap → template}/.agents/skills/remix/references/hydration-frames-navigation.md +0 -0
  84. /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,7 +30,7 @@ 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
@@ -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
@@ -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,22 @@ 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
285
+ - `remix/node-serve` — managed high-performance Node server. Use `serve` when you want Remix to
286
+ manage the server lifecycle, TLS options, or uWebSockets.js setup around a Fetch handler
234
287
  - `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
288
+ scripts and styles, getting public hrefs, and emitting preloads. Configure a `basePath`, and
289
+ keep `fileMap` URL patterns relative to it. Shared compiler options such as `target`,
290
+ `sourceMaps`, `sourceMapSourcePaths`, and `minify` live at the top level
291
+ - `remix/headers` `SuperHeaders` plus typed header parsers and builders. Use the default export
292
+ when you want a `Headers` subclass with typed accessors like `headers.contentType`,
293
+ `headers.cacheControl`, and `headers.setCookie`; use named classes such as `CacheControl`,
294
+ `ContentDisposition`, and `Vary` when working with individual header values
239
295
  - `remix/response/redirect` — `redirect(href, status?)`. Use for the canonical "POST then redirect"
240
296
  pattern and other location changes
241
297
  - `remix/response/html` — `createHtmlResponse`. Use when you need an HTML `Response` from a string
@@ -244,10 +300,14 @@ what it exports. Open the linked reference file when you need full examples.
244
300
  the global `compression()` middleware
245
301
  - `remix/response/file` — file-download responses. Use for `Content-Disposition: attachment`
246
302
  responses
247
- - `remix/route-pattern` — low-level URL matching and generation. Use when working with raw
248
- patterns outside the router (custom matchers, scripts)
303
+ - `remix/route-pattern` — low-level URL matching and generation. Use `RoutePattern` or
304
+ `createMatcher` when working with raw patterns outside the router. `href(...)` encodes pathname
305
+ and search params for you, and `match(...)` returns decoded params
306
+ - `remix/route-pattern/specificity` — pattern ranking helpers. Use only when building custom
307
+ matcher or reporting logic outside the normal router/matcher APIs
249
308
  - `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
309
+ `xForwardedHeaders` when the upstream needs forwarded proto, host, and port. It also rewrites
310
+ proxied `Set-Cookie` domain/path attributes by default
251
311
 
252
312
  ### Data, Validation, and Persistence
253
313
 
@@ -260,9 +320,11 @@ what it exports. Open the linked reference file when you need full examples.
260
320
  Use when input arrives as a string but should be a typed value
261
321
  - `remix/data-schema/form-data` — `f.object` and `f.field` for parsing `FormData` directly. Use
262
322
  in actions that read browser forms
323
+ - `remix/data-schema/lazy` — recursive or mutually-referential schemas. Use when a schema needs to
324
+ refer to itself or another schema that is declared later
263
325
  - `remix/data-table` — typed tables and a `Database` interface. Use for `table`, `column`,
264
326
  `createDatabase` when modeling persisted data
265
- - `remix/data-table-sqlite`, `remix/data-table-postgres`, `remix/data-table-mysql` — adapters.
327
+ - `remix/data-table/sqlite`, `remix/data-table/postgres`, `remix/data-table/mysql` — adapters.
266
328
  Use to back `createDatabase` with a real engine. SQLite accepts Node, Bun, and compatible
267
329
  synchronous clients with the shared `prepare`/`exec` surface
268
330
  - `remix/data-table/migrations` — migration authoring and runners. Use for `createMigration`,
@@ -271,27 +333,29 @@ what it exports. Open the linked reference file when you need full examples.
271
333
  apply migrations
272
334
  - `remix/data-table/operators` — query operators such as `inList(...)`. Use when `where` clauses
273
335
  need set or comparison logic
336
+ - `remix/data-table/sql-helpers` — SQL helper utilities for adapter or advanced query work. Avoid
337
+ this in normal app code unless you are intentionally working below the table/query API
274
338
 
275
339
  ### Auth, Sessions, and Cookies
276
340
 
277
341
  - `remix/session` — the `Session` object: `get`, `set`, `flash`, `unset`, `regenerateId`. Use for
278
342
  any per-browser state where tampering would be a bug (login, "I submitted this form already",
279
343
  cart, flash messages)
280
- - `remix/session-middleware` — `session(cookie, storage)`. Use to wire a session cookie and
344
+ - `remix/middleware/session` — `session(cookie, storage)`. Use to wire a session cookie and
281
345
  storage backend into the root middleware stack
282
- - `remix/session/fs-storage`, `remix/session/memory-storage`, `remix/session/cookie-storage` —
346
+ - `remix/session-storage/fs`, `remix/session-storage/memory`, `remix/session-storage/cookie` —
283
347
  storage backends. Use `fs-storage` for single-process apps, `memory-storage` for tests,
284
348
  `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
349
+ - `remix/session-storage/redis` — Redis-backed storage. Use for multi-process or multi-host
286
350
  deployments
287
- - `remix/session-storage-memcache` — Memcache-backed storage. Same multi-host use case as Redis
351
+ - `remix/session-storage/memcache` — Memcache-backed storage. Same multi-host use case as Redis
288
352
  - `remix/cookie` — `createCookie` for plain signed/unsigned cookies. Use for non-sensitive
289
353
  preferences where the client is allowed to control the value (theme, locale, dismissed banner).
290
354
  For state where tampering matters, prefer `remix/session`
291
355
  - `remix/auth` — credentials, OAuth, OIDC, and Atmosphere providers. Use to define how identity is
292
356
  verified, start/finish external login, and refresh stored OAuth/OIDC token bundles with
293
357
  `refreshExternalAuth(...)`
294
- - `remix/auth-middleware` — `auth({ schemes })`, `requireAuth`, the `Auth` context key. Use to
358
+ - `remix/middleware/auth` — `auth({ schemes })`, `requireAuth`, the `Auth` context key. Use to
295
359
  resolve identity into the request context and to gate routes
296
360
 
297
361
  ### UI, Hydration, and Browser Behavior
@@ -299,62 +363,75 @@ what it exports. Open the linked reference file when you need full examples.
299
363
  - `remix/ui` — the component runtime: components, core mixins, `clientEntry`, `run`, `<Frame>`,
300
364
  navigation helpers, and `createRoot`. Use for app UI behavior
301
365
  - `remix/ui/server` — server rendering: `renderToStream`, `renderToString`. Use in the
302
- `render(...)` helper that returns HTML responses
366
+ `app/actions/render.tsx` helper that returns HTML responses
303
367
  - `remix/ui/animation` — animation APIs: `animateEntrance`, `animateExit`, `animateLayout`,
304
368
  `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.
369
+ - `remix/ui/<primitive>` — UI primitives, mixins, glyphs, and theme helpers. Current subpaths
370
+ include `remix/ui/accordion`, `remix/ui/anchor`, `remix/ui/breadcrumbs`, `remix/ui/button`,
371
+ `remix/ui/combobox`, `remix/ui/glyph`, `remix/ui/listbox`, `remix/ui/menu`,
372
+ `remix/ui/popover`, `remix/ui/scroll-lock`, `remix/ui/select`, `remix/ui/separator`, and
373
+ `remix/ui/theme`
307
374
  - `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
375
+ - `remix/ui/jsx-runtime` and `remix/ui/jsx-dev-runtime` — JSX transform targets. Configured in
376
+ `tsconfig.json`, rarely imported directly
310
377
  - `remix/html-template` — escaped HTML template literals. Use when generating HTML outside the
311
378
  component system (RSS feeds, email bodies, error pages)
312
379
  - `remix/file-storage` — backend-agnostic `File` storage interface. Use as the type bound for
313
380
  upload destinations
314
- - `remix/file-storage/fs`, `remix/file-storage/memory`, `remix/file-storage-s3` — storage
381
+ - `remix/file-storage/fs`, `remix/file-storage/memory`, `remix/file-storage/s3` — storage
315
382
  backends. Use to implement an upload destination
316
383
 
317
384
  ### Middleware
318
385
 
319
- - `remix/static-middleware` — `staticFiles(dir)`. Use to serve files from `public/` exactly as
386
+ - `remix/middleware/static` — `staticFiles(dir)`. Use to serve files from `public/` exactly as
320
387
  they exist on disk
321
- - `remix/form-data-middleware` — `formData()`. Use to parse `FormData` once and expose it via
388
+ - `remix/middleware/form-data` — `formData()`. Use to parse `FormData` once and expose it via
322
389
  `get(FormData)` instead of calling `await request.formData()` in each action
323
390
  - `remix/form-data-parser` — lower-level `parseFormData`, `FileUpload`. Use when implementing
324
391
  custom upload handlers. Upload handler errors propagate directly
325
392
  - `remix/multipart-parser` and `remix/multipart-parser/node` — low-level multipart stream parsing.
326
393
  `MultipartPart.headers` is a plain object keyed by lower-case header name; read values with
327
394
  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
395
+ - `remix/middleware/compression` — `compression()`. Use globally for text-like responses
396
+ - `remix/middleware/logger` — `logger()`. Use in development for request logs; pass `colors` to
330
397
  force terminal color output on or off
331
- - `remix/method-override-middleware` — `methodOverride()`. Use when HTML forms need `PUT`,
398
+ - `remix/middleware/method-override` — `methodOverride()`. Use when HTML forms need `PUT`,
332
399
  `PATCH`, or `DELETE`
333
- - `remix/async-context-middleware` — `asyncContext()`, `getContext()`. Use when helpers outside
400
+ - `remix/middleware/async-context` — `asyncContext()`, `getContext()`. Use when helpers outside
334
401
  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
402
+ - `remix/middleware/cors` — `cors(opts?)`. Use for endpoints called cross-origin
403
+ - `remix/middleware/csrf` — `csrf(opts?)`. Use when session-backed forms mutate state and need
337
404
  synchronizer-token CSRF protection
338
- - `remix/cop-middleware` — cross-origin protection. Use to reject unsafe cross-origin browser
405
+ - `remix/middleware/cop` — cross-origin protection. Use to reject unsafe cross-origin browser
339
406
  requests
340
407
 
341
408
  ### Test
342
409
 
343
410
  - `remix/test` — `describe`, `it`, and lifecycle hooks. Use as the test framework
344
411
  - `remix/test/cli` — programmatic test runner APIs such as `runRemixTest`
412
+ - `remix/node-fetch-server/test` — `createTestServer` for end-to-end tests that need a real local
413
+ HTTP server around a Fetch handler
345
414
  - `remix/cli` — programmatic Remix CLI API. Use the `remix` executable for project commands such
346
- as `remix test`, `remix routes`, and `remix doctor`
415
+ as `remix test`, `remix routes`, `remix doctor`, and `remix version`
347
416
  - `remix/assert` — assertion helpers. Use in place of `node:assert` so messages render cleanly
348
417
  in the runner
349
418
  - `remix/terminal` — ANSI styles, color detection, style factories, and testable terminal streams.
350
419
  Use for CLIs and terminal output instead of hand-rolled escape sequences
420
+ - `remix/fs` — small filesystem helpers such as `openLazyFile` and `writeFile`. Use in Node-only
421
+ app or tooling code when you need lazy file responses or safe file writes
422
+ - `remix/lazy-file` — `LazyFile` primitives and byte-range helpers. Use when implementing file or
423
+ range responses below the higher-level response/file helpers
424
+ - `remix/mime` — content-type and MIME detection helpers. Use instead of maintaining app-local
425
+ extension maps
426
+ - `remix/tar-parser` — streaming tar parsing. Use for import/export tooling that consumes tar
427
+ archives
351
428
 
352
429
  ## Canonical Patterns
353
430
 
354
431
  ### Define routes first
355
432
 
356
433
  ```typescript
357
- import { form, get, post, resources, route } from 'remix/fetch-router/routes'
434
+ import { form, get, post, resources, route } from 'remix/routes'
358
435
 
359
436
  export const routes = route({
360
437
  home: '/',
@@ -377,12 +454,11 @@ export const routes = route({
377
454
  ### Type controllers against the route contract
378
455
 
379
456
  ```typescript
380
- import type { Controller } from 'remix/fetch-router'
457
+ import { createController } from 'remix/router'
381
458
 
382
- import type { AppContext } from '../router.ts'
383
459
  import { routes } from '../routes.ts'
384
460
 
385
- export default {
461
+ export default createController(routes.books, {
386
462
  actions: {
387
463
  async index({ get }) {
388
464
  let db = get(Database)
@@ -396,30 +472,38 @@ export default {
396
472
  return render(<BookShowPage book={book} />)
397
473
  },
398
474
  },
399
- } satisfies Controller<typeof routes.books, AppContext>
475
+ })
476
+ ```
477
+
478
+ ### Register Controllers Explicitly
479
+
480
+ ```typescript
481
+ import { createRouter } from 'remix/router'
482
+
483
+ import rootController from './actions/controller.tsx'
484
+ import adminController from './actions/admin/controller.tsx'
485
+ import adminBooksController from './actions/admin/books/controller.tsx'
486
+ import authController from './actions/auth/controller.tsx'
487
+ import authLoginController from './actions/auth/login/controller.tsx'
488
+ import booksController from './actions/books/controller.tsx'
489
+ import contactController from './actions/contact/controller.tsx'
490
+ import { routes } from './routes.ts'
491
+
492
+ export const router = createRouter({ middleware })
493
+
494
+ router.map(routes, rootController)
495
+ router.map(routes.contact, contactController)
496
+ router.map(routes.books, booksController)
497
+ router.map(routes.auth, authController)
498
+ router.map(routes.auth.login, authLoginController)
499
+ router.map(routes.admin, adminController)
500
+ router.map(routes.admin.books, adminBooksController)
400
501
  ```
401
502
 
402
503
  ### Compose middleware deliberately
403
504
 
404
505
  ```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
- >
506
+ import { createRouter } from 'remix/router'
423
507
 
424
508
  let middleware = []
425
509
 
@@ -439,21 +523,24 @@ middleware.push(loadAuth())
439
523
  let router = createRouter({ middleware })
440
524
  ```
441
525
 
442
- ### Mutate, validate, and respond
526
+ ### Validate, mutate, and respond
443
527
 
444
528
  ```typescript
529
+ import { createController } from 'remix/router'
445
530
  import { redirect } from 'remix/response/redirect'
446
531
  import * as s from 'remix/data-schema'
447
532
  import * as f from 'remix/data-schema/form-data'
448
533
  import { Session } from 'remix/session'
449
534
  import { Database } from 'remix/data-table'
450
535
 
536
+ import { routes } from '../routes.ts'
537
+
451
538
  let bookSchema = f.object({
452
539
  slug: f.field(s.string()),
453
540
  title: f.field(s.string()),
454
541
  })
455
542
 
456
- export default {
543
+ export default createController(routes.books, {
457
544
  actions: {
458
545
  async create({ get }) {
459
546
  let parsed = s.parseSafe(bookSchema, get(FormData))
@@ -470,7 +557,7 @@ export default {
470
557
  return redirect(routes.books.show.href({ slug: book.slug }))
471
558
  },
472
559
  },
473
- } satisfies Controller<typeof routes.books, AppContext>
560
+ })
474
561
  ```
475
562
 
476
563
  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
@@ -68,18 +68,18 @@ fail fast when the secret is missing.
68
68
 
69
69
  ```typescript
70
70
  // Filesystem storage
71
- import { createFsSessionStorage } from 'remix/session/fs-storage'
71
+ import { createFsSessionStorage } from 'remix/session-storage/fs'
72
72
  export let sessionStorage = createFsSessionStorage('./tmp/sessions')
73
73
 
74
74
  // Memory storage (for tests)
75
- import { createMemorySessionStorage } from 'remix/session/memory-storage'
75
+ import { createMemorySessionStorage } from 'remix/session-storage/memory'
76
76
  export let sessionStorage = createMemorySessionStorage()
77
77
  ```
78
78
 
79
79
  ### Add session middleware
80
80
 
81
81
  ```typescript
82
- import { session } from 'remix/session-middleware'
82
+ import { session } from 'remix/middleware/session'
83
83
 
84
84
  let router = createRouter({
85
85
  middleware: [
@@ -151,7 +151,7 @@ bypassable by clearing cookies; if the guarantee needs to survive that, you also
151
151
  ### Basic setup
152
152
 
153
153
  ```typescript
154
- import { auth, createSessionAuthScheme } from 'remix/auth-middleware'
154
+ import { auth, createSessionAuthScheme } from 'remix/middleware/auth'
155
155
  import { Session } from 'remix/session'
156
156
  import { Database } from 'remix/data-table'
157
157
 
@@ -179,7 +179,7 @@ export function loadAuth() {
179
179
  ### Reading auth state
180
180
 
181
181
  ```typescript
182
- import { Auth } from 'remix/auth-middleware'
182
+ import { Auth } from 'remix/middleware/auth'
183
183
 
184
184
  function handler({ get }) {
185
185
  let auth = get(Auth)
@@ -303,7 +303,9 @@ module-scope provider to `finishExternalAuth(...)` and `refreshExternalAuth(...)
303
303
  ### OAuth controller
304
304
 
305
305
  ```typescript
306
- export default {
306
+ import { createController } from 'remix/router'
307
+
308
+ export default createController(routes.auth.google, {
307
309
  actions: {
308
310
  // GET /auth/google — redirect to Google
309
311
  async index(context) {
@@ -326,10 +328,10 @@ export default {
326
328
  authAccountId: authAccount.id,
327
329
  })
328
330
 
329
- return redirect(returnTo ?? routes.account.href())
331
+ return redirect(returnTo ?? routes.account.index.href())
330
332
  },
331
333
  },
332
- } satisfies Controller<typeof routes.auth.google>
334
+ })
333
335
  ```
334
336
 
335
337
  ### Refresh stored provider tokens
@@ -356,20 +358,41 @@ async function refreshGoogleTokens({ get }) {
356
358
 
357
359
  ### Controller-level protection
358
360
 
359
- Apply `requireAuth()` to an entire controller subtree:
361
+ Apply `requireAuth()` to every action in one controller:
362
+
363
+ ```typescript
364
+ import { createController } from 'remix/router'
365
+ import { requireAuth } from 'remix/middleware/auth'
366
+
367
+ export default createController(routes.account, {
368
+ middleware: [requireAuth()],
369
+ actions: {
370
+ index() {
371
+ /* guaranteed authenticated */
372
+ },
373
+ },
374
+ })
375
+ ```
376
+
377
+ Nested route maps need their own explicit protection:
360
378
 
361
379
  ```typescript
362
- import { requireAuth } from 'remix/auth-middleware'
380
+ // app/router.ts
381
+ router.map(routes.account, accountController)
382
+ router.map(routes.account.settings, accountSettingsController)
363
383
 
364
- export default {
384
+ // app/actions/account/settings/controller.tsx
385
+ export default createController(routes.account.settings, {
365
386
  middleware: [requireAuth()],
366
387
  actions: {
367
388
  index() {
368
389
  /* guaranteed authenticated */
369
390
  },
370
- settings: settingsController,
391
+ update() {
392
+ /* guaranteed authenticated */
393
+ },
371
394
  },
372
- } satisfies Controller<typeof routes.account>
395
+ })
373
396
  ```
374
397
 
375
398
  ### Stacking middleware
@@ -377,14 +400,14 @@ export default {
377
400
  Combine auth checks with role checks:
378
401
 
379
402
  ```typescript
380
- export default {
403
+ export default createController(routes.admin, {
381
404
  middleware: [requireAuth(), requireAdmin()],
382
405
  actions: {
383
406
  index() {
384
407
  /* requires auth + admin */
385
408
  },
386
409
  },
387
- } satisfies Controller<typeof routes.admin>
410
+ })
388
411
  ```
389
412
 
390
413
  ### Action-level protection
@@ -392,9 +415,9 @@ export default {
392
415
  Apply middleware to a single route:
393
416
 
394
417
  ```typescript
395
- import { Auth, requireAuth } from 'remix/auth-middleware'
418
+ import { Auth, requireAuth } from 'remix/middleware/auth'
396
419
 
397
- router.get(routes.account, {
420
+ router.get(routes.account.index, {
398
421
  middleware: [requireAuth()],
399
422
  handler(context) {
400
423
  let auth = context.get(Auth)
@@ -406,7 +429,7 @@ router.get(routes.account, {
406
429
  ### Redirect on auth failure
407
430
 
408
431
  ```typescript
409
- import { requireAuth } from 'remix/auth-middleware'
432
+ import { requireAuth } from 'remix/middleware/auth'
410
433
  import { redirect } from 'remix/response/redirect'
411
434
 
412
435
  export function requireAuthRedirect() {