remix

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Build 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
Request
,
Response
,
URL
, and
FormData
. All packages ship from a single npm package,
remix
, and are imported via subpath. There is no top-level
remix
import.
A Remix app has four main pieces:
  • Routes in
    app/routes.ts
    define the typed URL contract and power
    href()
    generation.
  • Controllers and actions implement that contract and return
    Response
    objects.
  • Middleware composes request lifecycle behavior and populates typed context via
    context.set(Key, value)
    .
  • Components render UI with
    remix/ui
    . This is not React. A component receives a
    handle
    , reads current props from
    handle.props
    , and returns a render function.
Remix 3是一款基于Web API(如
Request
Response
URL
FormData
)构建的服务端优先Web框架。所有包都通过单个npm包
remix
发布,并通过子路径导入,不存在顶层
remix
导入。
Remix应用包含四个核心部分:
  • 路由:位于
    app/routes.ts
    中,定义类型化URL契约,为
    href()
    生成提供支持。
  • 控制器与动作:实现URL契约并返回
    Response
    对象。
  • 中间件:组合请求生命周期行为,并通过
    context.set(Key, value)
    填充类型化上下文。
  • 组件:使用
    remix/ui
    渲染UI,这不是React。组件接收
    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
references/routing-and-controllers.md
Composing the request lifecycle, ordering middleware, bridging to a server
references/middleware-and-server.md
Compiling and serving browser modules, asset URL namespaces, preloads
references/assets-and-browser-modules.md
Parsing input, validating with schemas, defining tables, querying, migrations
references/data-and-validation.md
Per-browser state, login flows, route protection, identity
references/auth-and-sessions.md
Component setup, state, lifecycle, updates,
queueTask
, context
references/component-model.md
Event handlers, styles, refs, click/key behavior, simple animations
references/mixins-styling-events.md
clientEntry
,
run
,
<Frame>
, navigation,
<head>
references/hydration-frames-navigation.md
Router tests, component tests, test isolation
references/testing-patterns.md
Spring physics, tweens, layout transitions
references/animate-elements.md
Authoring custom reusable mixins
references/create-mixins.md
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、编写控制器和动作、返回响应
references/routing-and-controllers.md
组合请求生命周期、调整中间件顺序、与服务器对接
references/middleware-and-server.md
编译和提供浏览器模块、资源URL命名空间、预加载
references/assets-and-browser-modules.md
解析输入、使用Schema验证、定义表、查询、迁移
references/data-and-validation.md
单浏览器状态、登录流程、路由保护、身份认证
references/auth-and-sessions.md
组件配置、状态、生命周期、更新、
queueTask
、上下文
references/component-model.md
事件处理器、样式、引用、点击/按键行为、简单动画
references/mixins-styling-events.md
clientEntry
run
<Frame>
、导航、
<head>
references/hydration-frames-navigation.md
路由测试、组件测试、测试隔离
references/testing-patterns.md
弹簧物理、补间动画、布局过渡
references/animate-elements.md
编写自定义可复用mixin
references/create-mixins.md
常见组合:
  • 表单或CRUD功能 -> 路由、数据与验证、测试;如果是用户专属功能,添加认证相关资料
  • 受保护区域 -> 认证与会话、路由、测试
  • 交互式组件 -> 组件模型、Mixin与样式;仅当需要在浏览器中运行时添加水合相关资料
  • 浏览器资源管道 -> 资源与浏览器模块、水合、中间件与服务器
  • 文件上传 -> 中间件与服务器、数据与验证、测试
  • 导航或框架 -> 水合、框架、导航

Default Workflow

