@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.
- package/dist/lib/bootstrap-project.d.ts.map +1 -1
- package/dist/lib/bootstrap-project.js +32 -9
- package/dist/lib/cli.d.ts +29 -0
- package/dist/lib/cli.d.ts.map +1 -1
- package/dist/lib/cli.js +26 -4
- package/dist/lib/commands/completion.d.ts.map +1 -1
- package/dist/lib/commands/completion.js +5 -1
- package/dist/lib/commands/doctor.js +18 -18
- package/dist/lib/commands/routes.js +3 -3
- package/dist/lib/completion.js +1 -5
- package/dist/lib/controller-files.d.ts +0 -1
- package/dist/lib/controller-files.d.ts.map +1 -1
- package/dist/lib/controller-files.js +3 -5
- package/dist/lib/controller-ownership.d.ts +9 -9
- package/dist/lib/controller-ownership.d.ts.map +1 -1
- package/dist/lib/controller-ownership.js +56 -91
- package/dist/lib/doctor/controller-findings.d.ts +1 -1
- package/dist/lib/doctor/controller-findings.d.ts.map +1 -1
- package/dist/lib/doctor/controller-findings.js +15 -87
- package/dist/lib/doctor/controller-fix-plans.d.ts.map +1 -1
- package/dist/lib/doctor/controller-fix-plans.js +13 -24
- package/dist/lib/doctor/controller-placeholders.d.ts +2 -3
- package/dist/lib/doctor/controller-placeholders.d.ts.map +1 -1
- package/dist/lib/doctor/controller-placeholders.js +18 -149
- package/dist/lib/doctor/controllers.js +1 -1
- package/dist/lib/doctor/project.js +60 -52
- package/dist/lib/doctor/types.d.ts +2 -2
- package/dist/lib/doctor/types.d.ts.map +1 -1
- package/dist/lib/help-text.js +1 -1
- package/dist/lib/load-route-map-worker.js +17 -9
- package/dist/lib/route-map.d.ts +1 -1
- package/dist/lib/route-map.d.ts.map +1 -1
- package/dist/lib/route-map.js +29 -17
- package/package.json +5 -5
- package/src/lib/bootstrap-project.ts +39 -13
- package/src/lib/cli.ts +39 -4
- package/src/lib/commands/completion.ts +6 -1
- package/src/lib/commands/doctor.ts +18 -21
- package/src/lib/commands/routes.ts +3 -3
- package/src/lib/completion.ts +1 -5
- package/src/lib/controller-files.ts +4 -8
- package/src/lib/controller-ownership.ts +78 -141
- package/src/lib/doctor/controller-findings.ts +20 -97
- package/src/lib/doctor/controller-fix-plans.ts +13 -29
- package/src/lib/doctor/controller-placeholders.ts +17 -189
- package/src/lib/doctor/controllers.ts +1 -1
- package/src/lib/doctor/project.ts +60 -52
- package/src/lib/doctor/types.ts +1 -5
- package/src/lib/help-text.ts +3 -2
- package/src/lib/load-route-map-worker.ts +19 -10
- package/src/lib/route-map.ts +61 -16
- package/{bootstrap → template}/.agents/skills/remix/SKILL.md +162 -77
- package/{bootstrap → template}/.agents/skills/remix/references/assets-and-browser-modules.md +22 -14
- package/{bootstrap → template}/.agents/skills/remix/references/auth-and-sessions.md +41 -18
- package/{bootstrap → template}/.agents/skills/remix/references/component-model.md +4 -1
- package/{bootstrap → template}/.agents/skills/remix/references/data-and-validation.md +21 -5
- package/{bootstrap → template}/.agents/skills/remix/references/middleware-and-server.md +42 -53
- package/{bootstrap → template}/.agents/skills/remix/references/routing-and-controllers.md +111 -44
- package/{bootstrap → template}/.agents/skills/remix/references/testing-patterns.md +17 -1
- package/{bootstrap → template}/AGENTS.md +10 -9
- package/template/README.md +29 -0
- package/template/app/actions/controller.tsx +18 -0
- package/template/app/assets/entry.ts +8 -0
- package/{bootstrap/app/ui → template/app/assets}/prompt-button.tsx +2 -1
- package/{bootstrap → template}/app/assets.ts +5 -3
- package/template/app/middleware/render.tsx +44 -0
- package/template/app/router.ts +20 -0
- package/{bootstrap → template}/app/routes.ts +1 -2
- package/{bootstrap → template}/app/ui/document.tsx +11 -4
- package/{bootstrap → template}/app/ui/scaffold-home-page.tsx +39 -38
- package/template/gitignore +4 -0
- package/{bootstrap → template}/package.json +5 -6
- package/template/public/favicon.svg +11 -0
- package/template/server.ts +38 -0
- package/{bootstrap → template}/tsconfig.json +3 -2
- package/bootstrap/README.md +0 -27
- package/bootstrap/app/assets/entry.ts +0 -19
- package/bootstrap/app/controllers/auth.tsx +0 -21
- package/bootstrap/app/controllers/home.tsx +0 -11
- package/bootstrap/app/router.ts +0 -16
- package/bootstrap/app/ui/layout.tsx +0 -22
- package/bootstrap/app/utils/render.tsx +0 -26
- package/bootstrap/server.ts +0 -37
- /package/{bootstrap → template}/.agents/skills/remix/references/animate-elements.md +0 -0
- /package/{bootstrap → template}/.agents/skills/remix/references/create-mixins.md +0 -0
- /package/{bootstrap → template}/.agents/skills/remix/references/hydration-frames-navigation.md +0 -0
- /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.
|
|
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
|
|
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
|
-
- `
|
|
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
|
-
-
|
|
127
|
-
-
|
|
128
|
-
`
|
|
129
|
-
-
|
|
130
|
-
|
|
131
|
-
-
|
|
132
|
-
-
|
|
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
|
|
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
|
|
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:
|
|
163
|
-
|
|
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/
|
|
279
|
+
- `remix/router` — the router itself. Use for `createRouter`, controller and middleware
|
|
229
280
|
types, and registering routes
|
|
230
|
-
- `remix/
|
|
231
|
-
`
|
|
232
|
-
- `remix/node-fetch-server` —
|
|
233
|
-
`
|
|
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.
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
|
248
|
-
patterns outside the router (
|
|
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
|
|
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
|
|
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
|
|
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
|
|
347
|
+
- `remix/session-storage/redis` — Redis-backed storage. Use for multi-process or multi-host
|
|
286
348
|
deployments
|
|
287
|
-
- `remix/session-storage
|
|
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
|
|
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
|
|
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.
|
|
306
|
-
`remix/ui/accordion`, `remix/ui/
|
|
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
|
|
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
|
|
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
|
|
384
|
+
- `remix/middleware/static` — `staticFiles(dir)`. Use to serve files from `public/` exactly as
|
|
320
385
|
they exist on disk
|
|
321
|
-
- `remix/form-data
|
|
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
|
|
329
|
-
- `remix/logger
|
|
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
|
|
396
|
+
- `remix/middleware/method-override` — `methodOverride()`. Use when HTML forms need `PUT`,
|
|
332
397
|
`PATCH`, or `DELETE`
|
|
333
|
-
- `remix/async-context
|
|
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
|
|
336
|
-
- `remix/csrf
|
|
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
|
|
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
|
|
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/
|
|
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
|
|
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
|
-
}
|
|
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
|
-
###
|
|
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
|
-
}
|
|
558
|
+
})
|
|
474
559
|
```
|
|
475
560
|
|
|
476
561
|
This shape works without JavaScript, returns a `Response` for every outcome, and is ready for
|
package/{bootstrap → template}/.agents/skills/remix/references/assets-and-browser-modules.md
RENAMED
|
@@ -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,
|
|
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 {
|
|
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
|
-
|
|
39
|
+
basePath: '/assets',
|
|
40
|
+
rootDir: process.cwd(),
|
|
36
41
|
fileMap: {
|
|
37
|
-
'
|
|
38
|
-
'
|
|
42
|
+
'app/*path': 'app/*path',
|
|
43
|
+
'node_modules/*path': 'node_modules/*path',
|
|
39
44
|
},
|
|
40
|
-
allow: ['app/assets/**', '
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
- `
|
|
66
|
-
|
|
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
|