remix
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseBuild a Remix App
构建Remix应用
Use this skill for end-to-end Remix app work. It should help the agent choose the right layer
first, reach for the right package, and avoid the most common Remix-specific mistakes.
本技能适用于Remix应用的端到端开发工作,可帮助Agent优先选择合适的层级、正确的包,并避免最常见的Remix专属错误。
What Remix Is
Remix是什么
Remix 3 is a server-first web framework built on Web APIs such as , , ,
and . All packages ship from a single npm package, , and are imported via
subpath. There is no top-level import.
RequestResponseURLFormDataremixremixA Remix app has four main pieces:
- Routes in define the typed URL contract and power
app/routes.tsgeneration.href() - Controllers and actions implement that contract and return objects.
Response - Middleware composes request lifecycle behavior and populates typed context via
.
context.set(Key, value) - Components render UI with . This is not React. A component receives a
remix/ui, reads current props fromhandle, and returns a render function.handle.props
Remix 3是一款基于Web API(如、、和)构建的服务端优先Web框架。所有包都通过单个npm包发布,并通过子路径导入,不存在顶层导入。
RequestResponseURLFormDataremixremixRemix应用包含四个核心部分:
- 路由:位于中,定义类型化URL契约,为
app/routes.ts生成提供支持。href() - 控制器与动作:实现URL契约并返回对象。
Response - 中间件:组合请求生命周期行为,并通过填充类型化上下文。
context.set(Key, value) - 组件:使用渲染UI,这不是React。组件接收
remix/ui,从handle读取当前属性,并返回渲染函数。handle.props
When To Use This Skill
何时使用本技能
Use this skill for:
- new features or refactors that touch routing, controllers, middleware, data, auth, sessions, UI, or tests
- reviewing Remix app code for correctness, architecture, or framework usage
- answering "how should this be structured in Remix?" questions
- finding the right package, reference doc, or default pattern for a task
适用于以下场景:
- 涉及路由、控制器、中间件、数据、认证、会话、UI或测试的新功能开发或重构
- 评审Remix应用代码的正确性、架构或框架使用方式
- 解答“在Remix中应该如何构建这个功能?”这类问题
- 为任务找到合适的包、参考文档或默认模式
Load Only The References You Need
仅加载所需的参考资料
Classify the task first, then load the smallest useful reference set. Each reference file starts
with a "What This Covers" section that lists the topics inside it — read that first to confirm
the file is relevant before reading the rest.
Use the table below to find candidates. Loading more than two or three files at once is usually a
sign that the task hasn't been narrowed enough yet.
| Task involves... | Start with |
|---|---|
| Defining URLs, writing controllers and actions, returning responses | |
| Composing the request lifecycle, ordering middleware, bridging to a server | |
| Compiling and serving browser modules, asset URL namespaces, preloads | |
| Parsing input, validating with schemas, defining tables, querying, migrations | |
| Per-browser state, login flows, route protection, identity | |
Component setup, state, lifecycle, updates, | |
| Event handlers, styles, refs, click/key behavior, simple animations | |
| |
| Router tests, component tests, test isolation | |
| Spring physics, tweens, layout transitions | |
| Authoring custom reusable mixins | |
Common bundles:
- Form or CRUD feature -> routing, data and validation, testing; add auth if user-specific
- Protected area -> auth and sessions, routing, testing
- Interactive widget -> component model, mixins and styling; add hydration only if it runs in the browser
- Browser asset pipeline -> assets and browser modules, hydration, middleware and server
- File upload -> middleware and server, data and validation, testing
- Navigation or frames -> hydration, frames, navigation
先对任务进行分类,再加载最小的有用参考集。每个参考文件开头都有“涵盖内容”部分,列出了文件内的主题——先阅读该部分确认文件相关后,再阅读其余内容。
使用下表查找候选资料。同时加载超过两到三个文件通常意味着任务尚未足够细化。
| 任务涉及... | 优先参考 |
|---|---|
| 定义URL、编写控制器和动作、返回响应 | |
| 组合请求生命周期、调整中间件顺序、与服务器对接 | |
| 编译和提供浏览器模块、资源URL命名空间、预加载 | |
| 解析输入、使用Schema验证、定义表、查询、迁移 | |
| 单浏览器状态、登录流程、路由保护、身份认证 | |
组件配置、状态、生命周期、更新、 | |
| 事件处理器、样式、引用、点击/按键行为、简单动画 | |
| |
| 路由测试、组件测试、测试隔离 | |
| 弹簧物理、补间动画、布局过渡 | |
| 编写自定义可复用mixin | |
常见组合:
- 表单或CRUD功能 -> 路由、数据与验证、测试;如果是用户专属功能,添加认证相关资料
- 受保护区域 -> 认证与会话、路由、测试
- 交互式组件 -> 组件模型、Mixin与样式;仅当需要在浏览器中运行时添加水合相关资料
- 浏览器资源管道 -> 资源与浏览器模块、水合、中间件与服务器
- 文件上传 -> 中间件与服务器、数据与验证、测试
- 导航或框架 -> 水合、框架、导航
Default Workflow
默认工作流程
- Classify the change. Decide whether it changes the route contract, request lifecycle, data model, auth or session behavior, or only UI.
- Start from the server contract. Add or update before wiring handlers or UI.
app/routes.ts - Put code in the narrowest owner. Favor route-local code first, then promote only when reuse is real.
- Make the server path correct before adding browser behavior. A route should return the right
via
Responsebefore you addrouter.fetch(...), animations, or DOM effects.clientEntry(...) - Add middleware deliberately. Keep fast-exit middleware early and request-enriching
middleware later. Export a typed from the root middleware stack and use it in controllers.
AppContext - Validate input at the boundary. Parse and validate ,
Request, params, cookies, and external payloads before they reach rendering or persistence logic.FormData - Hydrate only when necessary. Prefer server-rendered UI. Use and
clientEntry(...)only for real browser interactivity or browser-only APIs.run(...) - Test the narrowest meaningful layer. Prefer router tests for route behavior. Use component tests when the behavior is truly interactive or DOM-specific.
- Finish with verification. Re-read the route flow, confirm auth and authorization boundaries, and run the smallest relevant test and typecheck loop.
- 对变更进行分类:确定变更是否会修改路由契约、请求生命周期、数据模型、认证或会话行为,还是仅涉及UI。
- 从服务器契约开始:在连接处理器或UI之前,先添加或更新。
app/routes.ts - 将代码放在最窄的归属方:优先使用路由本地代码,仅在确实需要复用再进行提升。
- 在添加浏览器行为前确保服务器路径正确:在添加、动画或DOM效果之前,路由应能通过
clientEntry(...)返回正确的router.fetch(...)。Response - 谨慎添加中间件:将快速退出的中间件放在前面,将丰富请求的中间件放在后面。从根中间件栈导出类型化的并在控制器中使用。
AppContext - 在边界处验证输入:在输入到达渲染或持久化逻辑之前,解析并验证、
Request、参数、Cookie和外部负载。FormData - 仅在必要时进行水合:优先使用服务端渲染UI。仅在需要真正的浏览器交互或仅浏览器API时,才使用和
clientEntry(...)。run(...) - 测试最窄的有意义层级:优先使用路由测试验证路由行为。当行为确实是交互式或DOM专属时,再使用组件测试。
- 完成后进行验证:重新阅读路由流程,确认认证与授权边界,并运行最小的相关测试和类型检查循环。
Project Layout
项目布局
Use these root directories consistently:
- for runtime application code
app/ - for migrations and local database files
db/ - for static assets served as-is
public/ - for shared helpers, fixtures, and integration coverage
test/ - for uploads, caches, local session files, and other scratch data
tmp/
Inside , organize by responsibility:
app/- for client entrypoints and client-owned browser behavior
assets/ - for route-owned handlers and route-local UI
controllers/ - for schema, queries, persistence setup, migrations, and runtime data initialization
data/ - for request lifecycle concerns such as auth, sessions, uploads, and database injection
middleware/ - for shared cross-route UI primitives
ui/ - only for genuinely cross-layer helpers that do not clearly belong elsewhere
utils/ - for the route contract
routes.ts - for router setup and wiring
router.ts
请统一使用以下根目录:
- :存放运行时应用代码
app/ - :存放迁移文件和本地数据库文件
db/ - :存放原样提供的静态资源
public/ - :存放共享助手、测试数据和集成测试覆盖代码
test/ - :存放上传文件、缓存、本地会话文件和其他临时数据
tmp/
在内部,按职责组织:
app/- :存放客户端入口点和客户端专属浏览器行为代码
assets/ - :存放路由专属处理器和路由本地UI
controllers/ - :存放Schema、查询、持久化配置、迁移和运行时数据初始化代码
data/ - :存放请求生命周期相关代码,如认证、会话、上传和数据库注入
middleware/ - :存放跨路由共享的UI原语
ui/ - :仅存放真正跨层级且无明确归属的助手代码
utils/ - :路由契约文件
routes.ts - :路由配置和连接文件
router.ts
Placement Precedence
放置优先级
When code could live in multiple places:
- Put it in the narrowest owner first.
- If it belongs to one route, keep it with that route.
- If it is shared UI across route areas, move it to .
app/ui/ - If it is request lifecycle setup, keep it in .
app/middleware/ - If it is schema, query, persistence, or startup data logic, keep it in .
app/data/ - Use only as a last resort for truly cross-layer helpers.
app/utils/
当代码可放在多个位置时:
- 优先放在最窄的归属方。
- 如果属于单个路由,将其与该路由放在一起。
- 如果是跨路由区域的共享UI,移至。
app/ui/ - 如果是请求生命周期配置,放在。
app/middleware/ - 如果是Schema、查询、持久化或启动数据逻辑,放在。
app/data/ - 仅当确实没有其他合适位置时,才使用存放跨层级助手代码。
app/utils/
Route Ownership
路由归属
- Use a flat file in for a simple leaf action, such as
app/controllers/app/controllers/home.tsx - Use a folder with when a route owns nested routes or multiple actions, such as
controller.tsxapp/controllers/account/controller.tsx - Mirror nested route structure on disk, such as
app/controllers/auth/login/controller.tsx - Keep route-local UI next to its owner, such as
app/controllers/contact/page.tsx - Move shared UI to
app/ui/ - If a flat leaf grows child routes or multiple actions, promote it to a controller folder
- 对于简单的叶子动作,在中使用扁平文件,如
app/controllers/app/controllers/home.tsx - 当路由拥有嵌套路由或多个动作时,使用包含的文件夹,如
controller.tsxapp/controllers/account/controller.tsx - 在磁盘上镜像嵌套路由结构,如
app/controllers/auth/login/controller.tsx - 将路由本地UI放在其归属方旁边,如
app/controllers/contact/page.tsx - 将共享UI移至
app/ui/ - 如果扁平叶子路由扩展出子路由或多个动作,将其升级为控制器文件夹
Layout Anti-Patterns
布局反模式
- Do not create as a generic dumping ground
app/lib/ - Do not create as a second shared UI bucket when
app/components/already owns that roleapp/ui/ - Do not put shared cross-route UI in
app/controllers/ - Do not put middleware or persistence helpers in when they have a clearer home
app/utils/ - Do not create folders for simple leaf actions unless they are real controllers
- 不要创建作为通用垃圾场
app/lib/ - 当已承担共享UI职责时,不要创建
app/ui/作为第二个共享UI存储区app/components/ - 不要将跨路由共享UI放在中
app/controllers/ - 当中间件或持久化助手有明确归属时,不要放在中
app/utils/ - 不要为简单的叶子动作创建文件夹,除非它们是真正的控制器
Core Remix Rules
Remix核心规则
- Import from , never
remix/<subpath>import { ... } from 'remix' - Treat as the source of truth for URLs. Use
app/routes.tsfor redirects, links, tests, and internal URL constructionroutes.<name>.href(...) - Controllers and actions should return explicit objects, including redirects, 404s, and validation failures. At the route boundary, prefer returning a
Responsefor expected outcomes (validation errors, conflicts, not found) over throwing for control flowResponse - Model HTTP behavior explicitly. Status codes, headers, redirects, cache rules, and content types are part of the route contract
- Make the server route correct first. A POST should already return the right HTML, redirect, or
error response on its own before layers interactivity on top
clientEntry(...) - Validate input at the boundary using (and
remix/data-schemafor forms).remix/data-schema/form-datamakes the failure path a return value instead of an exceptionparseSafe - Derive from the root middleware stack so
AppContext,get(Database),get(Session), and similar keys stay typed. If the controller never reads from context, it doesn't need the harnessget(Auth) - Outside actions and controllers, only use when
getContext()is in the middleware stackasyncContext() - Remix Component is not React: read props from , keep state in setup-scope variables, call
handle.propsexplicitly, and do DOM-sensitive work in event handlers orhandle.update(), not in renderqueueTask(...) - Prefer host-element mixins via for behavior and styling instead of inventing custom host prop conventions. Use
mix={mixin(...)}only when composing multiple mixinsmix={[...]} - Hydrated props must be serializable. Do not pass functions, class instances, or opaque runtime objects
clientEntry(...)
- 从导入,绝不要使用
remix/<subpath>import { ... } from 'remix' - 将视为URL的唯一来源。在重定向、链接、测试和内部URL构建中使用
app/routes.tsroutes.<name>.href(...) - 控制器和动作应返回明确的对象,包括重定向、404和验证失败。在路由边界,对于预期结果(验证错误、冲突、未找到),优先返回
Response而非通过抛出异常进行控制流处理Response - 显式建模HTTP行为。状态码、头信息、重定向、缓存规则和内容类型是路由契约的一部分
- 先确保服务端路由正确。在添加交互性之前,POST请求本身应已能返回正确的HTML、重定向或错误响应
clientEntry(...) - 使用(表单使用
remix/data-schema)在边界处验证输入。remix/data-schema/form-data将失败路径作为返回值而非异常parseSafe - 从根中间件栈派生,使
AppContext、get(Database)、get(Session)等键保持类型化。如果控制器从不读取上下文,则无需使用该工具get(Auth) - 在动作和控制器之外,仅当中间件栈中存在时才使用
asyncContext()getContext() - Remix Component不是React:从读取属性,将状态放在作用域变量中,显式调用
handle.props,并在事件处理器或handle.update()中处理DOM相关工作,而非在渲染中处理queueTask(...) - 优先通过使用宿主元素Mixin实现行为和样式,而非自定义宿主属性约定。仅在组合多个Mixin时使用
mix={mixin(...)}mix={[...]} - 水合的属性必须可序列化。不要传递函数、类实例或不透明的运行时对象
clientEntry(...)
Security And Session Defaults
安全与会话默认值
- Never ship demo secrets. In non-test environments, require session and provider secrets from the environment and fail fast if they are missing
- Use hardened cookies: always,
httpOnlyby default, andsameSitewhen serving over HTTPSsecure - Regenerate session IDs on login, logout, and privilege changes
- Use to protect authenticated route areas, but still authorize resource ownership inside handlers and data writes
requireAuth() - Add CSRF protection when browser forms mutate state using cookie-backed sessions
- Add CORS only for endpoints that must be called cross-origin. Prefer same-origin by default
- Prefer JSX or for HTML generation so escaping stays correct
remix/html-template - Validate uploads for size, type, and destination. Treat filenames and content as untrusted input
- 绝不要发布演示密钥。在非测试环境中,要求从环境变量获取会话和提供者密钥,如果缺失则快速失败
- 使用加固的Cookie:始终启用,默认启用
httpOnly,在HTTPS服务时启用sameSitesecure - 在登录、登出和权限变更时重新生成会话ID
- 使用保护认证路由区域,但仍需在处理器和数据写入中验证资源所有权
requireAuth() - 当浏览器表单使用基于Cookie的会话修改状态时,添加CSRF保护
- 仅为必须跨域调用的端点添加CORS。默认优先使用同源
- 优先使用JSX或生成HTML,以确保转义正确
remix/html-template - 验证上传文件的大小、类型和目标位置。将文件名和内容视为不可信输入
Testing Defaults
测试默认值
- Prefer server and router tests first. Drive the app with and assert on the returned
router.fetch(new Request(...))Response - Build a fresh router per test or per suite so sessions, in-memory storage, and database state stay isolated
- Use in tests so URLs stay coupled to the route contract
routes.<name>.href(...) - For auth or session scenarios, use a test cookie and instead of production storage
createMemorySessionStorage() - Use component tests only for interactive or DOM-specific behavior. Render with , interact with the real DOM, and call
createRoot(...)between stepsroot.flush() - Prefer one representative behavior test over many repetitive assertion variants
- 优先使用服务端和路由测试。通过驱动应用,并断言返回的
router.fetch(new Request(...))Response - 每个测试或测试套件构建一个全新的路由,确保会话、内存存储和数据库状态隔离
- 在测试中使用,使URL与路由契约保持关联
routes.<name>.href(...) - 对于认证或会话场景,使用测试Cookie和而非生产存储
createMemorySessionStorage() - 仅对交互式或DOM专属行为使用组件测试。使用渲染,与真实DOM交互,并在步骤之间调用
createRoot(...)root.flush() - 优先编写一个代表性的行为测试,而非多个重复的断言变体
Common Mistakes To Avoid
需避免的常见错误
- Treating Remix Component like React and reaching for hooks or implicit rerendering
- Importing from a top-level entry instead of a subpath
remix - Adding before the server-rendered route behavior is correct
clientEntry(...) - Passing non-serializable props into
clientEntry(...) - Calling without
getContext()in the middleware stackasyncContext() - Getting middleware order wrong; fast exits like static files belong early, request enrichment later
- Skipping boundary validation and trusting raw , params, cookies, or external payloads
FormData - Letting route-local domain errors leak out of the controller. Translate expected outcomes
(validation, conflicts, not-found) into the HTTP the route means to return rather than throwing a custom
Responsesubclass and catching it elsewhereError - Reaching for when a tamper-sensitive or server-managed per-browser fact really wants
createCookie. If editing the value would be a bug, use a sessionremix/session - Building a JSON-only RPC layer when a normal form POST, redirect, or resource route would be simpler. Fetch-from-the-client is a layer on top of sound route behavior, not a replacement for it
- Treating JSON state endpoints and reloads as mutually exclusive patterns. Pick the lightest sync mechanism that fits the UX; small widgets may reasonably poll a JSON endpoint
<Frame> - Assuming authentication is enough without per-resource authorization checks
- Dropping shared code into vague buckets like ,
utils.ts, orhelpers.tswhen ownership is knowncommon.ts - Writing only component tests for a feature whose main behavior is really an HTTP route concern
- 将Remix Component当作React使用,尝试使用钩子或隐式重新渲染
- 从顶层入口导入,而非子路径
remix - 在服务端渲染路由行为正确之前添加
clientEntry(...) - 将不可序列化的属性传入
clientEntry(...) - 在中间件栈中没有时调用
asyncContext()getContext() - 中间件顺序错误;静态文件等快速退出的中间件应放在前面,丰富请求的中间件放在后面
- 跳过边界验证,信任原始的、参数、Cookie或外部负载
FormData - 让路由本地领域错误泄露到控制器之外。将预期结果(验证、冲突、未找到)转换为路由应返回的HTTP ,而非抛出自定义
Response子类并在其他地方捕获Error - 当需要防篡改或服务端管理的单浏览器状态时,使用而非
createCookie。如果修改值会导致错误,应使用会话remix/session - 当普通表单POST、重定向或资源路由更简单时,构建纯JSON的RPC层。客户端Fetch是健全路由行为之上的一层,而非替代方案
- 将JSON状态端点和重载视为互斥模式。选择最适合UX的最轻量级同步机制;小组件可以合理地轮询JSON端点
<Frame> - 假设认证足够,而不进行每个资源的授权检查
- 当归属明确时,将共享代码放入、
utils.ts或helpers.ts等模糊存储区common.ts - 仅为主要行为是HTTP路由相关的功能编写组件测试
Package Map
包映射
Use this map to find the right package quickly. Each entry says what the package is for, not just
what it exports. Open the linked reference file when you need full examples.
使用此映射快速找到合适的包。每个条目说明了包的用途,而非仅导出内容。需要完整示例时,打开链接的参考文件。
Routing, Server, and Responses
路由、服务器与响应
- — the router itself. Use for
remix/fetch-router, controller and middleware types, and registering routescreateRouter - — declarative route builders. Use for
remix/fetch-router/routes,route,get,post,put,del,formwhen definingresourcesapp/routes.ts - — adapter from Node's
remix/node-fetch-servermodule to a Fetch-style router. Use forhttpincreateRequestListenerserver.ts - — browser asset server. Use for
remix/assetswhen serving compiled scripts and styles, getting public hrefs, and emitting preloads. Shared compiler options such ascreateAssetServer,target,sourceMaps, andsourceMapSourcePathslive at the top levelminify - — typed header parsers and builders. Use when reading
remix/headers,Accept, or settingCookie,CacheControl, etc., instead of hand-formatting stringsVary - —
remix/response/redirect. Use for the canonical "POST then redirect" pattern and other location changesredirect(href, status?) - —
remix/response/html. Use when you need an HTMLcreateHtmlResponsefrom a string or stream without rendering throughResponseremix/ui - —
remix/response/compress. Use when compressing one-off responses outside the globalcompressResponsemiddlewarecompression() - — file-download responses. Use for
remix/response/fileresponsesContent-Disposition: attachment - — low-level URL matching and generation. Use when working with raw patterns outside the router (custom matchers, scripts)
remix/route-pattern - — Fetch-based HTTP proxying. Use to forward a request to another origin; pass
remix/fetch-proxywhen the upstream needs forwarded proto, host, and portxForwardedHeaders
- —— 路由本身。用于
remix/fetch-router、控制器和中间件类型,以及注册路由createRouter - —— 声明式路由构建器。定义
remix/fetch-router/routes时,用于app/routes.ts、route、get、post、put、del、formresources - —— Node的
remix/node-fetch-server模块到Fetch风格路由的适配器。在http中用于server.tscreateRequestListener - —— 浏览器资源服务器。用于
remix/assets,提供编译后的脚本和样式、获取公共href以及生成预加载。createAssetServer、target、sourceMaps和sourceMapSourcePaths等共享编译选项位于顶层minify - —— 类型化的头信息解析器和构建器。读取
remix/headers、Accept或设置Cookie、CacheControl等时使用,而非手动格式化字符串Vary - ——
remix/response/redirect。用于标准的“POST后重定向”模式和其他位置变更redirect(href, status?) - ——
remix/response/html。当需要从字符串或流生成HTMLcreateHtmlResponse而不通过Response渲染时使用remix/ui - ——
remix/response/compress。在全局compressResponse中间件之外压缩一次性响应时使用compression() - —— 文件下载响应。用于
remix/response/file响应Content-Disposition: attachment - —— 底层URL匹配和生成。在路由之外处理原始模式(自定义匹配器、脚本)时使用
remix/route-pattern - —— 基于Fetch的HTTP代理。用于将请求转发到其他源;当上游需要转发的协议、主机和端口时,传递
remix/fetch-proxyxForwardedHeaders
Data, Validation, and Persistence
数据、验证与持久化
- — schema builders for runtime validation. Use for
remix/data-schemaandparseto validate any input that crosses a trust boundary, andparseSafewhen validated output should map to a different value or type.transform(...) - — common check helpers (
remix/data-schema/checks,email,minLength, etc.). Use to compose into a schemamaxLength - — coercion helpers for strings, numbers, booleans, dates, and ids. Use when input arrives as a string but should be a typed value
remix/data-schema/coerce - —
remix/data-schema/form-dataandf.objectfor parsingf.fielddirectly. Use in actions that read browser formsFormData - — typed tables and a
remix/data-tableinterface. Use forDatabase,table,columnwhen modeling persisted datacreateDatabase - ,
remix/data-table-sqlite,remix/data-table-postgres— adapters. Use to backremix/data-table-mysqlwith a real engine. SQLite accepts Node, Bun, and compatible synchronous clients with the sharedcreateDatabase/preparesurfaceexec - — migration authoring and runners. Use for
remix/data-table/migrations,createMigrationcreateMigrationRunner - —
remix/data-table/migrations/nodefrom disk. Use in startup scripts that apply migrationsloadMigrations - — query operators such as
remix/data-table/operators. Use wheninList(...)clauses need set or comparison logicwhere
- —— 用于运行时验证的Schema构建器。使用
remix/data-schema和parse验证任何跨越信任边界的输入,当验证后的输出需要映射到不同值或类型时使用parseSafe.transform(...) - —— 通用检查助手(
remix/data-schema/checks、email、minLength等)。用于组合到Schema中maxLength - —— 字符串、数字、布尔值、日期和ID的强制转换助手。当输入以字符串形式到达但应为类型化值时使用
remix/data-schema/coerce - —— 用于直接解析
remix/data-schema/form-data的FormData和f.object。在读取浏览器表单的动作中使用f.field - —— 类型化表和
remix/data-table接口。用于Database、table、column,对持久化数据建模createDatabase - 、
remix/data-table-sqlite、remix/data-table-postgres—— 适配器。用于为remix/data-table-mysql提供真实引擎支持。SQLite接受Node、Bun和兼容的同步客户端,具有共享的createDatabase/prepare接口exec - —— 迁移编写和运行器。用于
remix/data-table/migrations、createMigrationcreateMigrationRunner - —— 从磁盘加载迁移。在应用迁移的启动脚本中使用
remix/data-table/migrations/node - —— 查询操作符,如
remix/data-table/operators。当inList(...)子句需要集合或比较逻辑时使用where
Auth, Sessions, and Cookies
认证、会话与Cookie
- — the
remix/sessionobject:Session,get,set,flash,unset. Use for any per-browser state where tampering would be a bug (login, "I submitted this form already", cart, flash messages)regenerateId - —
remix/session-middleware. Use to wire a session cookie and storage backend into the root middleware stacksession(cookie, storage) - ,
remix/session/fs-storage,remix/session/memory-storage— storage backends. Useremix/session/cookie-storagefor single-process apps,fs-storagefor tests,memory-storagefor stateless deployments where data fits in a cookiecookie-storage - — Redis-backed storage. Use for multi-process or multi-host deployments
remix/session-storage-redis - — Memcache-backed storage. Same multi-host use case as Redis
remix/session-storage-memcache - —
remix/cookiefor plain signed/unsigned cookies. Use for non-sensitive preferences where the client is allowed to control the value (theme, locale, dismissed banner). For state where tampering matters, prefercreateCookieremix/session - — credentials, OAuth, OIDC, and Atmosphere providers. Use to define how identity is verified, start/finish external login, and refresh stored OAuth/OIDC token bundles with
remix/authrefreshExternalAuth(...) - —
remix/auth-middleware,auth({ schemes }), therequireAuthcontext key. Use to resolve identity into the request context and to gate routesAuth
- ——
remix/session对象:Session、get、set、flash、unset。用于任何防篡改的单浏览器状态(登录、“我已提交此表单”、购物车、闪现消息)regenerateId - ——
remix/session-middleware。用于将会话Cookie和存储后端连接到根中间件栈session(cookie, storage) - 、
remix/session/fs-storage、remix/session/memory-storage—— 存储后端。单进程应用使用remix/session/cookie-storage,测试使用fs-storage,数据可放入Cookie的无状态部署使用memory-storagecookie-storage - —— 基于Redis的存储。用于多进程或多主机部署
remix/session-storage-redis - —— 基于Memcache的存储。与Redis的多主机使用场景相同
remix/session-storage-memcache - ——
remix/cookie用于普通签名/未签名Cookie。用于非敏感偏好设置,客户端可控制值(主题、语言、已关闭的横幅)。对于防篡改的状态,优先使用createCookieremix/session - —— 凭证、OAuth、OIDC和Atmosphere提供者。用于定义身份验证方式、启动/完成外部登录,以及使用
remix/auth刷新存储的OAuth/OIDC令牌包refreshExternalAuth(...) - ——
remix/auth-middleware、auth({ schemes })、requireAuth上下文键。用于将身份解析到请求上下文,并保护路由Auth
UI, Hydration, and Browser Behavior
UI、水合与浏览器行为
- — the component runtime: components, core mixins,
remix/ui,clientEntry,run, navigation helpers, and<Frame>. Use for app UI behaviorcreateRoot - — server rendering:
remix/ui/server,renderToStream. Use in therenderToStringhelper that returns HTML responsesrender(...) - — animation APIs:
remix/ui/animation,animateEntrance,animateExit,animateLayout,spring, andtweeneasings - — UI primitives, mixins, glyphs, and theme helpers. Import from
remix/ui/<primitive>,remix/ui/accordion,remix/ui/button, etc.remix/ui/select - — component test rendering helpers such as
remix/ui/testrender - — JSX transform target. Configured in
remix/ui/jsx-runtime, rarely imported directlytsconfig.json - — escaped HTML template literals. Use when generating HTML outside the component system (RSS feeds, email bodies, error pages)
remix/html-template - — backend-agnostic
remix/file-storagestorage interface. Use as the type bound for upload destinationsFile - ,
remix/file-storage/fs,remix/file-storage/memory— storage backends. Use to implement an upload destinationremix/file-storage-s3
- —— 组件运行时:组件、核心Mixin、
remix/ui、clientEntry、run、导航助手和<Frame>。用于应用UI行为createRoot - —— 服务端渲染:
remix/ui/server、renderToStream。在返回HTML响应的renderToString助手中使用render(...) - —— 动画API:
remix/ui/animation、animateEntrance、animateExit、animateLayout、spring和tweeneasings - —— UI原语、Mixin、图标和主题助手。从
remix/ui/<primitive>、remix/ui/accordion、remix/ui/button等导入remix/ui/select - —— 组件测试渲染助手,如
remix/ui/testrender - —— JSX转换目标。在
remix/ui/jsx-runtime中配置,很少直接导入tsconfig.json - —— 转义的HTML模板字面量。在组件系统之外生成HTML(RSS源、邮件正文、错误页面)时使用
remix/html-template - —— 与后端无关的
remix/file-storage存储接口。用作上传目标的类型绑定File - 、
remix/file-storage/fs、remix/file-storage/memory—— 存储后端。用于实现上传目标remix/file-storage-s3
Middleware
中间件
- —
remix/static-middleware. Use to serve files fromstaticFiles(dir)exactly as they exist on diskpublic/ - —
remix/form-data-middleware. Use to parseformData()once and expose it viaFormDatainstead of callingget(FormData)in each actionawait request.formData() - — lower-level
remix/form-data-parser,parseFormData. Use when implementing custom upload handlers. Upload handler errors propagate directlyFileUpload - and
remix/multipart-parser— low-level multipart stream parsing.remix/multipart-parser/nodeis a plain object keyed by lower-case header name; read values with bracket notation such asMultipartPart.headerspart.headers['content-type'] - —
remix/compression-middleware. Use globally for text-like responsescompression() - —
remix/logger-middleware. Use in development for request logs; passlogger()to force terminal color output on or offcolors - —
remix/method-override-middleware. Use when HTML forms needmethodOverride(),PUT, orPATCHDELETE - —
remix/async-context-middleware,asyncContext(). Use when helpers outside actions need request context without threading it through every callgetContext() - —
remix/cors-middleware. Use for endpoints called cross-origincors(opts?) - —
remix/csrf-middleware. Use when session-backed forms mutate state and need synchronizer-token CSRF protectioncsrf(opts?) - — cross-origin protection. Use to reject unsafe cross-origin browser requests
remix/cop-middleware
- ——
remix/static-middleware。用于原样提供staticFiles(dir)中的文件public/ - ——
remix/form-data-middleware。用于一次性解析formData()并通过FormData暴露,而非在每个动作中调用get(FormData)await request.formData() - —— 底层的
remix/form-data-parser、parseFormData。用于实现自定义上传处理器。上传处理器错误会直接传播FileUpload - 和
remix/multipart-parser—— 底层多部分流解析。remix/multipart-parser/node是小写头名称为键的普通对象;使用括号表示法读取值,如MultipartPart.headerspart.headers['content-type'] - ——
remix/compression-middleware。全局用于类文本响应compression() - ——
remix/logger-middleware。在开发环境中用于请求日志;传递logger()强制开启或关闭终端颜色输出colors - ——
remix/method-override-middleware。当HTML表单需要methodOverride()、PUT或PATCH方法时使用DELETE - ——
remix/async-context-middleware、asyncContext()。当动作之外的助手需要请求上下文而无需在每次调用中传递时使用getContext() - ——
remix/cors-middleware。用于跨域调用的端点cors(opts?) - ——
remix/csrf-middleware。当基于会话的表单修改状态并需要同步令牌CSRF保护时使用csrf(opts?) - —— 跨域保护。用于拒绝不安全的跨域浏览器请求
remix/cop-middleware
Test
测试
- —
remix/test,describe, and lifecycle hooks. Use as the test frameworkit - — programmatic test runner APIs such as
remix/test/clirunRemixTest - — programmatic Remix CLI API. Use the
remix/cliexecutable for project commands such asremix,remix test, andremix routesremix doctor - — assertion helpers. Use in place of
remix/assertso messages render cleanly in the runnernode:assert - — ANSI styles, color detection, style factories, and testable terminal streams. Use for CLIs and terminal output instead of hand-rolled escape sequences
remix/terminal
- ——
remix/test、describe和生命周期钩子。用作测试框架it - —— 程序化测试运行器API,如
remix/test/clirunRemixTest - —— 程序化Remix CLI API。使用
remix/cli可执行文件执行项目命令,如remix、remix test和remix routesremix doctor - —— 断言助手。替代
remix/assert使用,使消息在运行器中清晰显示node:assert - —— ANSI样式、颜色检测、样式工厂和可测试的终端流。用于CLI和终端输出,而非手动编写转义序列
remix/terminal
Canonical Patterns
标准模式
Define routes first
先定义路由
typescript
import { form, get, post, resources, route } from 'remix/fetch-router/routes'
export const routes = route({
home: '/',
contact: form('contact'),
books: {
index: '/books',
show: '/books/:slug',
},
auth: route('auth', {
login: form('login'),
logout: post('logout'),
}),
admin: route('admin', {
index: get('/'),
books: resources('books', { param: 'bookId' }),
}),
})typescript
import { form, get, post, resources, route } from 'remix/fetch-router/routes'
export const routes = route({
home: '/',
contact: form('contact'),
books: {
index: '/books',
show: '/books/:slug',
},
auth: route('auth', {
login: form('login'),
logout: post('logout'),
}),
admin: route('admin', {
index: get('/'),
books: resources('books', { param: 'bookId' }),
}),
})Type controllers against the route contract
针对路由契约为控制器添加类型
typescript
import type { Controller } from 'remix/fetch-router'
import type { AppContext } from '../router.ts'
import { routes } from '../routes.ts'
export default {
actions: {
async index({ get }) {
let db = get(Database)
let allBooks = await db.findMany(books, { orderBy: ['id', 'asc'] })
return render(<BooksIndexPage allBooks={allBooks} />)
},
async show({ get, params }) {
let db = get(Database)
let book = await db.findOne(books, { where: { slug: params.slug } })
if (!book) return new Response('Not Found', { status: 404 })
return render(<BookShowPage book={book} />)
},
},
} satisfies Controller<typeof routes.books, AppContext>typescript
import type { Controller } from 'remix/fetch-router'
import type { AppContext } from '../router.ts'
import { routes } from '../routes.ts'
export default {
actions: {
async index({ get }) {
let db = get(Database)
let allBooks = await db.findMany(books, { orderBy: ['id', 'asc'] })
return render(<BooksIndexPage allBooks={allBooks} />)
},
async show({ get, params }) {
let db = get(Database)
let book = await db.findOne(books, { where: { slug: params.slug } })
if (!book) return new Response('Not Found', { status: 404 })
return render(<BookShowPage book={book} />)
},
},
} satisfies Controller<typeof routes.books, AppContext>Compose middleware deliberately
谨慎组合中间件
typescript
import {
createRouter,
type AnyParams,
type MiddlewareContext,
type WithParams,
} from 'remix/fetch-router'
export type RootMiddleware = [
ReturnType<typeof formData>,
ReturnType<typeof session>,
ReturnType<typeof loadDatabase>,
ReturnType<typeof loadAuth>,
]
export type AppContext<params extends AnyParams = AnyParams> = WithParams<
MiddlewareContext<RootMiddleware>,
params
>
let middleware = []
if (process.env.NODE_ENV === 'development') {
middleware.push(logger())
}
middleware.push(compression())
middleware.push(staticFiles('./public'))
middleware.push(formData())
middleware.push(methodOverride())
middleware.push(session(cookie, storage))
middleware.push(asyncContext())
middleware.push(loadDatabase())
middleware.push(loadAuth())
let router = createRouter({ middleware })typescript
import {
createRouter,
type AnyParams,
type MiddlewareContext,
type WithParams,
} from 'remix/fetch-router'
export type RootMiddleware = [
ReturnType<typeof formData>,
ReturnType<typeof session>,
ReturnType<typeof loadDatabase>,
ReturnType<typeof loadAuth>,
]
export type AppContext<params extends AnyParams = AnyParams> = WithParams<
MiddlewareContext<RootMiddleware>,
params
>
let middleware = []
if (process.env.NODE_ENV === 'development') {
middleware.push(logger())
}
middleware.push(compression())
middleware.push(staticFiles('./public'))
middleware.push(formData())
middleware.push(methodOverride())
middleware.push(session(cookie, storage))
middleware.push(asyncContext())
middleware.push(loadDatabase())
middleware.push(loadAuth())
let router = createRouter({ middleware })Validate, mutate, and respond
验证、修改与响应
typescript
import { redirect } from 'remix/response/redirect'
import * as s from 'remix/data-schema'
import * as f from 'remix/data-schema/form-data'
import { Session } from 'remix/session'
import { Database } from 'remix/data-table'
let bookSchema = f.object({
slug: f.field(s.string()),
title: f.field(s.string()),
})
export default {
actions: {
async create({ get }) {
let parsed = s.parseSafe(bookSchema, get(FormData))
if (!parsed.success) {
return render(<NewBookPage errors={parsed.issues} />, { status: 400 })
}
let db = get(Database)
let book = await db.create(books, parsed.value)
let session = get(Session)
session.flash('message', `Added ${book.title}.`)
return redirect(routes.books.show.href({ slug: book.slug }))
},
},
} satisfies Controller<typeof routes.books, AppContext>This shape works without JavaScript, returns a for every outcome, and is ready for
interactivity when the UI needs it.
ResponseclientEntry(...)typescript
import { redirect } from 'remix/response/redirect'
import * as s from 'remix/data-schema'
import * as f from 'remix/data-schema/form-data'
import { Session } from 'remix/session'
import { Database } from 'remix/data-table'
let bookSchema = f.object({
slug: f.field(s.string()),
title: f.field(s.string()),
})
export default {
actions: {
async create({ get }) {
let parsed = s.parseSafe(bookSchema, get(FormData))
if (!parsed.success) {
return render(<NewBookPage errors={parsed.issues} />, { status: 400 })
}
let db = get(Database)
let book = await db.create(books, parsed.value)
let session = get(Session)
session.flash('message', `Added ${book.title}.`)
return redirect(routes.books.show.href({ slug: book.slug }))
},
},
} satisfies Controller<typeof routes.books, AppContext>这种结构无需JavaScript即可工作,为每种结果返回,并且当UI需要时可随时添加交互性。
ResponseclientEntry(...)Build UI from handle props plus render
从handle属性和渲染构建UI
tsx
import { on, type Handle } from 'remix/ui'
function Counter(handle: Handle<{ initialCount?: number; label: string }>) {
let count = handle.props.initialCount ?? 0
return () => (
<button
mix={on('click', () => {
count++
handle.update()
})}
>
{handle.props.label}: {count}
</button>
)
}Only add and when the component needs browser interactivity or
browser-only APIs.
clientEntry(...)run(...)tsx
import { on, type Handle } from 'remix/ui'
function Counter(handle: Handle<{ initialCount?: number; label: string }>) {
let count = handle.props.initialCount ?? 0
return () => (
<button
mix={on('click', () => {
count++
handle.update()
})}
>
{handle.props.label}: {count}
</button>
)
}仅当组件需要浏览器交互或仅浏览器API时,才添加和。
clientEntry(...)run(...)