默认工作流程

  1. Classify the change. Decide whether it changes the route contract, request lifecycle, data model, auth or session behavior, or only UI.
  2. Start from the server contract. Add or update
    app/routes.ts
    before wiring handlers or UI.
  3. Put code in the narrowest owner. Favor route-local code first, then promote only when reuse is real.
  4. Make the server path correct before adding browser behavior. A route should return the right
    Response
    via
    router.fetch(...)
    before you add
    clientEntry(...)
    , animations, or DOM effects.
  5. Add middleware deliberately. Keep fast-exit middleware early and request-enriching middleware later. Export a typed
    AppContext
    from the root middleware stack and use it in controllers.
  6. Validate input at the boundary. Parse and validate
    Request
    ,
    FormData
    , params, cookies, and external payloads before they reach rendering or persistence logic.
  7. Hydrate only when necessary. Prefer server-rendered UI. Use
    clientEntry(...)
    and
    run(...)
    only for real browser interactivity or browser-only APIs.
  8. Test the narrowest meaningful layer. Prefer router tests for route behavior. Use component tests when the behavior is truly interactive or DOM-specific.
  9. Finish with verification. Re-read the route flow, confirm auth and authorization boundaries, and run the smallest relevant test and typecheck loop.
  1. 对变更进行分类:确定变更是否会修改路由契约、请求生命周期、数据模型、认证或会话行为,还是仅涉及UI。
  2. 从服务器契约开始:在连接处理器或UI之前,先添加或更新
    app/routes.ts
  3. 将代码放在最窄的归属方:优先使用路由本地代码,仅在确实需要复用再进行提升。
  4. 在添加浏览器行为前确保服务器路径正确:在添加
    clientEntry(...)
    、动画或DOM效果之前,路由应能通过
    router.fetch(...)
    返回正确的
    Response
  5. 谨慎添加中间件:将快速退出的中间件放在前面,将丰富请求的中间件放在后面。从根中间件栈导出类型化的
    AppContext
    并在控制器中使用。
  6. 在边界处验证输入:在输入到达渲染或持久化逻辑之前,解析并验证
    Request
    FormData
    、参数、Cookie和外部负载。
  7. 仅在必要时进行水合:优先使用服务端渲染UI。仅在需要真正的浏览器交互或仅浏览器API时,才使用
    clientEntry(...)
    run(...)
  8. 测试最窄的有意义层级:优先使用路由测试验证路由行为。当行为确实是交互式或DOM专属时,再使用组件测试。
  9. 完成后进行验证:重新阅读路由流程,确认认证与授权边界,并运行最小的相关测试和类型检查循环。

Project Layout

项目布局

Use these root directories consistently:
  • app/
    for runtime application code
  • db/
    for migrations and local database files
  • public/
    for static assets served as-is
  • test/
    for shared helpers, fixtures, and integration coverage
  • tmp/
    for uploads, caches, local session files, and other scratch data
Inside
app/
, organize by responsibility:
  • assets/
    for client entrypoints and client-owned browser behavior
  • controllers/
    for route-owned handlers and route-local UI
  • data/
    for schema, queries, persistence setup, migrations, and runtime data initialization
  • middleware/
    for request lifecycle concerns such as auth, sessions, uploads, and database injection
  • ui/
    for shared cross-route UI primitives
  • utils/
    only for genuinely cross-layer helpers that do not clearly belong elsewhere
  • routes.ts
    for the route contract
  • router.ts
    for router setup and wiring
请统一使用以下根目录:
  • app/
    :存放运行时应用代码
  • db/
    :存放迁移文件和本地数据库文件
  • public/
    :存放原样提供的静态资源
  • test/
    :存放共享助手、测试数据和集成测试覆盖代码
  • tmp/
    :存放上传文件、缓存、本地会话文件和其他临时数据
app/
内部,按职责组织:
  • assets/
    :存放客户端入口点和客户端专属浏览器行为代码
  • controllers/
    :存放路由专属处理器和路由本地UI
  • data/
    :存放Schema、查询、持久化配置、迁移和运行时数据初始化代码
  • middleware/
    :存放请求生命周期相关代码,如认证、会话、上传和数据库注入
  • ui/
    :存放跨路由共享的UI原语
  • utils/
    :仅存放真正跨层级且无明确归属的助手代码
  • routes.ts
    :路由契约文件
  • router.ts
    :路由配置和连接文件

Placement Precedence

放置优先级

When code could live in multiple places:
  1. Put it in the narrowest owner first.
  2. If it belongs to one route, keep it with that route.
  3. If it is shared UI across route areas, move it to
    app/ui/
    .
  4. If it is request lifecycle setup, keep it in
    app/middleware/
    .
  5. If it is schema, query, persistence, or startup data logic, keep it in
    app/data/
    .
  6. Use
    app/utils/
    only as a last resort for truly cross-layer helpers.
