@remix-run/cli 0.0.0 → 0.2.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 (159) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +77 -3
  3. package/bootstrap/.agents/skills/remix/SKILL.md +501 -0
  4. package/bootstrap/.agents/skills/remix/references/animate-elements.md +195 -0
  5. package/bootstrap/.agents/skills/remix/references/assets-and-browser-modules.md +122 -0
  6. package/bootstrap/.agents/skills/remix/references/auth-and-sessions.md +420 -0
  7. package/bootstrap/.agents/skills/remix/references/component-model.md +282 -0
  8. package/bootstrap/.agents/skills/remix/references/create-mixins.md +158 -0
  9. package/bootstrap/.agents/skills/remix/references/data-and-validation.md +363 -0
  10. package/bootstrap/.agents/skills/remix/references/hydration-frames-navigation.md +297 -0
  11. package/bootstrap/.agents/skills/remix/references/middleware-and-server.md +243 -0
  12. package/bootstrap/.agents/skills/remix/references/mixins-styling-events.md +213 -0
  13. package/bootstrap/.agents/skills/remix/references/routing-and-controllers.md +324 -0
  14. package/bootstrap/.agents/skills/remix/references/testing-patterns.md +156 -0
  15. package/bootstrap/AGENTS.md +39 -0
  16. package/bootstrap/README.md +27 -0
  17. package/bootstrap/app/assets/entry.ts +19 -0
  18. package/bootstrap/app/assets.ts +18 -0
  19. package/bootstrap/app/controllers/auth.tsx +21 -0
  20. package/bootstrap/app/controllers/home.tsx +11 -0
  21. package/bootstrap/app/router.ts +16 -0
  22. package/bootstrap/app/routes.ts +7 -0
  23. package/bootstrap/app/ui/document.tsx +26 -0
  24. package/bootstrap/app/ui/layout.tsx +22 -0
  25. package/bootstrap/app/ui/prompt-button.tsx +162 -0
  26. package/bootstrap/app/ui/scaffold-home-page.tsx +526 -0
  27. package/bootstrap/app/utils/render.tsx +26 -0
  28. package/bootstrap/package.json +22 -0
  29. package/bootstrap/server.ts +37 -0
  30. package/bootstrap/tsconfig.json +18 -0
  31. package/dist/index.d.ts +2 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +1 -0
  34. package/dist/lib/bootstrap-project.d.ts +17 -0
  35. package/dist/lib/bootstrap-project.d.ts.map +1 -0
  36. package/dist/lib/bootstrap-project.js +137 -0
  37. package/dist/lib/cli-context.d.ts +11 -0
  38. package/dist/lib/cli-context.d.ts.map +1 -0
  39. package/dist/lib/cli-context.js +19 -0
  40. package/dist/lib/cli.d.ts +6 -0
  41. package/dist/lib/cli.d.ts.map +1 -0
  42. package/dist/lib/cli.js +84 -0
  43. package/dist/lib/commands/completion.d.ts +3 -0
  44. package/dist/lib/commands/completion.d.ts.map +1 -0
  45. package/dist/lib/commands/completion.js +51 -0
  46. package/dist/lib/commands/doctor.d.ts +4 -0
  47. package/dist/lib/commands/doctor.d.ts.map +1 -0
  48. package/dist/lib/commands/doctor.js +293 -0
  49. package/dist/lib/commands/help.d.ts +4 -0
  50. package/dist/lib/commands/help.d.ts.map +1 -0
  51. package/dist/lib/commands/help.js +100 -0
  52. package/dist/lib/commands/new.d.ts +4 -0
  53. package/dist/lib/commands/new.d.ts.map +1 -0
  54. package/dist/lib/commands/new.js +70 -0
  55. package/dist/lib/commands/routes.d.ts +4 -0
  56. package/dist/lib/commands/routes.d.ts.map +1 -0
  57. package/dist/lib/commands/routes.js +161 -0
  58. package/dist/lib/commands/test.d.ts +4 -0
  59. package/dist/lib/commands/test.d.ts.map +1 -0
  60. package/dist/lib/commands/test.js +23 -0
  61. package/dist/lib/commands/version.d.ts +4 -0
  62. package/dist/lib/commands/version.d.ts.map +1 -0
  63. package/dist/lib/commands/version.js +29 -0
  64. package/dist/lib/completion.d.ts +12 -0
  65. package/dist/lib/completion.d.ts.map +1 -0
  66. package/dist/lib/completion.js +310 -0
  67. package/dist/lib/contained-path.d.ts +2 -0
  68. package/dist/lib/contained-path.d.ts.map +1 -0
  69. package/dist/lib/contained-path.js +10 -0
  70. package/dist/lib/controller-files.d.ts +14 -0
  71. package/dist/lib/controller-files.d.ts.map +1 -0
  72. package/dist/lib/controller-files.js +53 -0
  73. package/dist/lib/controller-ownership.d.ts +41 -0
  74. package/dist/lib/controller-ownership.d.ts.map +1 -0
  75. package/dist/lib/controller-ownership.js +224 -0
  76. package/dist/lib/display-path.d.ts +2 -0
  77. package/dist/lib/display-path.d.ts.map +1 -0
  78. package/dist/lib/display-path.js +12 -0
  79. package/dist/lib/doctor/controller-findings.d.ts +4 -0
  80. package/dist/lib/doctor/controller-findings.d.ts.map +1 -0
  81. package/dist/lib/doctor/controller-findings.js +132 -0
  82. package/dist/lib/doctor/controller-fix-plans.d.ts +4 -0
  83. package/dist/lib/doctor/controller-fix-plans.d.ts.map +1 -0
  84. package/dist/lib/doctor/controller-fix-plans.js +130 -0
  85. package/dist/lib/doctor/controller-placeholders.d.ts +4 -0
  86. package/dist/lib/doctor/controller-placeholders.d.ts.map +1 -0
  87. package/dist/lib/doctor/controller-placeholders.js +183 -0
  88. package/dist/lib/doctor/controllers.d.ts +8 -0
  89. package/dist/lib/doctor/controllers.d.ts.map +1 -0
  90. package/dist/lib/doctor/controllers.js +13 -0
  91. package/dist/lib/doctor/environment.d.ts +17 -0
  92. package/dist/lib/doctor/environment.d.ts.map +1 -0
  93. package/dist/lib/doctor/environment.js +226 -0
  94. package/dist/lib/doctor/fixes.d.ts +3 -0
  95. package/dist/lib/doctor/fixes.d.ts.map +1 -0
  96. package/dist/lib/doctor/fixes.js +65 -0
  97. package/dist/lib/doctor/project.d.ts +10 -0
  98. package/dist/lib/doctor/project.d.ts.map +1 -0
  99. package/dist/lib/doctor/project.js +371 -0
  100. package/dist/lib/doctor/types.d.ts +48 -0
  101. package/dist/lib/doctor/types.d.ts.map +1 -0
  102. package/dist/lib/doctor/types.js +15 -0
  103. package/dist/lib/errors.d.ts +167 -0
  104. package/dist/lib/errors.d.ts.map +1 -0
  105. package/dist/lib/errors.js +307 -0
  106. package/dist/lib/help-text.d.ts +13 -0
  107. package/dist/lib/help-text.d.ts.map +1 -0
  108. package/dist/lib/help-text.js +43 -0
  109. package/dist/lib/load-route-map-worker.d.ts +2 -0
  110. package/dist/lib/load-route-map-worker.d.ts.map +1 -0
  111. package/dist/lib/load-route-map-worker.js +92 -0
  112. package/dist/lib/parse-args.d.ts +22 -0
  113. package/dist/lib/parse-args.d.ts.map +1 -0
  114. package/dist/lib/parse-args.js +35 -0
  115. package/dist/lib/remix-version.d.ts +2 -0
  116. package/dist/lib/remix-version.d.ts.map +1 -0
  117. package/dist/lib/remix-version.js +58 -0
  118. package/dist/lib/reporter.d.ts +56 -0
  119. package/dist/lib/reporter.d.ts.map +1 -0
  120. package/dist/lib/reporter.js +357 -0
  121. package/dist/lib/route-map.d.ts +38 -0
  122. package/dist/lib/route-map.d.ts.map +1 -0
  123. package/dist/lib/route-map.js +195 -0
  124. package/dist/lib/terminal.d.ts +17 -0
  125. package/dist/lib/terminal.d.ts.map +1 -0
  126. package/dist/lib/terminal.js +66 -0
  127. package/package.json +47 -5
  128. package/src/index.ts +1 -0
  129. package/src/lib/bootstrap-project.ts +222 -0
  130. package/src/lib/cli-context.ts +38 -0
  131. package/src/lib/cli.ts +116 -0
  132. package/src/lib/commands/completion.ts +74 -0
  133. package/src/lib/commands/doctor.ts +393 -0
  134. package/src/lib/commands/help.ts +123 -0
  135. package/src/lib/commands/new.ts +104 -0
  136. package/src/lib/commands/routes.ts +251 -0
  137. package/src/lib/commands/test.ts +31 -0
  138. package/src/lib/commands/version.ts +39 -0
  139. package/src/lib/completion.ts +418 -0
  140. package/src/lib/contained-path.ts +13 -0
  141. package/src/lib/controller-files.ts +79 -0
  142. package/src/lib/controller-ownership.ts +397 -0
  143. package/src/lib/display-path.ts +16 -0
  144. package/src/lib/doctor/controller-findings.ts +157 -0
  145. package/src/lib/doctor/controller-fix-plans.ts +184 -0
  146. package/src/lib/doctor/controller-placeholders.ts +242 -0
  147. package/src/lib/doctor/controllers.ts +23 -0
  148. package/src/lib/doctor/environment.ts +289 -0
  149. package/src/lib/doctor/fixes.ts +84 -0
  150. package/src/lib/doctor/project.ts +435 -0
  151. package/src/lib/doctor/types.ts +95 -0
  152. package/src/lib/errors.ts +367 -0
  153. package/src/lib/help-text.ts +88 -0
  154. package/src/lib/load-route-map-worker.ts +110 -0
  155. package/src/lib/parse-args.ts +82 -0
  156. package/src/lib/remix-version.ts +80 -0
  157. package/src/lib/reporter.ts +535 -0
  158. package/src/lib/route-map.ts +303 -0
  159. package/src/lib/terminal.ts +86 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Shopify Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,4 +1,78 @@
