@remix-run/cli 0.3.1 → 0.3.2
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.js +7 -4
- package/package.json +3 -3
- package/src/lib/bootstrap-project.ts +9 -6
- package/template/.agents/skills/remix/SKILL.md +115 -262
- package/template/.agents/skills/remix/references/animate-elements.md +8 -17
- package/template/.agents/skills/remix/references/assets-and-browser-modules.md +14 -33
- package/template/.agents/skills/remix/references/auth-and-sessions.md +10 -31
- package/template/.agents/skills/remix/references/component-model.md +11 -26
- package/template/.agents/skills/remix/references/create-mixins.md +4 -9
- package/template/.agents/skills/remix/references/data-and-validation.md +54 -77
- package/template/.agents/skills/remix/references/hydration-frames-navigation.md +23 -56
- package/template/.agents/skills/remix/references/middleware-and-server.md +17 -38
- package/template/.agents/skills/remix/references/mixins-styling-events.md +14 -32
- package/template/.agents/skills/remix/references/routing-and-controllers.md +17 -43
- package/template/.agents/skills/remix/references/testing-patterns.md +13 -32
- package/template/app/ui/document.tsx +22 -17
- package/template/app/ui/scaffold-home-page.tsx +56 -47
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## What This Covers
|
|
4
4
|
|
|
5
|
-
How server-rendered UI becomes interactive in the browser, and how the page updates without a full
|
|
6
|
-
navigation. Read this when the task involves:
|
|
5
|
+
How server-rendered UI becomes interactive in the browser, and how the page updates without a full navigation. Read this when the task involves:
|
|
7
6
|
|
|
8
7
|
- Marking a component for client-side hydration with `clientEntry`
|
|
9
8
|
- Booting the client runtime with `run`
|
|
@@ -12,19 +11,13 @@ navigation. Read this when the task involves:
|
|
|
12
11
|
- Server rendering with `renderToStream` or `renderToString`
|
|
13
12
|
- Managing the document `<head>`
|
|
14
13
|
|
|
15
|
-
For component-local state and updates, see `component-model.md`. For host-element behavior and
|
|
16
|
-
events, see `mixins-styling-events.md`.
|
|
14
|
+
For component-local state and updates, see `component-model.md`. For host-element behavior and events, see `mixins-styling-events.md`.
|
|
17
15
|
|
|
18
16
|
## Server First, Then Hydrate
|
|
19
17
|
|
|
20
|
-
Make the server route correct before adding `clientEntry(...)`. A POST should already do the right
|
|
21
|
-
thing on its own — return HTML, a redirect, or an error response — and a GET should already render
|
|
22
|
-
the page the user expects. `clientEntry` exists to layer interactivity on top of UI that already
|
|
23
|
-
works without it.
|
|
18
|
+
Make the server route correct before adding `clientEntry(...)`. A POST should already do the right thing on its own — return HTML, a redirect, or an error response — and a GET should already render the page the user expects. `clientEntry` exists to layer interactivity on top of UI that already works without it.
|
|
24
19
|
|
|
25
|
-
When server state changes after a mutation, prefer reloading a `<Frame>` when the UI region already
|
|
26
|
-
maps cleanly to a server-rendered route. Frames re-fetch the same route, so the rendering logic
|
|
27
|
-
stays in one place and the client does not need a parallel "state" API.
|
|
20
|
+
When server state changes after a mutation, prefer reloading a `<Frame>` when the UI region already maps cleanly to a server-rendered route. Frames re-fetch the same route, so the rendering logic stays in one place and the client does not need a parallel "state" API.
|
|
28
21
|
|
|
29
22
|
```tsx
|
|
30
23
|
on('submit', async (event, signal) => {
|
|
@@ -39,15 +32,11 @@ on('submit', async (event, signal) => {
|
|
|
39
32
|
})
|
|
40
33
|
```
|
|
41
34
|
|
|
42
|
-
Use polling or a small JSON state endpoint when the data changes outside this page, or when a tiny
|
|
43
|
-
shared widget would be heavier to model as a frame. Pick the lightest sync mechanism that preserves
|
|
44
|
-
clear ownership of rendering logic.
|
|
35
|
+
Use polling or a small JSON state endpoint when the data changes outside this page, or when a tiny shared widget would be heavier to model as a frame. Pick the lightest sync mechanism that preserves clear ownership of rendering logic.
|
|
45
36
|
|
|
46
37
|
## Client Entries
|
|
47
38
|
|
|
48
|
-
Use `clientEntry` to mark a component for client-side hydration. In source-served apps, prefer the
|
|
49
|
-
source module's `import.meta.url` as the entry ID and let server rendering map it to the public
|
|
50
|
-
asset URL:
|
|
39
|
+
Use `clientEntry` to mark a component for client-side hydration. In source-served apps, prefer the source module's `import.meta.url` as the entry ID and let server rendering map it to the public asset URL:
|
|
51
40
|
|
|
52
41
|
```tsx
|
|
53
42
|
import { clientEntry, on, type Handle } from 'remix/ui'
|
|
@@ -76,9 +65,7 @@ export const Counter = clientEntry(
|
|
|
76
65
|
)
|
|
77
66
|
```
|
|
78
67
|
|
|
79
|
-
On the server, provide `resolveClientEntry` to `renderToStream(...)` so source file URLs become
|
|
80
|
-
browser-loadable asset URLs. Keep this resolution in the render helper so component modules do not
|
|
81
|
-
hard-code deployment-specific asset paths:
|
|
68
|
+
On the server, provide `resolveClientEntry` to `renderToStream(...)` so source file URLs become browser-loadable asset URLs. Keep this resolution in the render helper so component modules do not hard-code deployment-specific asset paths:
|
|
82
69
|
|
|
83
70
|
```tsx
|
|
84
71
|
let stream = renderToStream(<App />, {
|
|
@@ -96,21 +83,15 @@ let stream = renderToStream(<App />, {
|
|
|
96
83
|
})
|
|
97
84
|
```
|
|
98
85
|
|
|
99
|
-
If the module export name differs from the component function name, include `#ExportName` in the
|
|
100
|
-
entry ID or return the exact export name from `resolveClientEntry`. A render helper that only
|
|
101
|
-
supports source-owned entries can also fail fast when `entryId` is not a `file://` URL.
|
|
86
|
+
If the module export name differs from the component function name, include `#ExportName` in the entry ID or return the exact export name from `resolveClientEntry`. A render helper that only supports source-owned entries can also fail fast when `entryId` is not a `file://` URL.
|
|
102
87
|
|
|
103
|
-
On the server, `clientEntry` components render like any other component. The server wraps their
|
|
104
|
-
output in comment markers and serializes props into a `<script type="application/json">` tag.
|
|
88
|
+
On the server, `clientEntry` components render like any other component. The server wraps their output in comment markers and serializes props into a `<script type="application/json">` tag.
|
|
105
89
|
|
|
106
|
-
Client entry props must be serializable: strings, numbers, booleans, `null`, `undefined`, plain
|
|
107
|
-
objects/arrays of the above, JSX elements, and `<Frame>` elements. Functions and class instances
|
|
108
|
-
cannot be passed.
|
|
90
|
+
Client entry props must be serializable: strings, numbers, booleans, `null`, `undefined`, plain objects/arrays of the above, JSX elements, and `<Frame>` elements. Functions and class instances cannot be passed.
|
|
109
91
|
|
|
110
92
|
## Booting the Client
|
|
111
93
|
|
|
112
|
-
Use `run` to start the client runtime. It scans the document for client entry markers, loads
|
|
113
|
-
modules, and hydrates each one:
|
|
94
|
+
Use `run` to start the client runtime. It scans the document for client entry markers, loads modules, and hydrates each one:
|
|
114
95
|
|
|
115
96
|
```tsx
|
|
116
97
|
import { run } from 'remix/ui'
|
|
@@ -137,10 +118,8 @@ await app.ready()
|
|
|
137
118
|
|
|
138
119
|
### `run` options
|
|
139
120
|
|
|
140
|
-
- **`loadModule(moduleUrl, exportName)`** (required) — return the component function for each
|
|
141
|
-
|
|
142
|
-
- **`resolveFrame(src, signal, target)`** (optional) — called when a `<Frame>` loads or reloads
|
|
143
|
-
content. `target` is available when frame targeting matters.
|
|
121
|
+
- **`loadModule(moduleUrl, exportName)`** (required) — return the component function for each client entry. Typically uses dynamic `import()`.
|
|
122
|
+
- **`resolveFrame(src, signal, target)`** (optional) — called when a `<Frame>` loads or reloads content. `target` is available when frame targeting matters.
|
|
144
123
|
|
|
145
124
|
### `app` methods
|
|
146
125
|
|
|
@@ -152,8 +131,7 @@ await app.ready()
|
|
|
152
131
|
|
|
153
132
|
## Frames
|
|
154
133
|
|
|
155
|
-
A `<Frame>` renders server content into the page. Frames stream after the initial HTML, nest inside
|
|
156
|
-
other frames, contain client entries, and can be reloaded without full page navigation.
|
|
134
|
+
A `<Frame>` renders server content into the page. Frames stream after the initial HTML, nest inside other frames, contain client entries, and can be reloaded without full page navigation.
|
|
157
135
|
|
|
158
136
|
```tsx
|
|
159
137
|
import { Frame } from 'remix/ui'
|
|
@@ -177,10 +155,8 @@ function App() {
|
|
|
177
155
|
|
|
178
156
|
### Blocking vs non-blocking
|
|
179
157
|
|
|
180
|
-
- **Without `fallback`** (blocking) — the server waits for frame content before sending the initial
|
|
181
|
-
|
|
182
|
-
- **With `fallback`** (non-blocking) — the fallback renders immediately; real content streams in
|
|
183
|
-
later and replaces it
|
|
158
|
+
- **Without `fallback`** (blocking) — the server waits for frame content before sending the initial HTML chunk
|
|
159
|
+
- **With `fallback`** (non-blocking) — the fallback renders immediately; real content streams in later and replaces it
|
|
184
160
|
|
|
185
161
|
### Reloading frames
|
|
186
162
|
|
|
@@ -197,21 +173,17 @@ await handle.frames.get('cart-summary')?.reload()
|
|
|
197
173
|
handle.frames.top.reload()
|
|
198
174
|
```
|
|
199
175
|
|
|
200
|
-
When a frame reloads, matching DOM nodes are updated in place. Client entries receive updated props
|
|
201
|
-
while preserving their local component state.
|
|
176
|
+
When a frame reloads, matching DOM nodes are updated in place. Client entries receive updated props while preserving their local component state.
|
|
202
177
|
|
|
203
178
|
### Nested frames
|
|
204
179
|
|
|
205
|
-
Frames can nest. Each frame owns its own DOM region and hydrates client entries independently.
|
|
206
|
-
During SSR, `handle.frame.src` points at the frame being rendered, while
|
|
207
|
-
`handle.frames.top.src` stays fixed at the outer document URL.
|
|
180
|
+
Frames can nest. Each frame owns its own DOM region and hydrates client entries independently. During SSR, `handle.frame.src` points at the frame being rendered, while `handle.frames.top.src` stays fixed at the outer document URL.
|
|
208
181
|
|
|
209
182
|
## Server Rendering
|
|
210
183
|
|
|
211
184
|
### `renderToStream`
|
|
212
185
|
|
|
213
|
-
Renders a component tree to a `ReadableStream<Uint8Array>`. Sends initial HTML immediately and
|
|
214
|
-
streams frame content as it resolves:
|
|
186
|
+
Renders a component tree to a `ReadableStream<Uint8Array>`. Sends initial HTML immediately and streams frame content as it resolves:
|
|
215
187
|
|
|
216
188
|
```tsx
|
|
217
189
|
import { renderToStream } from 'remix/ui/server'
|
|
@@ -235,11 +207,8 @@ return new Response(stream, {
|
|
|
235
207
|
Options:
|
|
236
208
|
|
|
237
209
|
- **`frameSrc`** — seeds SSR frame state; populates `handle.frame.src` and `handle.frames.top.src`
|
|
238
|
-
- **`topFrameSrc`** — overrides the root frame URL for nested frame renders (carry forward from
|
|
239
|
-
|
|
240
|
-
- **`resolveFrame(src, target, context)`** — return HTML string, `ReadableStream<Uint8Array>`, or a
|
|
241
|
-
promise of either. `context.currentFrameSrc` is the containing frame URL; `context.topFrameSrc`
|
|
242
|
-
is the outer document URL
|
|
210
|
+
- **`topFrameSrc`** — overrides the root frame URL for nested frame renders (carry forward from `resolveFrame` context)
|
|
211
|
+
- **`resolveFrame(src, target, context)`** — return HTML string, `ReadableStream<Uint8Array>`, or a promise of either. `context.currentFrameSrc` is the containing frame URL; `context.topFrameSrc` is the outer document URL
|
|
243
212
|
- **`onError(error)`** — called on rendering errors
|
|
244
213
|
|
|
245
214
|
### `renderToString`
|
|
@@ -253,8 +222,7 @@ let html = await renderToString(<App />)
|
|
|
253
222
|
|
|
254
223
|
### CSS in SSR
|
|
255
224
|
|
|
256
|
-
Components using the `css` mixin have styles collected during rendering and emitted as a single
|
|
257
|
-
`<style>` tag in `<head>`. No client-side style injection needed.
|
|
225
|
+
Components using the `css` mixin have styles collected during rendering and emitted as a single `<style>` tag in `<head>`. No client-side style injection needed.
|
|
258
226
|
|
|
259
227
|
## Navigation
|
|
260
228
|
|
|
@@ -293,5 +261,4 @@ function App() {
|
|
|
293
261
|
}
|
|
294
262
|
```
|
|
295
263
|
|
|
296
|
-
Put `title`, `meta`, `link`, and `style` tags inside an explicit `<head>`. Bare head-like tags
|
|
297
|
-
rendered outside `<head>` stay where they are — they are not moved into the document head for you.
|
|
264
|
+
Put `title`, `meta`, `link`, and `style` tags inside an explicit `<head>`. Bare head-like tags rendered outside `<head>` stay where they are — they are not moved into the document head for you.
|
|
@@ -2,22 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## What This Covers
|
|
4
4
|
|
|
5
|
-
How to compose the request lifecycle and bridge the router to a runtime. Read this when the task
|
|
6
|
-
involves:
|
|
5
|
+
How to compose the request lifecycle and bridge the router to a runtime. Read this when the task involves:
|
|
7
6
|
|
|
8
7
|
- Choosing or ordering built-in middleware in the root stack
|
|
9
8
|
- Writing custom middleware that sets typed context values
|
|
10
|
-
- Adding fast-exit handling (static files, CORS preflights) versus request-enriching layers
|
|
11
|
-
(sessions, auth, data loading)
|
|
9
|
+
- Adding fast-exit handling (static files, CORS preflights) versus request-enriching layers (sessions, auth, data loading)
|
|
12
10
|
- Choosing when to keep the generated Node server versus switching server adapters
|
|
13
11
|
|
|
14
|
-
For data and persistence specifics, see `data-and-validation.md`. For session and auth specifics,
|
|
15
|
-
see `auth-and-sessions.md`.
|
|
12
|
+
For data and persistence specifics, see `data-and-validation.md`. For session and auth specifics, see `auth-and-sessions.md`.
|
|
16
13
|
|
|
17
14
|
## Middleware Stack
|
|
18
15
|
|
|
19
|
-
Middleware runs in order for every request. Place fast-exit middleware (static files) early and
|
|
20
|
-
request-enriching middleware (session, auth) later.
|
|
16
|
+
Middleware runs in order for every request. Place fast-exit middleware (static files) early and request-enriching middleware (session, auth) later.
|
|
21
17
|
|
|
22
18
|
Recommended ordering:
|
|
23
19
|
|
|
@@ -68,29 +64,22 @@ let router = createRouter({ middleware })
|
|
|
68
64
|
|
|
69
65
|
### Static files vs browser modules
|
|
70
66
|
|
|
71
|
-
- Use `staticFiles()` for files that should be served directly from disk, such as images, fonts,
|
|
72
|
-
|
|
73
|
-
- Use `remix/assets` when browser modules should be compiled and served from source files with
|
|
74
|
-
import rewriting, preloads, or fingerprinted URLs
|
|
67
|
+
- Use `staticFiles()` for files that should be served directly from disk, such as images, fonts, or already-built assets in `public/`
|
|
68
|
+
- Use `remix/assets` when browser modules should be compiled and served from source files with import rewriting, preloads, or fingerprinted URLs
|
|
75
69
|
|
|
76
70
|
### Ordering notes
|
|
77
71
|
|
|
78
72
|
- Put fast exits early: `staticFiles()`, `cors()` preflight handling, and `cop()` when used
|
|
79
|
-
- Parse request bodies before middleware that depends on them, such as `methodOverride()` and form
|
|
80
|
-
field token extraction in `csrf()`
|
|
73
|
+
- Parse request bodies before middleware that depends on them, such as `methodOverride()` and form field token extraction in `csrf()`
|
|
81
74
|
- Run `session()` before `csrf()` and before session-backed `auth()`
|
|
82
75
|
- Add `asyncContext()` before helpers or shared code call `getContext()`
|
|
83
|
-
- Keep route protection like `requireAuth()` at controller or action scope unless the entire app is
|
|
84
|
-
private
|
|
76
|
+
- Keep route protection like `requireAuth()` at controller or action scope unless the entire app is private
|
|
85
77
|
|
|
86
78
|
### Common stacks
|
|
87
79
|
|
|
88
|
-
- **Session-backed HTML app** -> `compression()`, `staticFiles()`, optional `cop()`, `formData()`,
|
|
89
|
-
|
|
90
|
-
- **
|
|
91
|
-
`auth({ schemes })`
|
|
92
|
-
- **Upload flow** -> `compression()`, `staticFiles()`, `formData({ uploadHandler })`, then
|
|
93
|
-
sessions, auth, and data-loading middleware as needed
|
|
80
|
+
- **Session-backed HTML app** -> `compression()`, `staticFiles()`, optional `cop()`, `formData()`, `methodOverride()`, `session()`, optional `csrf()`, `asyncContext()`, `auth({ schemes })`
|
|
81
|
+
- **Cross-origin API** -> `compression()`, `cors()`, optional `asyncContext()`, optional `auth({ schemes })`
|
|
82
|
+
- **Upload flow** -> `compression()`, `staticFiles()`, `formData({ uploadHandler })`, then sessions, auth, and data-loading middleware as needed
|
|
94
83
|
|
|
95
84
|
### Middleware with options
|
|
96
85
|
|
|
@@ -115,14 +104,11 @@ formData({
|
|
|
115
104
|
})
|
|
116
105
|
```
|
|
117
106
|
|
|
118
|
-
Errors thrown or rejected by `uploadHandler` propagate directly. Catch domain-specific upload
|
|
119
|
-
errors at the route boundary when they should become user-facing `Response` objects.
|
|
107
|
+
Errors thrown or rejected by `uploadHandler` propagate directly. Catch domain-specific upload errors at the route boundary when they should become user-facing `Response` objects.
|
|
120
108
|
|
|
121
109
|
## Writing Custom Middleware
|
|
122
110
|
|
|
123
|
-
Middleware is a function that receives `(context, next)`. Return a `Response` to short-circuit, call
|
|
124
|
-
and return `next()` when you need the downstream response, or return nothing when you only set
|
|
125
|
-
context and want the router to continue automatically.
|
|
111
|
+
Middleware is a function that receives `(context, next)`. Return a `Response` to short-circuit, call and return `next()` when you need the downstream response, or return nothing when you only set context and want the router to continue automatically.
|
|
126
112
|
|
|
127
113
|
### Setting context values
|
|
128
114
|
|
|
@@ -158,9 +144,7 @@ export function requireAdmin(): Middleware {
|
|
|
158
144
|
|
|
159
145
|
### Async context for helpers
|
|
160
146
|
|
|
161
|
-
`asyncContext()` stores the request context in `AsyncLocalStorage` so helpers can reach it
|
|
162
|
-
without the context being threaded through every call. Wrap `getContext()` in app-specific
|
|
163
|
-
helpers:
|
|
147
|
+
`asyncContext()` stores the request context in `AsyncLocalStorage` so helpers can reach it without the context being threaded through every call. Wrap `getContext()` in app-specific helpers:
|
|
164
148
|
|
|
165
149
|
```typescript
|
|
166
150
|
// app/utils/context.ts
|
|
@@ -210,8 +194,7 @@ Middleware can be applied at three levels:
|
|
|
210
194
|
})
|
|
211
195
|
```
|
|
212
196
|
|
|
213
|
-
Controller middleware does not flow into other controllers. Add the middleware to each
|
|
214
|
-
controller that needs it.
|
|
197
|
+
Controller middleware does not flow into other controllers. Add the middleware to each controller that needs it.
|
|
215
198
|
|
|
216
199
|
3. **Action-level** — runs for a single route:
|
|
217
200
|
```typescript
|
|
@@ -223,10 +206,6 @@ Middleware can be applied at three levels:
|
|
|
223
206
|
|
|
224
207
|
## Node Server Setup
|
|
225
208
|
|
|
226
|
-
New apps already include a `server.ts` that adapts the app router with
|
|
227
|
-
`remix/node-fetch-server`. Keep that generated server unless the task specifically needs to change
|
|
228
|
-
runtime behavior such as host/protocol handling, TLS, HTTP/2, WebSockets, deployment lifecycle, or
|
|
229
|
-
test-only server setup.
|
|
209
|
+
New apps already include a `server.ts` that adapts the app router with `remix/node-fetch-server`. Keep that generated server unless the task specifically needs to change runtime behavior such as host/protocol handling, TLS, HTTP/2, WebSockets, deployment lifecycle, or test-only server setup.
|
|
230
210
|
|
|
231
|
-
Use `remix/node-fetch-server` when you want to keep owning a standard Node `http`, `https`, or
|
|
232
|
-
`http2` server directly.
|
|
211
|
+
Use `remix/node-fetch-server` when you want to keep owning a standard Node `http`, `https`, or `http2` server directly.
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## What This Covers
|
|
4
4
|
|
|
5
|
-
How to attach behavior, styles, and DOM-aware setup to host elements with `mix`. Read this when the
|
|
6
|
-
task involves:
|
|
5
|
+
How to attach behavior, styles, and DOM-aware setup to host elements with `mix`. Read this when the task involves:
|
|
7
6
|
|
|
8
7
|
- DOM event handling with `on(...)`
|
|
9
8
|
- Static styling with `css(...)` and dynamic styling with `style`
|
|
@@ -12,18 +11,13 @@ task involves:
|
|
|
12
11
|
- Native click, pointer, and keyboard behavior with `on(...)`, plus attributes with `attrs(...)`
|
|
13
12
|
- Element-level animation mixins from `remix/ui/animation`
|
|
14
13
|
|
|
15
|
-
For richer animation work (springs, tweens, layout transitions), see `animate-elements.md`. For
|
|
16
|
-
authoring custom mixins, see `create-mixins.md`. For component lifecycle and updates, see
|
|
17
|
-
`component-model.md`.
|
|
14
|
+
For richer animation work (springs, tweens, layout transitions), see `animate-elements.md`. For authoring custom mixins, see `create-mixins.md`. For component lifecycle and updates, see `component-model.md`.
|
|
18
15
|
|
|
19
|
-
Compose behavior on host elements with `mix`. Pass a single mixin directly (`mix={on(...)}`), or
|
|
20
|
-
an array when composing multiple mixins (`mix={[css(...), on(...)]}`). Core mixins are imported
|
|
21
|
-
from `remix/ui`; animation mixins are imported from `remix/ui/animation`.
|
|
16
|
+
Compose behavior on host elements with `mix`. Pass a single mixin directly (`mix={on(...)}`), or an array when composing multiple mixins (`mix={[css(...), on(...)]}`). Core mixins are imported from `remix/ui`; animation mixins are imported from `remix/ui/animation`.
|
|
22
17
|
|
|
23
18
|
## `on(type, handler, capture?)`
|
|
24
19
|
|
|
25
|
-
Attaches a typed DOM event handler. The handler receives the event and an `AbortSignal` that aborts
|
|
26
|
-
when the handler is re-entered or the component is removed — this prevents race conditions:
|
|
20
|
+
Attaches a typed DOM event handler. The handler receives the event and an `AbortSignal` that aborts when the handler is re-entered or the component is removed — this prevents race conditions:
|
|
27
21
|
|
|
28
22
|
```tsx
|
|
29
23
|
<input
|
|
@@ -56,9 +50,7 @@ Multiple events on the same element:
|
|
|
56
50
|
|
|
57
51
|
## `css(styles)`
|
|
58
52
|
|
|
59
|
-
Applies generated class names for CSS object styles. Produces static CSS rules inserted into the
|
|
60
|
-
document. Supports pseudo-selectors, pseudo-elements, attribute selectors, descendant selectors, and
|
|
61
|
-
media queries using `&` to reference the current element:
|
|
53
|
+
Applies generated class names for CSS object styles. Produces static CSS rules inserted into the document. Supports pseudo-selectors, pseudo-elements, attribute selectors, descendant selectors, and media queries using `&` to reference the current element:
|
|
62
54
|
|
|
63
55
|
```tsx
|
|
64
56
|
<button
|
|
@@ -80,9 +72,7 @@ media queries using `&` to reference the current element:
|
|
|
80
72
|
|
|
81
73
|
### `css(...)` vs `style` prop
|
|
82
74
|
|
|
83
|
-
Use `css(...)` for static styles, selectors, and media queries. Use `style` for dynamic values that
|
|
84
|
-
change often. Prefer CSS nested selectors for parent-state-affects-children over managing hover/focus
|
|
85
|
-
state in JavaScript:
|
|
75
|
+
Use `css(...)` for static styles, selectors, and media queries. Use `style` for dynamic values that change often. Prefer CSS nested selectors for parent-state-affects-children over managing hover/focus state in JavaScript:
|
|
86
76
|
|
|
87
77
|
```tsx
|
|
88
78
|
<div
|
|
@@ -96,8 +86,7 @@ state in JavaScript:
|
|
|
96
86
|
|
|
97
87
|
## `ref(callback)`
|
|
98
88
|
|
|
99
|
-
Calls a callback when an element is inserted. The callback receives the DOM node and an
|
|
100
|
-
`AbortSignal` that aborts when the element is removed:
|
|
89
|
+
Calls a callback when an element is inserted. The callback receives the DOM node and an `AbortSignal` that aborts when the element is removed:
|
|
101
90
|
|
|
102
91
|
```tsx
|
|
103
92
|
<input mix={ref((node) => node.focus())} />
|
|
@@ -116,8 +105,7 @@ The `ref` callback runs once when the element is first rendered, not on every up
|
|
|
116
105
|
|
|
117
106
|
## `link(href, options?)`
|
|
118
107
|
|
|
119
|
-
Adds client-side navigation behavior to any element. Makes non-anchor elements behave like Remix
|
|
120
|
-
navigation links:
|
|
108
|
+
Adds client-side navigation behavior to any element. Makes non-anchor elements behave like Remix navigation links:
|
|
121
109
|
|
|
122
110
|
```tsx
|
|
123
111
|
<article mix={link('/courses/intro')}>
|
|
@@ -125,20 +113,17 @@ navigation links:
|
|
|
125
113
|
</article>
|
|
126
114
|
```
|
|
127
115
|
|
|
128
|
-
Options match `NavigationOptions`: `src`, `target`, `history` (`'push' | 'replace'`),
|
|
129
|
-
`resetScroll`.
|
|
116
|
+
Options match `NavigationOptions`: `src`, `target`, `history` (`'push' | 'replace'`), `resetScroll`.
|
|
130
117
|
|
|
131
118
|
## Native press and keyboard interactions
|
|
132
119
|
|
|
133
|
-
Use native DOM events directly with `on(...)`. For buttons and links, `click` already includes
|
|
134
|
-
keyboard activation when the element has the right semantics:
|
|
120
|
+
Use native DOM events directly with `on(...)`. For buttons and links, `click` already includes keyboard activation when the element has the right semantics:
|
|
135
121
|
|
|
136
122
|
```tsx
|
|
137
123
|
<button mix={on('click', () => doAction())}>Action</button>
|
|
138
124
|
```
|
|
139
125
|
|
|
140
|
-
For gesture-specific behavior, compose the pointer or keyboard events the interaction actually
|
|
141
|
-
needs:
|
|
126
|
+
For gesture-specific behavior, compose the pointer or keyboard events the interaction actually needs:
|
|
142
127
|
|
|
143
128
|
```tsx
|
|
144
129
|
<button
|
|
@@ -177,8 +162,7 @@ Animates an element when it is inserted into the DOM. Config specifies the **sta
|
|
|
177
162
|
|
|
178
163
|
### `animateExit(config)`
|
|
179
164
|
|
|
180
|
-
Animates an element when it is removed. Config specifies the **ending** style. The element is kept
|
|
181
|
-
in the DOM until the animation completes:
|
|
165
|
+
Animates an element when it is removed. Config specifies the **ending** style. The element is kept in the DOM until the animation completes:
|
|
182
166
|
|
|
183
167
|
```tsx
|
|
184
168
|
{
|
|
@@ -206,8 +190,6 @@ Animates layout changes (position/size) using FLIP-style transforms:
|
|
|
206
190
|
}
|
|
207
191
|
```
|
|
208
192
|
|
|
209
|
-
Options: `duration` (default 200ms), `easing` (default spring snappy), `size` (boolean, default
|
|
210
|
-
true — include scale projection for size changes).
|
|
193
|
+
Options: `duration` (default 200ms), `easing` (default spring snappy), `size` (boolean, default true — include scale projection for size changes).
|
|
211
194
|
|
|
212
|
-
Always key elements you expect to animate. Use `...spring(preset)` to spread `duration` and
|
|
213
|
-
`easing` into any animation config.
|
|
195
|
+
Always key elements you expect to animate. Use `...spring(preset)` to spread `duration` and `easing` into any animation config.
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## What This Covers
|
|
4
4
|
|
|
5
|
-
Patterns for declaring URLs, handling requests, and wiring routes to controllers. Read this when
|
|
6
|
-
the task involves:
|
|
5
|
+
Patterns for declaring URLs, handling requests, and wiring routes to controllers. Read this when the task involves:
|
|
7
6
|
|
|
8
7
|
- Defining or changing the URL surface of the app
|
|
9
8
|
- Writing or reorganizing controllers and actions
|
|
@@ -11,9 +10,7 @@ the task involves:
|
|
|
11
10
|
- Returning a `Response` for HTML, redirects, JSON, or errors
|
|
12
11
|
- Generating internal URLs with `.href()`
|
|
13
12
|
|
|
14
|
-
The companion reference for shaping `Request` bodies, validating input, and dealing with persisted
|
|
15
|
-
data is `data-and-validation.md`. For request lifecycle and middleware ordering, see
|
|
16
|
-
`middleware-and-server.md`.
|
|
13
|
+
The companion reference for shaping `Request` bodies, validating input, and dealing with persisted data is `data-and-validation.md`. For request lifecycle and middleware ordering, see `middleware-and-server.md`.
|
|
17
14
|
|
|
18
15
|
## Route Builders
|
|
19
16
|
|
|
@@ -21,10 +18,7 @@ Import all route builders from `remix/routes`.
|
|
|
21
18
|
|
|
22
19
|
### `route(prefix, map)` — nested route group
|
|
23
20
|
|
|
24
|
-
Adds a URL prefix to all children. Can also be called as `route(map)` without a prefix for a
|
|
25
|
-
top-level grouping. Inside `route(...)`, a nested map may be either a `route('prefix', { ... })`
|
|
26
|
-
call (when you want a shared URL prefix) or a plain object literal (when each leaf already owns
|
|
27
|
-
its absolute path).
|
|
21
|
+
Adds a URL prefix to all children. Can also be called as `route(map)` without a prefix for a top-level grouping. Inside `route(...)`, a nested map may be either a `route('prefix', { ... })` call (when you want a shared URL prefix) or a plain object literal (when each leaf already owns its absolute path).
|
|
28
22
|
|
|
29
23
|
```typescript
|
|
30
24
|
import { route, get, post } from 'remix/routes'
|
|
@@ -58,8 +52,7 @@ export const routes = route({
|
|
|
58
52
|
|
|
59
53
|
### `form(path, options?)` — form route
|
|
60
54
|
|
|
61
|
-
Creates a GET + POST pair for HTML form workflows. Expands to an `index` (GET) and an `action`
|
|
62
|
-
(POST) by default.
|
|
55
|
+
Creates a GET + POST pair for HTML form workflows. Expands to an `index` (GET) and an `action` (POST) by default.
|
|
63
56
|
|
|
64
57
|
```typescript
|
|
65
58
|
contact: form('contact')
|
|
@@ -92,9 +85,7 @@ redirect(routes.account.orders.show.href({ orderId: '42' }))
|
|
|
92
85
|
|
|
93
86
|
## Actions
|
|
94
87
|
|
|
95
|
-
An action is the handler for one leaf route. In Remix app code, actions should live in controllers.
|
|
96
|
-
Use `Action` only when a reusable helper needs to type one action before it is added to a
|
|
97
|
-
controller or when you are doing low-level router wiring outside the `app/actions` convention:
|
|
88
|
+
An action is the handler for one leaf route. In Remix app code, actions should live in controllers. Use `Action` only when a reusable helper needs to type one action before it is added to a controller or when you are doing low-level router wiring outside the `app/actions` convention:
|
|
98
89
|
|
|
99
90
|
```typescript
|
|
100
91
|
import { createAction } from 'remix/router'
|
|
@@ -130,8 +121,7 @@ router.get(routes.account.index, {
|
|
|
130
121
|
|
|
131
122
|
## Returning Responses
|
|
132
123
|
|
|
133
|
-
An action returns a `Response`. The shape of that response is part of the route contract, and
|
|
134
|
-
choosing it well saves a lot of glue elsewhere.
|
|
124
|
+
An action returns a `Response`. The shape of that response is part of the route contract, and choosing it well saves a lot of glue elsewhere.
|
|
135
125
|
|
|
136
126
|
### Render HTML
|
|
137
127
|
|
|
@@ -147,8 +137,7 @@ async handler({ get }) {
|
|
|
147
137
|
|
|
148
138
|
### Redirect after a mutation
|
|
149
139
|
|
|
150
|
-
For state-changing routes (POST, PUT, PATCH, DELETE), the canonical reply is a redirect to the
|
|
151
|
-
resulting page. Pass `303` explicitly when you want a POST-redirect-GET flow:
|
|
140
|
+
For state-changing routes (POST, PUT, PATCH, DELETE), the canonical reply is a redirect to the resulting page. Pass `303` explicitly when you want a POST-redirect-GET flow:
|
|
152
141
|
|
|
153
142
|
```typescript
|
|
154
143
|
import { redirect } from 'remix/response/redirect'
|
|
@@ -167,13 +156,11 @@ async create({ get }) {
|
|
|
167
156
|
}
|
|
168
157
|
```
|
|
169
158
|
|
|
170
|
-
This pattern works without JavaScript and stays compatible with `clientEntry(...)` enhancements
|
|
171
|
-
on top.
|
|
159
|
+
This pattern works without JavaScript and stays compatible with `clientEntry(...)` enhancements on top.
|
|
172
160
|
|
|
173
161
|
### Return an error response
|
|
174
162
|
|
|
175
|
-
For expected failures — validation, conflict, not found — return a `Response` directly. Reserve
|
|
176
|
-
thrown errors for genuinely unexpected failures.
|
|
163
|
+
For expected failures — validation, conflict, not found — return a `Response` directly. Reserve thrown errors for genuinely unexpected failures.
|
|
177
164
|
|
|
178
165
|
```typescript
|
|
179
166
|
async show({ get, params }) {
|
|
@@ -198,9 +185,7 @@ if (!parsed.success) {
|
|
|
198
185
|
|
|
199
186
|
### Return JSON
|
|
200
187
|
|
|
201
|
-
For routes consumed by client code rather than rendered as a page (autocomplete endpoints, polling
|
|
202
|
-
APIs, inter-service calls), return a JSON `Response`. Use `SuperHeaders` from `remix/headers` when
|
|
203
|
-
typed header accessors make the response clearer:
|
|
188
|
+
For routes consumed by client code rather than rendered as a page (autocomplete endpoints, polling APIs, inter-service calls), return a JSON `Response`. Use `SuperHeaders` from `remix/headers` when typed header accessors make the response clearer:
|
|
204
189
|
|
|
205
190
|
```typescript
|
|
206
191
|
import Headers from 'remix/headers'
|
|
@@ -214,20 +199,13 @@ return new Response(JSON.stringify({ results }), {
|
|
|
214
199
|
})
|
|
215
200
|
```
|
|
216
201
|
|
|
217
|
-
If you find yourself returning JSON for what is really a browser form submission, prefer the
|
|
218
|
-
redirect-after-POST pattern instead. JSON-only mutation endpoints make it harder to support
|
|
219
|
-
non-JS clients, harder to share rendering logic, and easier for the client to drift out of sync
|
|
220
|
-
with the server.
|
|
202
|
+
If you find yourself returning JSON for what is really a browser form submission, prefer the redirect-after-POST pattern instead. JSON-only mutation endpoints make it harder to support non-JS clients, harder to share rendering logic, and easier for the client to drift out of sync with the server.
|
|
221
203
|
|
|
222
204
|
## Controllers
|
|
223
205
|
|
|
224
|
-
A controller owns the direct leaf routes in one route map. Each key in `actions` matches a direct
|
|
225
|
-
leaf route key in the route definition passed to `router.map(...)`. Nested route-map keys do not
|
|
226
|
-
belong inside a controller's `actions`; map those route maps with their own controllers.
|
|
206
|
+
A controller owns the direct leaf routes in one route map. Each key in `actions` matches a direct leaf route key in the route definition passed to `router.map(...)`. Nested route-map keys do not belong inside a controller's `actions`; map those route maps with their own controllers.
|
|
227
207
|
|
|
228
|
-
Configure `RouterTypes.context` with your app context in the router module, then use
|
|
229
|
-
`createController()` so `get(Database)`, `get(Session)`, `get(Auth)`, etc. are typed against your
|
|
230
|
-
middleware stack without repeating a type clause on every controller.
|
|
208
|
+
Configure `RouterTypes.context` with your app context in the router module, then use `createController()` so `get(Database)`, `get(Session)`, `get(Auth)`, etc. are typed against your middleware stack without repeating a type clause on every controller.
|
|
231
209
|
|
|
232
210
|
```typescript
|
|
233
211
|
import { createController } from 'remix/router'
|
|
@@ -284,8 +262,7 @@ Because `account` is a nested route map, it is not an action key in the root con
|
|
|
284
262
|
|
|
285
263
|
### Nested route maps
|
|
286
264
|
|
|
287
|
-
Nested route maps use their own controllers under `app/actions/<route-key>/controller.tsx`.
|
|
288
|
-
Directory names under `app/actions/` are route-map keys, not URL path segments.
|
|
265
|
+
Nested route maps use their own controllers under `app/actions/<route-key>/controller.tsx`. Directory names under `app/actions/` are route-map keys, not URL path segments.
|
|
289
266
|
|
|
290
267
|
```typescript
|
|
291
268
|
// app/actions/account/controller.tsx
|
|
@@ -328,8 +305,7 @@ router.map(routes.account.settings, accountSettingsController)
|
|
|
328
305
|
|
|
329
306
|
### Controller middleware
|
|
330
307
|
|
|
331
|
-
The `middleware` array on a controller runs only for the direct actions in that controller, before
|
|
332
|
-
action-level middleware. It does not apply to other controllers.
|
|
308
|
+
The `middleware` array on a controller runs only for the direct actions in that controller, before action-level middleware. It does not apply to other controllers.
|
|
333
309
|
|
|
334
310
|
```typescript
|
|
335
311
|
export default createController(routes.admin, {
|
|
@@ -342,8 +318,7 @@ export default createController(routes.admin, {
|
|
|
342
318
|
|
|
343
319
|
## Registering Routes
|
|
344
320
|
|
|
345
|
-
Use `router.map` for route maps and controllers. Map each nested route map explicitly. Use verb
|
|
346
|
-
methods only for low-level router wiring outside the `app/actions` controller convention.
|
|
321
|
+
Use `router.map` for route maps and controllers. Map each nested route map explicitly. Use verb methods only for low-level router wiring outside the `app/actions` controller convention.
|
|
347
322
|
|
|
348
323
|
```typescript
|
|
349
324
|
let router = createRouter({ middleware })
|
|
@@ -363,8 +338,7 @@ router.post(routes.logout, logoutAction)
|
|
|
363
338
|
|
|
364
339
|
## Typed Context
|
|
365
340
|
|
|
366
|
-
Define an `AppContext` type from your middleware stack, then make it the default context used by
|
|
367
|
-
`createAction()` and `createController()`:
|
|
341
|
+
Define an `AppContext` type from your middleware stack, then make it the default context used by `createAction()` and `createController()`:
|
|
368
342
|
|
|
369
343
|
```typescript
|
|
370
344
|
import type { MiddlewareContext, ContextWithParams, AnyParams } from 'remix/router'
|