当代码可放在多个位置时:
  1. 优先放在最窄的归属方。
  2. 如果属于单个路由,将其与该路由放在一起。
  3. 如果是跨路由区域的共享UI,移至
    app/ui/
  4. 如果是请求生命周期配置,放在
    app/middleware/
  5. 如果是Schema、查询、持久化或启动数据逻辑,放在
    app/data/
  6. 仅当确实没有其他合适位置时,才使用
    app/utils/
    存放跨层级助手代码。

Route Ownership

路由归属

  • Use a flat file in
    app/controllers/
    for a simple leaf action, such as
    app/controllers/home.tsx
  • Use a folder with
    controller.tsx
    when a route owns nested routes or multiple actions, such as
    app/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.tsx
    的文件夹,如
    app/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
    app/lib/
    as a generic dumping ground
  • Do not create
    app/components/
    as a second shared UI bucket when
    app/ui/
    already owns that role
  • Do not put shared cross-route UI in
    app/controllers/
  • Do not put middleware or persistence helpers in
    app/utils/
    when they have a clearer home
  • Do not create folders for simple leaf actions unless they are real controllers
  • 不要创建
    app/lib/
    作为通用垃圾场
  • app/ui/
    已承担共享UI职责时,不要创建
    app/components/
    作为第二个共享UI存储区
  • 不要将跨路由共享UI放在
    app/controllers/
  • 当中间件或持久化助手有明确归属时,不要放在
    app/utils/
  • 不要为简单的叶子动作创建文件夹,除非它们是真正的控制器

Core Remix Rules

Remix核心规则

  • Import from
    remix/<subpath>
    , never
    import { ... } from 'remix'
  • Treat
    app/routes.ts
    as the source of truth for URLs. Use
    routes.<name>.href(...)
    for redirects, links, tests, and internal URL construction
  • Controllers and actions should return explicit
    Response
    objects, including redirects, 404s, and validation failures. At the route boundary, prefer returning a
    Response
    for expected outcomes (validation errors, conflicts, not found) over throwing for control flow
  • 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
    clientEntry(...)
    layers interactivity on top
  • Validate input at the boundary using
    remix/data-schema
    (and
    remix/data-schema/form-data
    for forms).
    parseSafe
    makes the failure path a return value instead of an exception
  • Derive
    AppContext
    from the root middleware stack so
    get(Database)
    ,
    get(Session)
    ,
    get(Auth)
    , and similar keys stay typed. If the controller never reads from context, it doesn't need the harness
  • Outside actions and controllers, only use
    getContext()
    when
    asyncContext()
    is in the middleware stack
  • Remix Component is not React: read props from
    handle.props
    , keep state in setup-scope variables, call
    handle.update()
    explicitly, and do DOM-sensitive work in event handlers or
    queueTask(...)
    , not in render
  • Prefer host-element mixins via
    mix={mixin(...)}
    for behavior and styling instead of inventing custom host prop conventions. Use
    mix={[...]}
    only when composing multiple mixins
  • Hydrated
    clientEntry(...)
    props must be serializable. Do not pass functions, class instances, or opaque runtime objects
  • remix/<subpath>
    导入,绝不要使用
    import { ... } from 'remix'
  • app/routes.ts
    视为URL的唯一来源。在重定向、链接、测试和内部URL构建中使用
    routes.<name>.href(...)
  • 控制器和动作应返回明确的
    Response
    对象,包括重定向、404和验证失败。在路由边界,对于预期结果(验证错误、冲突、未找到),优先返回
    Response
    而非通过抛出异常进行控制流处理
  • 显式建模HTTP行为。状态码、头信息、重定向、缓存规则和内容类型是路由契约的一部分
  • 先确保服务端路由正确。在
    clientEntry(...)
    添加交互性之前,POST请求本身应已能返回正确的HTML、重定向或错误响应
  • 使用
    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()
    ,并在事件处理器或
    queueTask(...)
    中处理DOM相关工作,而非在渲染中处理
  • 优先通过
    mix={mixin(...)}
    使用宿主元素Mixin实现行为和样式,而非自定义宿主属性约定。仅在组合多个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:
    httpOnly
    always,
    sameSite
    by default, and
    secure
    when serving over HTTPS
  • Regenerate session IDs on login, logout, and privilege changes
  • Use
    requireAuth()
    to protect authenticated route areas, but still authorize resource ownership inside handlers and data writes
  • 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
    remix/html-template
    for HTML generation so escaping stays correct
  • Validate uploads for size, type, and destination. Treat filenames and content as untrusted input
  • 绝不要发布演示密钥。在非测试环境中,要求从环境变量获取会话和提供者密钥,如果缺失则快速失败
  • 使用加固的Cookie:始终启用
    httpOnly
    ,默认启用
    sameSite
    ,在HTTPS服务时启用
    secure
  • 在登录、登出和权限变更时重新生成会话ID
  • 使用
    requireAuth()
    保护认证路由区域,但仍需在处理器和数据写入中验证资源所有权
  • 当浏览器表单使用基于Cookie的会话修改状态时,添加CSRF保护
  • 仅为必须跨域调用的端点添加CORS。默认优先使用同源
  • 优先使用JSX或
    remix/html-template
    生成HTML,以确保转义正确
  • 验证上传文件的大小、类型和目标位置。将文件名和内容视为不可信输入