1
- # Placeholder Package
1
+ # cli
2
2
 
3
- This package is a placeholder published at `0.0.0` to reserve the npm name and
4
- configure CI publish permissions.
3
+ Command-line interface for creating and managing Remix projects.
4
+
5
+ ## Features
6
+
7
+ - Create new Remix projects with `npx remix@next new` or installed `remix new`
8
+ - Print shell completion scripts with `remix completion`
9
+ - Check project environment and Remix app conventions with `remix doctor`
10
+ - Create low-risk project and controller files with `remix doctor --fix`
11
+ - Inspect the current app route tree with `remix routes`
12
+ - Run project tests with `remix test`
13
+ - Print the current Remix version with `remix version`
14
+ - Use the same CLI through the `remix` package or the `remix/cli` API
15
+ - Scaffold a starter app that matches the Remix project layout conventions
16
+
17
+ ## Installation
18
+
19
+ Use `npx remix@next new <target-dir>` to scaffold a new Remix app. Install `remix` when you want the local `remix` command:
20
+
21
+ ```sh
22
+ npm i remix
23
+ ```
24
+
25
+ ## Shell completion
26
+
27
+ Install bash completion:
28
+
29
+ ```sh
30
+ remix completion bash >> ~/.bashrc
31
+ ```
32
+
33
+ Install zsh completion:
34
+
35
+ ```sh
36
+ remix completion zsh >> ~/.zshrc
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ Use `npx remix@next new my-remix-app` to scaffold a new Remix app. After installing Remix, the equivalent local command is `remix new my-remix-app`.
42
+
43
+ The rest of the CLI is available through the installed `remix` command:
44
+
45
+ ```sh
46
+ remix new my-remix-app
47
+ remix completion bash >> ~/.bashrc
48
+ remix doctor
49
+ remix doctor --fix
50
+ remix routes
51
+ remix routes --table
52
+ remix routes --table --no-headers
53
+ remix test
54
+ remix version
55
+ remix --no-color doctor
56
+ ```
57
+
58
+ You can also run the CLI programmatically:
59
+
60
+ ```ts
61
+ import { runRemix } from 'remix/cli'
62
+
63
+ await runRemix(['new', 'my-remix-app'])
64
+ await runRemix(['completion', 'bash'])
65
+ await runRemix(['doctor'])
66
+ await runRemix(['doctor', '--fix'])
67
+ await runRemix(['routes'])
68
+ await runRemix(['routes', '--table'])
69
+ await runRemix(['routes', '--table', '--no-headers'])
70
+ await runRemix(['test'])
71
+ await runRemix(['version'])
72
+ ```
73
+
74
+ `runRemix()` returns the CLI exit code as a promise.
75
+
76
+ ## License
77
+
78
+ See [LICENSE](https://github.com/remix-run/remix/blob/main/LICENSE)
@@ -0,0 +1,501 @@
1
+ ---
2
+ name: remix
3
+ description: Build and review Remix 3 applications using the `remix` npm package and subpath imports. Use when working on Remix app structure, routes, controllers, middleware, validation, data access, auth, sessions, file uploads, server setup, UI components, hydration, navigation, or tests.
4
+ ---
5
+
6
+ # Build a Remix App
7
+
8
+ Use this skill for end-to-end Remix app work. It should help the agent choose the right layer
9
+ first, reach for the right package, and avoid the most common Remix-specific mistakes.
10
+
11
+ ## What Remix Is
12
+
13
+ Remix 3 is a server-first web framework built on Web APIs such as `Request`, `Response`, `URL`,
14
+ and `FormData`. All packages ship from a single npm package, `remix`, and are imported via
15
+ subpath. There is no top-level `remix` import.
16
+
17
+ A Remix app has four main pieces:
18
+
19
+ - **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.
21
+ - **Middleware** composes request lifecycle behavior and populates typed context via
22
+ `context.set(Key, value)`.
23
+ - **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.
25
+
26
+ ## When To Use This Skill
27
+
28
+ Use this skill for:
29
+
30
+ - new features or refactors that touch routing, controllers, middleware, data, auth, sessions, UI,
31
+ or tests
32
+ - reviewing Remix app code for correctness, architecture, or framework usage
33
+ - answering "how should this be structured in Remix?" questions
34
+ - finding the right package, reference doc, or default pattern for a task
35
+
36
+ ## Load Only The References You Need
37
+
38
+ Classify the task first, then load the smallest useful reference set. Each reference file starts
39
+ with a "What This Covers" section that lists the topics inside it — read that first to confirm
40
+ the file is relevant before reading the rest.
41
+
42
+ Use the table below to find candidates. Loading more than two or three files at once is usually a
43
+ sign that the task hasn't been narrowed enough yet.
44
+
45
+ | Task involves... | Start with |
46
+ | ----------------------------------------------------------------------------- | ------------------------------------------- |
47
+ | Defining URLs, writing controllers and actions, returning responses | `references/routing-and-controllers.md` |
48
+ | Composing the request lifecycle, ordering middleware, bridging to a server | `references/middleware-and-server.md` |
49
+ | Compiling and serving browser modules, asset URL namespaces, preloads | `references/assets-and-browser-modules.md` |
50
+ | Parsing input, validating with schemas, defining tables, querying, migrations | `references/data-and-validation.md` |
51
+ | Per-browser state, login flows, route protection, identity | `references/auth-and-sessions.md` |
52
+ | Component setup, state, lifecycle, updates, `queueTask`, context | `references/component-model.md` |
53
+ | Event handlers, styles, refs, click/key behavior, simple animations | `references/mixins-styling-events.md` |
54
+ | `clientEntry`, `run`, `<Frame>`, navigation, `<head>` | `references/hydration-frames-navigation.md` |
55
+ | Router tests, component tests, test isolation | `references/testing-patterns.md` |
56
+ | Spring physics, tweens, layout transitions | `references/animate-elements.md` |
57
+ | Authoring custom reusable mixins | `references/create-mixins.md` |
58
+
59
+ Common bundles:
60
+
61
+ - **Form or CRUD feature** -> routing, data and validation, testing; add auth if user-specific
62
+ - **Protected area** -> auth and sessions, routing, testing
63
+ - **Interactive widget** -> component model, mixins and styling; add hydration only if it runs in
64
+ the browser
65
+ - **Browser asset pipeline** -> assets and browser modules, hydration, middleware and server
66
+ - **File upload** -> middleware and server, data and validation, testing
67
+ - **Navigation or frames** -> hydration, frames, navigation
68
+
69
+ ## Default Workflow
70
+
71
+ 1. **Classify the change.** Decide whether it changes the route contract, request lifecycle, data
72
+ model, auth or session behavior, or only UI.
73
+ 2. **Start from the server contract.** Add or update `app/routes.ts` before wiring handlers or UI.
74
+ 3. **Put code in the narrowest owner.** Favor route-local code first, then promote only when reuse
75
+ is real.
76
+ 4. **Make the server path correct before adding browser behavior.** A route should return the right
77
+ `Response` via `router.fetch(...)` before you add `clientEntry(...)`, animations, or DOM
78
+ effects.
79
+ 5. **Add middleware deliberately.** Keep fast-exit middleware early and request-enriching
80
+ middleware later. Export a typed `AppContext` from the root middleware stack and use it in
81
+ controllers.
82
+ 6. **Validate input at the boundary.** Parse and validate `Request`, `FormData`, params, cookies,
83
+ and external payloads before they reach rendering or persistence logic.
84
+ 7. **Hydrate only when necessary.** Prefer server-rendered UI. Use `clientEntry(...)` and `run(...)`
85
+ only for real browser interactivity or browser-only APIs.
86
+ 8. **Test the narrowest meaningful layer.** Prefer router tests for route behavior. Use component
87
+ tests when the behavior is truly interactive or DOM-specific.
88
+ 9. **Finish with verification.** Re-read the route flow, confirm auth and authorization boundaries,
89
+ and run the smallest relevant test and typecheck loop.
90
+
91
+ ## Project Layout
92
+
93
+ Use these root directories consistently:
94
+
95
+ - `app/` for runtime application code
96
+ - `db/` for migrations and local database files
97
+ - `public/` for static assets served as-is
98
+ - `test/` for shared helpers, fixtures, and integration coverage
99
+ - `tmp/` for uploads, caches, local session files, and other scratch data
100
+
101
+ Inside `app/`, organize by responsibility:
102
+
103
+ - `assets/` for client entrypoints and client-owned browser behavior
104
+ - `controllers/` for route-owned handlers and route-local UI
105
+ - `data/` for schema, queries, persistence setup, migrations, and runtime data initialization
106
+ - `middleware/` for request lifecycle concerns such as auth, sessions, uploads, and database
107
+ injection
108
+ - `ui/` for shared cross-route UI primitives
109
+ - `utils/` only for genuinely cross-layer helpers that do not clearly belong elsewhere
110
+ - `routes.ts` for the route contract
111
+ - `router.ts` for router setup and wiring
112
+
113
+ ### Placement Precedence
114
+
115
+ When code could live in multiple places:
116
+
117
+ 1. Put it in the narrowest owner first.
118
+ 2. If it belongs to one route, keep it with that route.
119
+ 3. If it is shared UI across route areas, move it to `app/ui/`.
120
+ 4. If it is request lifecycle setup, keep it in `app/middleware/`.
121
+ 5. If it is schema, query, persistence, or startup data logic, keep it in `app/data/`.
122
+ 6. Use `app/utils/` only as a last resort for truly cross-layer helpers.
123
+
124
+ ### Route Ownership
125
+
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
133
+
134
+ ### Layout Anti-Patterns
135
+
136
+ - Do not create `app/lib/` as a generic dumping ground
137
+ - Do not create `app/components/` as a second shared UI bucket when `app/ui/` already owns that
138
+ role
139
+ - Do not put shared cross-route UI in `app/controllers/`
140
+ - 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
+
143
+ ## Core Remix Rules
144
+
145
+ - Import from `remix/<subpath>`, never `import { ... } from 'remix'`
146
+ - Treat `app/routes.ts` as the source of truth for URLs. Use `routes.<name>.href(...)` for
147
+ redirects, links, tests, and internal URL construction
148
+ - Controllers and actions should return explicit `Response` objects, including redirects, 404s, and
149
+ validation failures. At the route boundary, prefer returning a `Response` for expected outcomes
150
+ (validation errors, conflicts, not found) over throwing for control flow
151
+ - Model HTTP behavior explicitly. Status codes, headers, redirects, cache rules, and content types
152
+ are part of the route contract
153
+ - Make the server route correct first. A POST should already return the right HTML, redirect, or
154
+ error response on its own before `clientEntry(...)` layers interactivity on top
155
+ - Validate input at the boundary using `remix/data-schema` (and `remix/data-schema/form-data` for
156
+ forms). `parseSafe` makes the failure path a return value instead of an exception
157
+ - Derive `AppContext` from the root middleware stack so `get(Database)`, `get(Session)`,
158
+ `get(Auth)`, and similar keys stay typed. If the controller never reads from context, it doesn't
159
+ need the harness
160
+ - Outside actions and controllers, only use `getContext()` when `asyncContext()` is in the
161
+ middleware stack
162
+ - Remix Component is not React: read props from `handle.props`, keep state in setup-scope
163
+ variables, call `handle.update()` explicitly, and do DOM-sensitive work in event handlers or
164
+ `queueTask(...)`, not in render
165
+ - Prefer host-element mixins via `mix={mixin(...)}` for behavior and styling instead of inventing
166
+ custom host prop conventions. Use `mix={[...]}` only when composing multiple mixins
167
+ - Hydrated `clientEntry(...)` props must be serializable. Do not pass functions, class instances, or
168
+ opaque runtime objects
169
+
170
+ ## Security And Session Defaults
171
+
172
+ - Never ship demo secrets. In non-test environments, require session and provider secrets from the
173
+ environment and fail fast if they are missing
174
+ - Use hardened cookies: `httpOnly` always, `sameSite` by default, and `secure` when serving over
175
+ HTTPS
176
+ - Regenerate session IDs on login, logout, and privilege changes
177
+ - Use `requireAuth()` to protect authenticated route areas, but still authorize resource ownership
178
+ inside handlers and data writes
179
+ - Add CSRF protection when browser forms mutate state using cookie-backed sessions
180
+ - Add CORS only for endpoints that must be called cross-origin. Prefer same-origin by default
181
+ - Prefer JSX or `remix/html-template` for HTML generation so escaping stays correct
182
+ - Validate uploads for size, type, and destination. Treat filenames and content as untrusted input
183
+
184
+ ## Testing Defaults
185
+
186
+ - Prefer server and router tests first. Drive the app with `router.fetch(new Request(...))` and
187
+ assert on the returned `Response`
188
+ - Build a fresh router per test or per suite so sessions, in-memory storage, and database state
189
+ stay isolated
190
+ - Use `routes.<name>.href(...)` in tests so URLs stay coupled to the route contract
191
+ - For auth or session scenarios, use a test cookie and `createMemorySessionStorage()` instead of
192
+ production storage
193
+ - Use component tests only for interactive or DOM-specific behavior. Render with `createRoot(...)`,
194
+ interact with the real DOM, and call `root.flush()` between steps
195
+ - Prefer one representative behavior test over many repetitive assertion variants
196
+
197
+ ## Common Mistakes To Avoid
198
+
199
+ - Treating Remix Component like React and reaching for hooks or implicit rerendering
200
+ - Importing from a top-level `remix` entry instead of a subpath
201
+ - Adding `clientEntry(...)` before the server-rendered route behavior is correct
202
+ - Passing non-serializable props into `clientEntry(...)`
203
+ - Calling `getContext()` without `asyncContext()` in the middleware stack
204
+ - Getting middleware order wrong; fast exits like static files belong early, request enrichment later
205
+ - Skipping boundary validation and trusting raw `FormData`, params, cookies, or external payloads
206
+ - Letting route-local domain errors leak out of the controller. Translate expected outcomes
207
+ (validation, conflicts, not-found) into the HTTP `Response` the route means to return rather than
208
+ throwing a custom `Error` subclass and catching it elsewhere
209
+ - Reaching for `createCookie` when a tamper-sensitive or server-managed per-browser fact really
210
+ wants `remix/session`. If editing the value would be a bug, use a session
211
+ - Building a JSON-only RPC layer when a normal form POST, redirect, or resource route would be
212
+ simpler. Fetch-from-the-client is a layer on top of sound route behavior, not a replacement for
213
+ it
214
+ - Treating JSON state endpoints and `<Frame>` reloads as mutually exclusive patterns. Pick the
215
+ lightest sync mechanism that fits the UX; small widgets may reasonably poll a JSON endpoint
216
+ - Assuming authentication is enough without per-resource authorization checks
217
+ - Dropping shared code into vague buckets like `utils.ts`, `helpers.ts`, or `common.ts` when
218
+ ownership is known
219
+ - Writing only component tests for a feature whose main behavior is really an HTTP route concern
220
+
221
+ ## Package Map
222
+
223
+ Use this map to find the right package quickly. Each entry says what the package is for, not just
224
+ what it exports. Open the linked reference file when you need full examples.
225
+
226
+ ### Routing, Server, and Responses
227
+
228
+ - `remix/fetch-router` — the router itself. Use for `createRouter`, controller and middleware
229
+ 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`
234
+ - `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
239
+ - `remix/response/redirect` — `redirect(href, status?)`. Use for the canonical "POST then redirect"
240
+ pattern and other location changes
241
+ - `remix/response/html` — `createHtmlResponse`. Use when you need an HTML `Response` from a string
242
+ or stream without rendering through `remix/ui`
243
+ - `remix/response/compress` — `compressResponse`. Use when compressing one-off responses outside
244
+ the global `compression()` middleware
245
+ - `remix/response/file` — file-download responses. Use for `Content-Disposition: attachment`
246
+ responses
247
+ - `remix/route-pattern` — low-level URL matching and generation. Use when working with raw
248
+ patterns outside the router (custom matchers, scripts)
249
+ - `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
251
+
252
+ ### Data, Validation, and Persistence
253
+
254
+ - `remix/data-schema` — schema builders for runtime validation. Use for `parse` and `parseSafe`
255
+ to validate any input that crosses a trust boundary, and `.transform(...)` when validated output
256
+ should map to a different value or type
257
+ - `remix/data-schema/checks` — common check helpers (`email`, `minLength`, `maxLength`, etc.).
258
+ Use to compose into a schema
259
+ - `remix/data-schema/coerce` — coercion helpers for strings, numbers, booleans, dates, and ids.
260
+ Use when input arrives as a string but should be a typed value
261
+ - `remix/data-schema/form-data` — `f.object` and `f.field` for parsing `FormData` directly. Use
262
+ in actions that read browser forms
263
+ - `remix/data-table` — typed tables and a `Database` interface. Use for `table`, `column`,
264
+ `createDatabase` when modeling persisted data
265
+ - `remix/data-table-sqlite`, `remix/data-table-postgres`, `remix/data-table-mysql` — adapters.
266
+ Use to back `createDatabase` with a real engine. SQLite accepts Node, Bun, and compatible
267
+ synchronous clients with the shared `prepare`/`exec` surface
268
+ - `remix/data-table/migrations` — migration authoring and runners. Use for `createMigration`,
269
+ `createMigrationRunner`
270
+ - `remix/data-table/migrations/node` — `loadMigrations` from disk. Use in startup scripts that
271
+ apply migrations
272
+ - `remix/data-table/operators` — query operators such as `inList(...)`. Use when `where` clauses
273
+ need set or comparison logic
274
+
275
+ ### Auth, Sessions, and Cookies
276
+
277
+ - `remix/session` — the `Session` object: `get`, `set`, `flash`, `unset`, `regenerateId`. Use for
278
+ any per-browser state where tampering would be a bug (login, "I submitted this form already",
279
+ cart, flash messages)
280
+ - `remix/session-middleware` — `session(cookie, storage)`. Use to wire a session cookie and
281
+ storage backend into the root middleware stack
282
+ - `remix/session/fs-storage`, `remix/session/memory-storage`, `remix/session/cookie-storage` —
283
+ storage backends. Use `fs-storage` for single-process apps, `memory-storage` for tests,
284
+ `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
286
+ deployments
287
+ - `remix/session-storage-memcache` — Memcache-backed storage. Same multi-host use case as Redis
288
+ - `remix/cookie` — `createCookie` for plain signed/unsigned cookies. Use for non-sensitive
289
+ preferences where the client is allowed to control the value (theme, locale, dismissed banner).
290
+ For state where tampering matters, prefer `remix/session`
291
+ - `remix/auth` — credentials, OAuth, OIDC, and Atmosphere providers. Use to define how identity is
292
+ verified, start/finish external login, and refresh stored OAuth/OIDC token bundles with
293
+ `refreshExternalAuth(...)`
294
+ - `remix/auth-middleware` — `auth({ schemes })`, `requireAuth`, the `Auth` context key. Use to
295
+ resolve identity into the request context and to gate routes
296
+
297
+ ### UI, Hydration, and Browser Behavior
298
+
299
+ - `remix/ui` — the component runtime: components, core mixins, `clientEntry`, `run`, `<Frame>`,
300
+ navigation helpers, and `createRoot`. Use for app UI behavior
301
+ - `remix/ui/server` — server rendering: `renderToStream`, `renderToString`. Use in the
302
+ `render(...)` helper that returns HTML responses
303
+ - `remix/ui/animation` — animation APIs: `animateEntrance`, `animateExit`, `animateLayout`,
304
+ `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.
307
+ - `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
310
+ - `remix/html-template` — escaped HTML template literals. Use when generating HTML outside the
311
+ component system (RSS feeds, email bodies, error pages)
312
+ - `remix/file-storage` — backend-agnostic `File` storage interface. Use as the type bound for
313
+ upload destinations
314
+ - `remix/file-storage/fs`, `remix/file-storage/memory`, `remix/file-storage-s3` — storage
315
+ backends. Use to implement an upload destination
316
+
317
+ ### Middleware
318
+
319
+ - `remix/static-middleware` — `staticFiles(dir)`. Use to serve files from `public/` exactly as
320
+ they exist on disk
321
+ - `remix/form-data-middleware` — `formData()`. Use to parse `FormData` once and expose it via
322
+ `get(FormData)` instead of calling `await request.formData()` in each action
323
+ - `remix/form-data-parser` — lower-level `parseFormData`, `FileUpload`. Use when implementing
324
+ custom upload handlers. Upload handler errors propagate directly
325
+ - `remix/multipart-parser` and `remix/multipart-parser/node` — low-level multipart stream parsing.
326
+ `MultipartPart.headers` is a plain object keyed by lower-case header name; read values with
327
+ 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
330
+ force terminal color output on or off
331
+ - `remix/method-override-middleware` — `methodOverride()`. Use when HTML forms need `PUT`,
332
+ `PATCH`, or `DELETE`
333
+ - `remix/async-context-middleware` — `asyncContext()`, `getContext()`. Use when helpers outside
334
+ 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
337
+ synchronizer-token CSRF protection
338
+ - `remix/cop-middleware` — cross-origin protection. Use to reject unsafe cross-origin browser
339
+ requests
340
+
341
+ ### Test
342
+
343
+ - `remix/test` — `describe`, `it`, and lifecycle hooks. Use as the test framework
344
+ - `remix/test/cli` — programmatic test runner APIs such as `runRemixTest`
345
+ - `remix/cli` — programmatic Remix CLI API. Use the `remix` executable for project commands such
346
+ as `remix test`, `remix routes`, and `remix doctor`
347
+ - `remix/assert` — assertion helpers. Use in place of `node:assert` so messages render cleanly
348
+ in the runner
349
+ - `remix/terminal` — ANSI styles, color detection, style factories, and testable terminal streams.
350
+ Use for CLIs and terminal output instead of hand-rolled escape sequences
351
+
352
+ ## Canonical Patterns
353
+
354
+ ### Define routes first
355
+
356
+ ```typescript
357
+ import { form, get, post, resources, route } from 'remix/fetch-router/routes'
358
+
359
+ export const routes = route({
360
+ home: '/',
361
+ contact: form('contact'),
362
+ books: {
363
+ index: '/books',
364
+ show: '/books/:slug',
365
+ },
366
+ auth: route('auth', {
367
+ login: form('login'),
368
+ logout: post('logout'),
369
+ }),
370
+ admin: route('admin', {
371
+ index: get('/'),
372
+ books: resources('books', { param: 'bookId' }),
373
+ }),
374
+ })
375
+ ```
376
+
377
+ ### Type controllers against the route contract
378
+
379
+ ```typescript
380
+ import type { Controller } from 'remix/fetch-router'
381
+
382
+ import type { AppContext } from '../router.ts'
383
+ import { routes } from '../routes.ts'
384
+
385
+ export default {
386
+ actions: {
387
+ async index({ get }) {
388
+ let db = get(Database)
389
+ let allBooks = await db.findMany(books, { orderBy: ['id', 'asc'] })
390
+ return render(<BooksIndexPage allBooks={allBooks} />)
391
+ },
392
+ async show({ get, params }) {
393
+ let db = get(Database)
394
+ let book = await db.findOne(books, { where: { slug: params.slug } })
395
+ if (!book) return new Response('Not Found', { status: 404 })
396
+ return render(<BookShowPage book={book} />)
397
+ },
398
+ },
399
+ } satisfies Controller<typeof routes.books, AppContext>
400
+ ```
401
+
402
+ ### Compose middleware deliberately
403
+
404
+ ```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
+ >
423
+
424
+ let middleware = []
425
+
426
+ if (process.env.NODE_ENV === 'development') {
427
+ middleware.push(logger())
428
+ }
429
+
430
+ middleware.push(compression())
431
+ middleware.push(staticFiles('./public'))
432
+ middleware.push(formData())
433
+ middleware.push(methodOverride())
434
+ middleware.push(session(cookie, storage))
435
+ middleware.push(asyncContext())
436
+ middleware.push(loadDatabase())
437
+ middleware.push(loadAuth())
438
+
439
+ let router = createRouter({ middleware })
440
+ ```
441
+
442
+ ### Mutate, validate, and respond
443
+
444
+ ```typescript
445
+ import { redirect } from 'remix/response/redirect'
446
+ import * as s from 'remix/data-schema'
447
+ import * as f from 'remix/data-schema/form-data'
448
+ import { Session } from 'remix/session'
449
+ import { Database } from 'remix/data-table'
450
+
451
+ let bookSchema = f.object({
452
+ slug: f.field(s.string()),
453
+ title: f.field(s.string()),
454
+ })
455
+
456
+ export default {
457
+ actions: {
458
+ async create({ get }) {
459
+ let parsed = s.parseSafe(bookSchema, get(FormData))
460
+ if (!parsed.success) {
461
+ return render(<NewBookPage errors={parsed.issues} />, { status: 400 })
462
+ }
463
+
464
+ let db = get(Database)
465
+ let book = await db.create(books, parsed.value)
466
+
467
+ let session = get(Session)
468
+ session.flash('message', `Added ${book.title}.`)
469
+
470
+ return redirect(routes.books.show.href({ slug: book.slug }))
471
+ },
472
+ },
473
+ } satisfies Controller<typeof routes.books, AppContext>
474
+ ```
475
+
476
+ This shape works without JavaScript, returns a `Response` for every outcome, and is ready for
477
+ `clientEntry(...)` interactivity when the UI needs it.
478
+
479
+ ### Build UI from handle props plus render
480
+
481
+ ```tsx
482
+ import { on, type Handle } from 'remix/ui'
483
+
484
+ function Counter(handle: Handle<{ initialCount?: number; label: string }>) {
485
+ let count = handle.props.initialCount ?? 0
486
+
487
+ return () => (
488
+ <button
489
+ mix={on('click', () => {
490
+ count++
491
+ handle.update()
492
+ })}
493
+ >
494
+ {handle.props.label}: {count}
495
+ </button>
496
+ )
497
+ }
498
+ ```
499
+
500
+ Only add `clientEntry(...)` and `run(...)` when the component needs browser interactivity or
501
+ browser-only APIs.