拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Remix 3 应用开发完全指南:路由契约、控制器、中间件与组件模型实战

Remix 3 应用开发完全指南路由契约、控制器、中间件与组件模型实战【免费下载链接】remixThe fully-stacked web framework项目地址: https://gitcode.com/GitHub_Trending/re/remixRemix 3 是一个 server-first 的 Web 框架构建在Request、Response、URL、FormData等标准 Web API 之上所有功能从单一 npm 包remix以子路径subpath方式导入。本文以仓库中的官方开发技能文档为骨架结合template/模板项目与packages/fetch-router、packages/remix的真实源码系统讲解如何从零搭建一个结构正确、类型安全、可测试的 Remix 应用——包括路由契约定义、控制器组织、中间件编排、输入校验、会话与认证、UI 组件模型以及测试策略。Remix 是什么四个核心组成一个 Remix 应用由四个相互独立又紧密耦合的部分组成它们各自承担不同的职责理解这一分层是正确使用 Remix 的前提Routes路由位于app/routes.ts定义类型化的 URL 契约并支撑href()的类型安全链接生成。路由契约同时被服务端与浏览器端共享是唯一事实来源source of truth。Controllers控制器位于app/actions下实现路由契约中声明的行为每个动作最终返回显式的Response对象。Middleware中间件编排请求生命周期行为通过context.set(Key, value)向请求上下文填充类型化数据。Components组件使用remix/ui渲染 UI。这不是 React——组件接收一个handle从handle.props读取当前 props并返回一个零参数渲染函数。所有包都从单一 npm 包remix以子路径方式导入不存在顶层remix导入。例如remix/router、remix/routes、remix/ui/button等。官方技能文档强调需要完整 API 文档时先查看发布包中对应源码文件旁的 READMEnode_modules/remix/src/subpath/README.md在源码仓库中权威 README 位于所属的packages/*包内而 packages/remix/src 下的镜像 README 是被有意忽略的。项目目录结构与放置优先级根目录约定技能文档规定了一套一致的根目录结构适用于所有 Remix 应用app/运行时应用代码db/迁移文件与本地数据库文件public/根级静态资源按应用根路径原样提供test/共享测试辅助、fixtures 与集成测试覆盖tmp/上传、缓存、本地 session 文件等临时数据在 template/app 模板中可以观察到这套结构的实际落地app/routes.ts定义路由契约app/router.ts负责路由器装配app/actions/存放控制器。app/内部按职责组织actions/控制器拥有的路由处理器、路由局部响应渲染、以及不跨路由区域共享的路由局部 UI/辅助代码data/schema、查询、持久化设置、迁移与运行时数据初始化middleware/认证、会话、上传、数据库注入等请求生命周期关注点app/actions/public/entry.ts浏览器运行时入口点public/目录放在最窄的属主内ui/跨路由共享的 UI 原语utils/仅用于确实跨层、且无更清晰归属的辅助代码routes.ts共享的服务端与浏览器端路由契约与类型安全 href 生成router.ts路由器设置与装配放置优先级Placement Precedence当一段代码可能放多个位置时按以下优先级决策先放在最窄的属主narrowest owner内属于单个路由的随该路由保留跨路由区域共享的 UI移到app/ui/请求生命周期设置的保留在app/middleware/schema、查询、持久化或启动数据逻辑放app/data/app/utils/仅作为真正的跨层辅助代码的最后手段。路由所有权Route Ownership顶层叶子动作放在app/actions/controller.tsx控制器的actions对象只包含传给router.map(...)的路由映射中的直接叶子路由键每个需要动作或控制器中间件的嵌套路由映射增加app/actions/route-key/controller.tsx并用router.map(routes.routeKey, controller)显式映射app/actions/下的目录以路由映射键命名而非 URL 路径段路由局部的 UI 与辅助代码放在拥有它们的控制器旁跨路由共享 UI 移到app/ui/若顶层叶子成长为路由映射将其处理器移入嵌套路由键控制器并在app/router.ts中显式映射该路由映射布局反模式Layout Anti-Patterns技能文档明确禁止的布局陷阱包括不要创建app/lib/作为通用垃圾场不要创建app/components/作为第二个共享 UI 桶app/ui/已承担该角色不要创建app/controllers/Remix 路由处理器位于app/actions/不要把共享跨路由 UI 放进app/actions/不要创建独立的根动作文件根路由动作放app/actions/controller.tsx不要把一个控制器的中间件误认为会保护另一个控制器注册的嵌套路由映射——每个需要保护的控制器都要显式添加控制器中间件。从路由契约到控制器核心工作流技能文档给出的默认工作流Default Workflow是端到端构建的九步指南分类变更判断变更影响的是路由契约、请求生命周期、数据模型、认证/会话行为还是仅 UI从服务端契约开始在接线处理器或 UI 之前先新增或更新app/routes.ts把代码放到最窄的属主优先路由局部代码确认真实复用时再上提先让服务端路径正确再添加浏览器行为一个路由应当通过router.fetch(...)返回正确的Response之后才添加clientEntry(...)、动画或 DOM 效果审慎添加中间件快速退出的中间件放前面请求增强型中间件放后面。从中间件栈导出类型化AppContext并在控制器中使用在边界校验输入在数据到达渲染或持久化逻辑之前解析并校验Request、FormData、params、cookies 与外部 payload仅在必要时水合Hydrate优先服务端渲染 UI。只有真实浏览器交互或浏览器专属 API 才使用clientEntry(...)和run(...)测试最窄的有意义层路由行为优先使用路由器测试真正交互式或 DOM 专属行为才用组件测试以验证收尾重读路由流程确认认证与授权边界运行最小相关的测试与类型检查循环。先定义路由Define routes first路由契约使用remix/routes的声明式构建器定义。packages/fetch-router/src/routes.ts 展示了这些构建器的真实出处route即createRoutes、get、post、put、del、head、options、patch、delete、form、resources等均从路由辅助模块导出。技能文档的标准示例import { form, get, post, resources, route } from remix/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 }), }), })template/app/routes.ts是更小的真实样例export const routes route({ assets: get(/assets/*path), home: / })。注意嵌套路由映射用route(prefix, { ... })包裹且路由映射键如home、books、auth用于后续的类型化访问。针对路由契约类型化控制器createController(routes, { actions })让每个动作的context.params类型与路由模式精确对应。packages/fetch-router/src/lib/controller.ts 中可以看到其类型机制ControllerActions只允许直接叶子路由键出现在actions中嵌套 RouteMap 键被类型层禁止createController返回控制器对象不变仅用于类型检查。技能文档的标准形态import { createController } from remix/router import { databaseContext } from ../middleware/database.ts import { routes } from ../routes.ts export default createController(routes.books, { actions: { async index({ get }) { let db get(databaseContext) let allBooks await db.findMany(books, { orderBy: [id, asc] }) return render(BooksIndexPage allBooks{allBooks} /) }, async show({ get, params }) { let db get(databaseContext) let book await db.findOne(books, { where: { slug: params.slug } }) if (!book) return new Response(Not Found, { status: 404 }) return render(BookShowPage book{book} /) }, }, })这里体现了 Remix 控制器的一个核心规则控制器应返回显式Response对象包括重定向、404 与校验失败。在路由边界期望的结果校验错误、冲突、未找到应转换为路由意图返回的 HTTPResponse而不是抛出自定义Error子类再在别处捕获——不要让路由局部的领域错误泄漏出控制器。显式注册控制器router.map(routes, controller)只映射routes中的直接叶子路由嵌套路由映射必须用各自显式的控制器映射。packages/fetch-router/src/lib/router.ts 的mapController实现会严格校验控制器actions中出现路由映射中不存在的键会抛Unknown action错误把嵌套路由映射键放进控制器actions会抛Cannot map nested route map key错误路由映射中的叶子缺少对应动作会抛Missing action错误。技能文档的标准装配import { createRouter } from remix/router import rootController from ./actions/controller.tsx import adminController from ./actions/admin/controller.tsx import adminBooksController from ./actions/admin/books/controller.tsx import authController from ./actions/auth/controller.tsx import authLoginController from ./actions/auth/login/controller.tsx import booksController from ./actions/books/controller.tsx import contactController from ./actions/contact/controller.tsx import { routes } from ./routes.ts export const router createRouter({ middleware }) router.map(routes, rootController) router.map(routes.contact, contactController) router.map(routes.books, booksController) router.map(routes.auth, authController) router.map(routes.auth.login, authLoginController) router.map(routes.admin, adminController) router.map(routes.admin.books, adminBooksController)template/app/router.ts展示了精简版createRouterAppContext({ middleware: [staticFiles(./public, { index: false }), renderMiddleware] })后接router.map(routes, controller)并通过declare module remix/router的RouterTypes.context扩展把AppContext注入全局类型。审慎编排中间件中间件是 Remix 请求生命周期的核心抽象。packages/fetch-router/src/lib/middleware.ts 显示一个中间件是一个特殊请求处理器(context, next) Response | PromiseResponse要么返回响应短路链条要么调用next()继续其类型层面用{ key, value }context.set(Key, value)或ContextEntries声明上下文效果MiddlewareContext类型递归推导出中间件栈最终产生的请求上下文。技能文档的编排原则是快速退出fast-exit的中间件放前面请求增强request-enriching的中间件放后面。标准示例import { createRouter } from remix/router 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 })在 packages/fetch-router/src/lib/router.ts 的dispatchRouter中可以看到路由器中间件包裹整个匹配分发过程runMiddleware(routerMiddleware, context, dispatch)动作级中间件则在dispatchMatches中于命中路由后执行。中间件栈的合并发生在mapController控制器中间件会与动作中间件通过mergeMiddleware连接控制器在前、动作在后。关于中间件与上下文技能文档还有两条重要规则从中间件栈导出类型化AppContextMiddlewareContext[...]使get(databaseContext)、get(Session)、get(Auth)等保持类型安全若控制器从不读上下文就不需要该 harness。在 actions 与控制器之外只有中间件栈中存在asyncContext()时才可使用getContext()。响应渲染与工具函数普通 Remix UI 应用应在路由器中间件栈中安装render()来自remix/middleware/render。当基于源码的clientEntry()模块需要浏览器 URL 时传入render({ assets })。在动作边界使用context.render(node, init)渲染 UI 响应。状态码、请求头等响应策略保持显式在动作中。renderWith(...)、renderToStream(...)、createHtmlResponse(...)仅在应用有意拥有自定义渲染契约或替换标准 UI 响应管线时使用。纯支撑代码放入聚焦的app/utils/topic.ts模块。格式化、MIME 分类、路径解析、排序与规范化应无需路由器、请求上下文或Response即可测试且不应从app/actions、remix/ui/server或remix/response/*导入。不要仅为让路由专属渲染器避开render(...)而引入 page-data 中间形状把响应组装留在 actions只抽取纯辅助函数。核心 Remix 规则速查从remix/subpath导入绝不写import { ... } from remix把app/routes.ts视为 URL 的事实来源。重定向、链接、测试与内部 URL 构造一律使用routes.name.href(...)控制器返回显式Response含重定向、404、校验失败在路由边界用Response表达期望结果而非抛错控制流router.map(routes, controller)只映射直接叶子路由嵌套路由映射需各自显式控制器显式建模 HTTP 行为状态码、请求头、重定向、缓存规则与内容类型都属于路由契约先让服务端路由正确POST 应自身就返回正确的 HTML、重定向或错误响应之后clientEntry(...)再叠加交互在边界用remix/data-schema表单用remix/data-schema/form-data校验输入parseSafe让失败路径变成返回值而非异常从中间件栈推导AppContext让get(databaseContext)、get(Session)、get(Auth)保持类型化Remix Component 不是 React写function Name(handle: HandleProps) { return () ... }从handle.props读 props状态保存在 setup 作用域变量中显式调用handle.update()DOM 敏感工作放在事件处理器或queueTask(...)中而非 render 里优先通过mix{mixin(...)}使用宿主元素 mixin组合多个 mixin 时用mix{[...]}简短的一次性静态样式用mix{css(...)}内联形成复用视觉配方、含大量选择器/媒体查询/keyframes 或大到遮蔽组件时才抽取模块级样式描述符仅当多个模块需要同一配方时才导出样式描述符否则随最窄属主保留水合的clientEntry(...)props 必须可序列化不传函数、类实例或不透明运行时对象安全与会话默认配置绝不发布演示密钥非测试环境要求 session 与 provider 密钥来自环境变量缺失即快速失败加固 cookiehttpOnly始终开启sameSite默认开启HTTPS 下开启secure登录、登出与权限变更时重新生成 session IDSession.regenerateId用requireAuth()保护认证路由区域但处理器与数据写入内部仍要做资源所有权授权浏览器表单在 cookie 会话下变更状态时添加 CSRF 保护仅对必须跨源调用的端点添加 CORS默认优先同源优先用 JSX 或remix/html-template生成 HTML保证转义正确校验上传的大小、类型与目的地把文件名与内容都视为不可信输入校验、变更与响应完整动作形态技能文档给出的校验-变更-响应标准模式演示了边界校验、会话 flash 与重定向的组合import { createController } from remix/router 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 { databaseContext } from ../middleware/database.ts import { routes } from ../routes.ts let bookSchema f.object({ slug: f.field(s.string()), title: f.field(s.string()), }) export default createController(routes.books, { 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(databaseContext) 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 })) }, }, })技能文档特别强调这个形态无需 JavaScript 即可工作每个结局都返回Response并且当 UI 需要时可以直接叠加clientEntry(...)交互。UI 组件模型从 handle props 构建Remix Component 与 React 有本质区别组件函数接收handle并返回零参数渲染函数状态存于 setup 作用域变量通过显式handle.update()触发更新。技能文档的计数器示例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(...)。JSX 转换目标配置在tsconfig.json中remix/ui/jsx-runtime与remix/ui/jsx-dev-runtime。测试默认策略优先服务端与路由器测试用router.fetch(new Request(...))驱动应用断言返回的Response。这正是 packages/fetch-router/src/lib/router.ts 中Router.fetch(input, init?)的用途——它创建RequestContext并走完整的中间件与匹配分发管线控制器测试形态对齐控制器根路由行为放app/actions/controller.test.ts(x)嵌套路由映射行为放在该路由键控制器旁每个测试或测试套件构建全新路由器保证 session、内存存储与数据库状态隔离测试中用routes.name.href(...)生成 URL保持与路由契约耦合认证/会话场景使用测试 cookie 与createMemorySessionStorage()而非生产存储纯app/utils辅助的测试与模块同置响应行为通过路由器或控制器测试验证组件测试仅用于交互式或 DOM 专属行为用createRoot(...)渲染、操作真实 DOM步骤间调用root.flush()优先一个有代表性的行为测试胜过多个重复断言变体包地图按任务选包技能文档提供了一张完整的包地图Package Map按这个包是干什么的而非它导出什么来组织是日常开发选包的核心工具。以下按领域归类核心子路径路由、服务器与响应子路径用途remix/router路由器本体createRouter、控制器、中间件类型、注册路由remix/routes声明式路由构建器route、get、post、put、del、form、resources用于定义app/routes.tsremix/node-fetch-server新应用的默认 Node 适配器server.ts中用createRequestListener配合node:http/node:https/node:http2启动remix/node-hmr可选开发期 Node HMR 运行器hmr.ts中用run监督server.tscreateHmrReadyFetch让稳定公共代理等待子服务器就绪remix/node-hmr/runtime子进程运行时 API创建浏览器 HMR 通道、发出服务器就绪事件remix/node-hmr/typesNode 模块中import.meta.hot的纯类型入口remix/assets浏览器资源服务器createAssetServer编译脚本/样式、生成 public href、发出 preload、接线浏览器 HMR。配置basePath用mounts扩展默认挂载默认app服务app、node_modules服务npmallowFiles/denyFiles做路径/glob 规则allowPackages按精确包名控制包级访问target、sourceMaps、minify等编译选项在顶层remix/headersSuperHeaders加类型化请求头解析/构建器默认导出获得带headers.contentType、headers.cacheControl、headers.setCookie的Headers子类CacheControl、ContentDisposition、Vary等具名类处理单值remix/response/redirectredirect(href, status?)POST then redirect 的规范形态remix/response/htmlcreateHtmlResponse不经remix/ui从字符串/流创建 HTMLResponseremix/response/compresscompressResponse在compression()中间件之外压缩一次性响应remix/response/file文件下载响应Content-Disposition: attachmentremix/route-pattern底层 URL 匹配与生成RoutePattern、createMatcher。href(...)自动编码 pathname 与搜索参数match(...)返回解码后的 paramsremix/fetch-proxy基于 Fetch 的 HTTP 代理转发请求到其他源xForwardedHeaders传递 proto/host/port默认重写被代理的Set-Cookie域/路径属性数据、校验与持久化子路径用途remix/data-schemaschema 构建器parse/parseSafe校验任何跨信任边界的输入.transform(...)把校验输出映射为其他值/类型remix/data-schema/checks常用 check 辅助email、minLength、maxLength等remix/data-schema/coerce字符串/数字/布尔/日期/id 强转辅助输入是字符串但应为类型化值时使用remix/data-schema/form-dataf.object与f.field直接解析FormData读取浏览器表单的动作中使用remix/data-schema/lazy递归或互相引用的 schemaremix/data-table类型化表与共享DatabaseAPItable、column建模持久化数据再从匹配方言包创建具体数据库集成包可实现DatabaseDriver并扩展Databaseremix/data-table/sqlite、postgres、mysql具体方言集成createSqliteDatabase/createPostgresDatabase/createMysqlDatabase。SQLite 接受 Node、Bun 及兼容同步客户端共享prepare/exec表面remix/data-table/migrations迁移编写与注册表createMigration、createMigrationRegistry用Database.migrate()运行remix/data-table/migrations/node从磁盘loadMigrations启动脚本中应用迁移remix/data-table/operators查询操作符如inList(...)remix/data-table/sql-helpersSQL 辅助工具正常应用代码应避免除非有意在表/查询 API 之下工作认证、会话与 Cookie子路径用途remix/sessionSession对象get、set、flash、unset、regenerateId。任何篡改即 bug 的浏览器状态登录态、已提交表单、购物车、flash 消息都应使用它remix/middleware/sessionsession(cookie, storage)把会话 cookie 与存储后端接入中间件栈remix/session-storage/fs、memory、cookie存储后端fs-storage用于单进程应用memory-storage用于测试cookie-storage用于数据可放入 cookie 的无状态部署remix/session-storage/redis、memcache多进程/多主机部署的存储后端remix/cookiecreateCookie创建普通签名/未签名 cookie用于客户端可控制值的非敏感偏好主题、语言、已关闭的横幅篡改有风险的状态优先remix/sessionremix/auth凭据、OAuth 与 OIDC 提供者定义身份验证方式、启动/完成外部登录、用refreshExternalAuth(...)刷新存储的 OAuth/OIDC token 包remix/middleware/authauth({ schemes })、requireAuth、Auth上下文键把身份解析进请求上下文并门控路由UI、水合与浏览器行为子路径用途remix/ui组件运行时组件、核心 mixin、clientEntry、run、Frame、导航辅助、createRootremix/ui/server底层服务端渲染renderToStream、renderToString。普通应用安装remix/middleware/render的render()此子路径用于自定义管线与静态字符串渲染remix/ui-hmr直接 Remix UI 组件 HMR 转换仅自定义模块钩子或构建集成时使用remix/ui-hmr/nodeNode import hook开发服务器经remix/node-hmr运行时配合--import remix/ui-hmr/noderemix/ui-hmr/assetsremix/assets的 UI 组件 HMR loader开发期在createAssetServer({ scripts: { loaders } })中用uiHmr()remix/ui/animation动画 APIanimateEntrance、animateExit、animateLayout、spring、tween、easingsremix/ui/primitiveUI 原语与 mixin当前包括accordion、anchor、button、checkbox、combobox、input、listbox、menu、popover、selectremix/ui/test组件测试渲染辅助如renderremix/ui/jsx-runtime、jsx-dev-runtimeJSX 转换目标在tsconfig.json配置极少直接导入remix/html-template转义 HTML 模板字面量组件系统之外生成 HTML 时使用RSS、邮件正文、错误页remix/file-storage后端无关的File存储接口上传目的地的类型约束remix/file-storage/fs、memory、s3上传目的地存储后端中间件子路径用途remix/middleware/renderrender({ assets?, onError? })标准 Remix UI 渲染器renderWith(factory)自定义请求作用域渲染器。普通 UI 动作返回context.render(node, init)remix/middleware/staticstaticFiles(dir)按磁盘原样提供public/文件remix/middleware/form-dataformData()解析一次FormData经get(FormData)暴露替代每个动作中重复await request.formData()remix/form-data-parser底层parseFormData、FileUpload自定义上传处理器。上传处理器错误直接传播remix/multipart-parser、/node底层 multipart 流解析。MultipartPart.headers是以小写头名为键的普通对象用括号表示法读取如part.headers[content-type]remix/middleware/compressioncompression()用于文本类响应remix/middleware/loggerlogger()开发期请求日志colors强制开关终端颜色remix/middleware/method-overridemethodOverride()HTML 表单需要PUT/PATCH/DELETE时使用remix/middleware/async-contextasyncContext()、getContext()actions/控制器之外的辅助需要请求上下文且不想逐层传参时使用remix/middleware/corscors(opts?)跨源调用端点remix/middleware/csrfcsrf(opts?)会话背书的表单变更状态需要同步器令牌 CSRF 保护时使用remix/middleware/cop跨源保护拒绝不安全的跨源浏览器请求测试与工具子路径用途remix/testdescribe、it与生命周期钩子测试框架本体remix/test/cli程序化测试运行器 API如runRemixTestremix/node-fetch-server/testcreateTestServer围绕 Fetch 处理器构建真实本地 HTTP 服务器的端到端测试remix/cli程序化 Remix CLI APIremix可执行文件提供remix test、remix routes、remix doctor、remix version等项目命令remix/assert断言辅助替代node:assert让消息在运行器中干净呈现remix/terminalANSI 样式、颜色检测、样式工厂与可测试终端流CLI 与终端输出专用remix/fs小型文件系统辅助openLazyFile、writeFileremix/lazy-fileLazyFile原语与字节区间辅助文件/区间响应实现remix/mimecontent-type 与 MIME 检测替代应用局部扩展名映射表remix/tar-parser流式 tar 解析消费 tar 归档的导入/导出工具常见错误清单Common Mistakes To Avoid技能文档总结了 Remix 开发中最常见的错误逐一对照自查可避免大量返工把 Remix Component 当 React 用去追求 hooks 或隐式重渲染从顶层remix入口导入而不是子路径在服务端渲染路由行为正确之前就添加clientEntry(...)向clientEntry(...)传入不可序列化 props中间件栈中没有asyncContext()却调用getContext()中间件顺序错误静态文件等快速退出应在前请求增强在后跳过边界校验信任原始FormData、params、cookies 或外部 payload让路由局部领域错误泄漏出控制器——期望结果校验、冲突、未找到应翻译为路由意图返回的 HTTPResponse而非抛出自定义Error再在别处捕获需要防篡改或服务端管理的浏览器状态时却用createCookie——编辑该值即 bug 的状态应使用remix/session普通表单 POST、重定向或资源路由更简单时却构建纯 JSON RPC 层——客户端 fetch 是健全路由行为之上的层次不是替代品把 JSON 状态端点与Frame重载视为互斥模式——选择与 UX 匹配的最轻同步机制小部件轮询 JSON 端点也合理认为认证足够、忽略按资源授权检查共享代码落入utils.ts、helpers.ts、common.ts等模糊桶而所有权已知重建旧的app/controllers或独立根动作文件布局而不是用app/actions下的控制器把嵌套路由映射键放进控制器actions对象——嵌套路由映射在app/router.ts显式映射把直接router.get(...)/router.post(...)注册当作默认应用结构而不是使用控制器假设一个控制器注册的中间件会应用到嵌套路由映射注册的控制器特性主要行为是 HTTP 路由关注点时只写组件测试实践路径从模板开始仓库提供了可直接运行的 template 模板项目其package.json、server.ts、hmr.ts与app/结构展示了完整应用的最小骨架技能文档建议启动模板风格应用时在server.ts中用createRequestListener配合node:http/node:https/node:http2。更完整的业务示例可参考仓库中的多个 demo如 demos/bookstore它们在app/actions、app/middleware、app/data的组织上与本技能文档完全一致是阅读真实控制器与中间件的良好素材。遵循本文所述的分层、工作流与包地图你可以构建出结构一致、类型安全、无需 JavaScript 即可工作的服务端优先 Remix 应用并在需要时平滑叠加浏览器交互、会话认证与完整测试覆盖。【免费下载链接】remixThe fully-stacked web framework项目地址: https://gitcode.com/GitHub_Trending/re/remix创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门