Testing Defaults

测试默认值

  • Prefer server and router tests first. Drive the app with
    router.fetch(new Request(...))
    and assert on the returned
    Response
  • Build a fresh router per test or per suite so sessions, in-memory storage, and database state stay isolated
  • Use
    routes.<name>.href(...)
    in tests so URLs stay coupled to the route contract
  • For auth or session scenarios, use a test cookie and
    createMemorySessionStorage()
    instead of production storage
  • Use component tests only for interactive or DOM-specific behavior. Render with
    createRoot(...)
    , interact with the real DOM, and call
    root.flush()
    between steps
  • Prefer one representative behavior test over many repetitive assertion variants
  • 优先使用服务端和路由测试。通过
    router.fetch(new Request(...))
    驱动应用,并断言返回的
    Response
  • 每个测试或测试套件构建一个全新的路由,确保会话、内存存储和数据库状态隔离
  • 在测试中使用
    routes.<name>.href(...)
    ,使URL与路由契约保持关联
  • 对于认证或会话场景,使用测试Cookie和
    createMemorySessionStorage()
    而非生产存储
  • 仅对交互式或DOM专属行为使用组件测试。使用
    createRoot(...)
    渲染,与真实DOM交互,并在步骤之间调用
    root.flush()
  • 优先编写一个代表性的行为测试,而非多个重复的断言变体

Common Mistakes To Avoid

需避免的常见错误

  • Treating Remix Component like React and reaching for hooks or implicit rerendering
  • Importing from a top-level
    remix
    entry instead of a subpath
  • Adding
    clientEntry(...)
    before the server-rendered route behavior is correct
  • Passing non-serializable props into
    clientEntry(...)
  • Calling
    getContext()
    without
    asyncContext()
    in the middleware stack
  • Getting middleware order wrong; fast exits like static files belong early, request enrichment later
  • Skipping boundary validation and trusting raw
    FormData
    , params, cookies, or external payloads
  • Letting route-local domain errors leak out of the controller. Translate expected outcomes (validation, conflicts, not-found) into the HTTP
    Response
    the route means to return rather than throwing a custom
    Error
    subclass and catching it elsewhere
  • Reaching for
    createCookie
    when a tamper-sensitive or server-managed per-browser fact really wants
    remix/session
    . If editing the value would be a bug, use a 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
    <Frame>
    reloads as mutually exclusive patterns. Pick the lightest sync mechanism that fits the UX; small widgets may reasonably poll a JSON endpoint
  • Assuming authentication is enough without per-resource authorization checks
  • Dropping shared code into vague buckets like
    utils.ts
    ,
    helpers.ts
    , or
    common.ts
    when ownership is known
  • Writing only component tests for a feature whose main behavior is really an HTTP route concern
  • 将Remix Component当作React使用,尝试使用钩子或隐式重新渲染
  • 从顶层
    remix
    入口导入,而非子路径
  • 在服务端渲染路由行为正确之前添加
    clientEntry(...)
  • 将不可序列化的属性传入
    clientEntry(...)
  • 在中间件栈中没有
    asyncContext()
    时调用
    getContext()
  • 中间件顺序错误;静态文件等快速退出的中间件应放在前面,丰富请求的中间件放在后面
  • 跳过边界验证,信任原始的
    FormData
    、参数、Cookie或外部负载
  • 让路由本地领域错误泄露到控制器之外。将预期结果(验证、冲突、未找到)转换为路由应返回的HTTP
    Response
    ,而非抛出自定义
    Error
    子类并在其他地方捕获
  • 当需要防篡改或服务端管理的单浏览器状态时,使用
    createCookie
    而非
    remix/session
    。如果修改值会导致错误,应使用会话
  • 当普通表单POST、重定向或资源路由更简单时,构建纯JSON的RPC层。客户端Fetch是健全路由行为之上的一层,而非替代方案
  • 将JSON状态端点和
    <Frame>
    重载视为互斥模式。选择最适合UX的最轻量级同步机制;小组件可以合理地轮询JSON端点
  • 假设认证足够,而不进行每个资源的授权检查
  • 当归属明确时,将共享代码放入
    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

路由、服务器与响应

  • remix/fetch-router
    — the router itself. Use for
    createRouter
    , controller and middleware types, and registering routes
  • remix/fetch-router/routes
    — declarative route builders. Use for
    route
    ,
    get
    ,
    post
    ,
    put
    ,
    del
    ,
    form
    ,
    resources
    when defining
    app/routes.ts
  • remix/node-fetch-server
    — adapter from Node's
    http
    module to a Fetch-style router. Use for
    createRequestListener
    in
    server.ts
  • remix/assets
    — browser asset server. Use for
    createAssetServer
    when serving compiled scripts and styles, getting public hrefs, and emitting preloads. Shared compiler options such as
    target
    ,
    sourceMaps
    ,
    sourceMapSourcePaths
    , and
    minify
    live at the top level
  • remix/headers
    — typed header parsers and builders. Use when reading
    Accept
    ,
    Cookie
    , or setting
    CacheControl
    ,
    Vary
    , etc., instead of hand-formatting strings
  • remix/response/redirect
    redirect(href, status?)
    . Use for the canonical "POST then redirect" pattern and other location changes
  • remix/response/html
    createHtmlResponse
    . Use when you need an HTML
    Response
    from a string or stream without rendering through
    remix/ui
  • remix/response/compress
    compressResponse
    . Use when compressing one-off responses outside the global
    compression()
    middleware
  • remix/response/file
    — file-download responses. Use for
    Content-Disposition: attachment
    responses
  • remix/route-pattern
    — low-level URL matching and generation. Use when working with raw patterns outside the router (custom matchers, scripts)
  • remix/fetch-proxy
    — Fetch-based HTTP proxying. Use to forward a request to another origin; pass
    xForwardedHeaders
    when the upstream needs forwarded proto, host, and port
  • remix/fetch-router
    —— 路由本身。用于
    createRouter
    、控制器和中间件类型,以及注册路由
  • remix/fetch-router/routes
    —— 声明式路由构建器。定义
    app/routes.ts
    时,用于
    route
    get
    post
    put
    del
    form
    resources
  • remix/node-fetch-server
    —— Node的
    http
    模块到Fetch风格路由的适配器。在
    server.ts
    中用于
    createRequestListener
  • remix/assets
    —— 浏览器资源服务器。用于
    createAssetServer
    ,提供编译后的脚本和样式、获取公共href以及生成预加载。
    target
    sourceMaps
    sourceMapSourcePaths
    minify
    等共享编译选项位于顶层
  • remix/headers
    —— 类型化的头信息解析器和构建器。读取
    Accept
    Cookie
    或设置
    CacheControl
    Vary
    等时使用,而非手动格式化字符串
  • remix/response/redirect
    ——
    redirect(href, status?)
    。用于标准的“POST后重定向”模式和其他位置变更
  • remix/response/html
    ——
    createHtmlResponse
    。当需要从字符串或流生成HTML
    Response
    而不通过
    remix/ui
    渲染时使用
  • remix/response/compress
    ——
    compressResponse
    。在全局
    compression()
    中间件之外压缩一次性响应时使用
  • remix/response/file
    —— 文件下载响应。用于
    Content-Disposition: attachment
    响应
  • remix/route-pattern
    —— 底层URL匹配和生成。在路由之外处理原始模式(自定义匹配器、脚本)时使用
  • remix/fetch-proxy
    —— 基于Fetch的HTTP代理。用于将请求转发到其他源;当上游需要转发的协议、主机和端口时,传递
    xForwardedHeaders

Data, Validation, and Persistence

数据、验证与持久化

  • remix/data-schema
    — schema builders for runtime validation. Use for
    parse
    and
    parseSafe
    to validate any input that crosses a trust boundary, and
    .transform(...)
    when validated output should map to a different value or type
  • remix/data-schema/checks
    — common check helpers (
    email
    ,
    minLength
    ,
    maxLength
    , etc.). Use to compose into a schema
  • remix/data-schema/coerce
    — 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/form-data
    f.object
    and
    f.field
    for parsing
    FormData
    directly. Use in actions that read browser forms
  • remix/data-table
    — typed tables and a
    Database
    interface. Use for
    table
    ,
    column
    ,
    createDatabase
    when modeling persisted data
  • remix/data-table-sqlite
    ,
    remix/data-table-postgres
    ,
    remix/data-table-mysql
    — adapters. Use to back
    createDatabase
    with a real engine. SQLite accepts Node, Bun, and compatible synchronous clients with the shared
    prepare
    /
    exec
    surface
  • remix/data-table/migrations
    — migration authoring and runners. Use for
    createMigration
    ,
    createMigrationRunner
  • remix/data-table/migrations/node
    loadMigrations
    from disk. Use in startup scripts that apply migrations
  • remix/data-table/operators
    — query operators such as
    inList(...)
    . Use when
    where
    clauses need set or comparison logic
  • remix/data-schema
    —— 用于运行时验证的Schema构建器。使用
    parse
    parseSafe
    验证任何跨越信任边界的输入,当验证后的输出需要映射到不同值或类型时使用
    .transform(...)
  • remix/data-schema/checks
    —— 通用检查助手(
    email
    minLength
    maxLength
    等)。用于组合到Schema中
  • remix/data-schema/coerce
    —— 字符串、数字、布尔值、日期和ID的强制转换助手。当输入以字符串形式到达但应为类型化值时使用
  • 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
    —— 适配器。用于为
    createDatabase
    提供真实引擎支持。SQLite接受Node、Bun和兼容的同步客户端,具有共享的
    prepare
    /
    exec
    接口
  • remix/data-table/migrations
    —— 迁移编写和运行器。用于
    createMigration
    createMigrationRunner
  • remix/data-table/migrations/node
    —— 从磁盘加载迁移。在应用迁移的启动脚本中使用
  • remix/data-table/operators
    —— 查询操作符,如
    inList(...)
    。当
    where
    子句需要集合或比较逻辑时使用

Auth, Sessions, and Cookies

认证、会话与Cookie

  • remix/session
    — the
    Session
    object:
    get
    ,
    set
    ,
    flash
    ,
    unset
    ,
    regenerateId
    . Use for any per-browser state where tampering would be a bug (login, "I submitted this form already", cart, flash messages)
  • remix/session-middleware
    session(cookie, storage)
    . Use to wire a session cookie and storage backend into the root middleware stack
  • remix/session/fs-storage
    ,
    remix/session/memory-storage
    ,
    remix/session/cookie-storage
    — storage backends. Use
    fs-storage
    for single-process apps,
    memory-storage
    for tests,
    cookie-storage
    for stateless deployments where data fits in a cookie
  • remix/session-storage-redis
    — Redis-backed storage. Use for multi-process or multi-host deployments
  • remix/session-storage-memcache
    — Memcache-backed storage. Same multi-host use case as Redis
  • remix/cookie
    createCookie
    for 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, prefer
    remix/session
  • remix/auth
    — 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
    refreshExternalAuth(...)
  • remix/auth-middleware
    auth({ schemes })
    ,
    requireAuth
    , the
    Auth
    context key. Use to resolve identity into the request context and to gate routes
  • remix/session
    ——
    Session
    对象:
    get
    set
    flash
    unset
    regenerateId
    。用于任何防篡改的单浏览器状态(登录、“我已提交此表单”、购物车、闪现消息)
  • remix/session-middleware
    ——
    session(cookie, storage)
    。用于将会话Cookie和存储后端连接到根中间件栈
  • remix/session/fs-storage
    remix/session/memory-storage
    remix/session/cookie-storage
    —— 存储后端。单进程应用使用
    fs-storage
    ,测试使用
    memory-storage
    ,数据可放入Cookie的无状态部署使用
    cookie-storage
  • remix/session-storage-redis
    —— 基于Redis的存储。用于多进程或多主机部署
  • remix/session-storage-memcache
    —— 基于Memcache的存储。与Redis的多主机使用场景相同
  • remix/cookie
    ——
    createCookie
    用于普通签名/未签名Cookie。用于非敏感偏好设置,客户端可控制值(主题、语言、已关闭的横幅)。对于防篡改的状态,优先使用
    remix/session
  • remix/auth
    —— 凭证、OAuth、OIDC和Atmosphere提供者。用于定义身份验证方式、启动/完成外部登录,以及使用
    refreshExternalAuth(...)
    刷新存储的OAuth/OIDC令牌包
  • remix/auth-middleware
    ——
    auth({ schemes })
    requireAuth
    Auth
    上下文键。用于将身份解析到请求上下文,并保护路由

UI, Hydration, and Browser Behavior

UI、水合与浏览器行为

  • remix/ui
    — the component runtime: components, core mixins,
    clientEntry
    ,
    run
    ,
    <Frame>
    , navigation helpers, and
    createRoot
    . Use for app UI behavior
  • remix/ui/server
    — server rendering:
    renderToStream
    ,
    renderToString
    . Use in the
    render(...)
    helper that returns HTML responses
  • remix/ui/animation
    — animation APIs:
    animateEntrance
    ,
    animateExit
    ,
    animateLayout
    ,
    spring
    ,
    tween
    , and
    easings
  • remix/ui/<primitive>
    — UI primitives, mixins, glyphs, and theme helpers. Import from
    remix/ui/accordion
    ,
    remix/ui/button
    ,
    remix/ui/select
    , etc.
  • remix/ui/test
    — component test rendering helpers such as
    render
  • remix/ui/jsx-runtime
    — JSX transform target. Configured in
    tsconfig.json
    , rarely imported directly
  • remix/html-template
    — escaped HTML template literals. Use when generating HTML outside the component system (RSS feeds, email bodies, error pages)
  • remix/file-storage
    — backend-agnostic
    File
    storage interface. Use as the type bound for upload destinations
  • remix/file-storage/fs
    ,
    remix/file-storage/memory
    ,
    remix/file-storage-s3
    — storage backends. Use to implement an upload destination
  • remix/ui
    —— 组件运行时:组件、核心Mixin、
    clientEntry
    run
    <Frame>
    、导航助手和
    createRoot
    。用于应用UI行为
  • remix/ui/server
    —— 服务端渲染:
    renderToStream
    renderToString
    。在返回HTML响应的
    render(...)
    助手中使用
  • remix/ui/animation
    —— 动画API:
    animateEntrance
    animateExit
    animateLayout
    spring
    tween
    easings
  • remix/ui/<primitive>
    —— UI原语、Mixin、图标和主题助手。从
    remix/ui/accordion
    remix/ui/button
    remix/ui/select
    等导入
  • remix/ui/test
    —— 组件测试渲染助手,如
    render
  • remix/ui/jsx-runtime
    —— JSX转换目标。在
    tsconfig.json
    中配置,很少直接导入
  • remix/html-template
    —— 转义的HTML模板字面量。在组件系统之外生成HTML(RSS源、邮件正文、错误页面)时使用
  • remix/file-storage
    —— 与后端无关的
    File
    存储接口。用作上传目标的类型绑定
  • remix/file-storage/fs
    remix/file-storage/memory
    remix/file-storage-s3
    —— 存储后端。用于实现上传目标

Middleware

中间件

  • remix/static-middleware
    staticFiles(dir)
    . Use to serve files from
    public/
    exactly as they exist on disk
  • remix/form-data-middleware
    formData()
    . Use to parse
    FormData
    once and expose it via
    get(FormData)
    instead of calling
    await request.formData()
    in each action
  • remix/form-data-parser
    — lower-level
    parseFormData
    ,
    FileUpload
    . Use when implementing custom upload handlers. Upload handler errors propagate directly
  • remix/multipart-parser
    and
    remix/multipart-parser/node
    — low-level multipart stream parsing.
    MultipartPart.headers
    is a plain object keyed by lower-case header name; read values with bracket notation such as
    part.headers['content-type']
  • remix/compression-middleware
    compression()
    . Use globally for text-like responses
  • remix/logger-middleware
    logger()
    . Use in development for request logs; pass
    colors
    to force terminal color output on or off
  • remix/method-override-middleware
    methodOverride()
    . Use when HTML forms need
    PUT
    ,
    PATCH
    , or
    DELETE
  • remix/async-context-middleware
    asyncContext()
    ,
    getContext()
    . Use when helpers outside actions need request context without threading it through every call
  • remix/cors-middleware
    cors(opts?)
    . Use for endpoints called cross-origin
  • remix/csrf-middleware
    csrf(opts?)
    . Use when session-backed forms mutate state and need synchronizer-token CSRF protection
  • remix/cop-middleware
    — cross-origin protection. Use to reject unsafe cross-origin browser requests
  • 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.headers
    是小写头名称为键的普通对象;使用括号表示法读取值,如
    part.headers['content-type']
  • remix/compression-middleware
    ——
    compression()
    。全局用于类文本响应
  • remix/logger-middleware
    ——
    logger()
    。在开发环境中用于请求日志;传递
    colors
    强制开启或关闭终端颜色输出
  • remix/method-override-middleware
    ——
    methodOverride()
    。当HTML表单需要
    PUT
    PATCH
    DELETE
    方法时使用
  • remix/async-context-middleware
    ——
    asyncContext()
    getContext()
    。当动作之外的助手需要请求上下文而无需在每次调用中传递时使用
  • remix/cors-middleware
    ——
    cors(opts?)
    。用于跨域调用的端点
  • remix/csrf-middleware
    ——
    csrf(opts?)
    。当基于会话的表单修改状态并需要同步令牌CSRF保护时使用
  • remix/cop-middleware
    —— 跨域保护。用于拒绝不安全的跨域浏览器请求

Test

测试

  • remix/test
    describe
    ,
    it
    , and lifecycle hooks. Use as the test framework
  • remix/test/cli
    — programmatic test runner APIs such as
    runRemixTest
  • remix/cli
    — programmatic Remix CLI API. Use the
    remix
    executable for project commands such as
    remix test
    ,
    remix routes
    , and
    remix doctor
  • remix/assert
    — assertion helpers. Use in place of
    node:assert
    so messages render cleanly in the runner
  • remix/terminal
    — ANSI styles, color detection, style factories, and testable terminal streams. Use for CLIs and terminal output instead of hand-rolled escape sequences
  • remix/test
    ——
    describe
    it
    和生命周期钩子。用作测试框架
  • remix/test/cli
    —— 程序化测试运行器API,如
    runRemixTest
  • remix/cli
    —— 程序化Remix CLI API。使用
    remix
    可执行文件执行项目命令,如
    remix test
    remix routes
    remix doctor
  • remix/assert
    —— 断言助手。替代
    node:assert
    使用,使消息在运行器中清晰显示
  • remix/terminal
    —— ANSI样式、颜色检测、样式工厂和可测试的终端流。用于CLI和终端输出,而非手动编写转义序列

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
Response
for every outcome, and is ready for
clientEntry(...)
interactivity when the UI needs it.
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即可工作,为每种结果返回
Response
,并且当UI需要时可随时添加
clientEntry(...)
交互性。

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
clientEntry(...)
and
run(...)
when the component needs browser interactivity or browser-only APIs.
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(...)