news 2026/9/11 16:40:31

Remix 模板项目实战指南:最小化全栈应用的目录结构、核心模块与开发命令

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Remix 模板项目实战指南:最小化全栈应用的目录结构、核心模块与开发命令

Remix 模板项目实战指南:最小化全栈应用的目录结构、核心模块与开发命令

【免费下载链接】remixThe fully-stacked web framework项目地址: https://gitcode.com/GitHub_Trending/re/remix

本指南围绕本仓库template/目录下的官方 Starter 模板展开,系统讲解一个最小 Remix 应用从目录骨架、路由契约、资产管线到开发命令的完整形态。读者学完后,将掌握如何基于该模板快速起步一个全栈应用、如何按官方约定组织app/actions/app/router.ts、如何配置服务端资产管线,以及npm run devnpm run hmrnpm run start等命令背后的真实行为。

模板定位:一个可立即运行的最小 Remix 应用

template/README.md开篇即点明模板本质——"A minimal Remix application starter with a home page",即一个自带首页的最小 Remix 应用 Starter。它不是为了展示全部功能,而是给出官方认可的最小目录骨架文件职责划分,让新项目以此为基础"生长"(Growing The App)。

模板根目录结构如下:

template/ ├── app/ │ ├── actions/ │ │ ├── public/ # 浏览器运行时入口与交互组件 │ │ │ ├── entry.ts │ │ │ └── prompt-button.tsx │ │ ├── controller.tsx # 顶层路由 actions 的归属地 │ │ ├── document.tsx # HTML 文档壳(head、字体、入口脚本) │ │ └── home-page.tsx # 路由自带的首页 UI │ ├── assets.ts # 服务端资产管线(编译、HMR、预加载) │ ├── router.ts # 路由→处理器绑定 + 中间件装配 │ └── routes.ts # 共享路由契约(类型安全 href) ├── public/ │ └── favicon.svg # 静态文件,原样从应用根路径提供 ├── hmr.ts # 独立 HMR 代理进程入口 ├── server.ts # Node HTTP 服务入口 ├── package.json └── tsconfig.json

模板的全部文件均可在仓库的 template/ 目录中逐一查看,其中 template/README.md 是本文所述约定的原始出处。

Starter Shape:每个文件各司其职

README 用 7 条清单概括了模板的"标准形态",逐条对照源码展开如下。

1.app/actions/controller.tsx:顶层路由 actions 的归属地

README 指明该文件 "owns the top-level route actions"。其真实实现是:

// template/app/actions/controller.tsx import { createController } from 'remix/router' import { assets } from '../assets.ts' import { routes } from '../routes.ts' import { HomePage } from './home-page.tsx' export default createController(routes, { actions: { async assets(context) { return (await assets.fetch(context.request)) ?? new Response('Not Found', { status: 404 }) }, home(context) { return context.render(<HomePage />) }, }, })

createController的职责在 packages/fetch-router/src/lib/controller.ts 中有明确注释:它把路由树的叶子节点映射到 action,同时保留每个 action 的 params 与 request-context 类型契约。也就是说,routes里定义了哪些路由,controller 的actions就必须恰好覆盖哪些键——类型系统会在编译期兜底。这里home直接调用context.render(<HomePage />)完成服务端渲染。

2.app/actions/home-page.tsxapp/actions/document.tsx:路由自带的 UI

  • home-page.tsx是首页 UI,文件头注释写着 "Delete this file and put your own home page in app/actions/controller.tsx",即官方明确允许、甚至鼓励你删除它,把首页替换成自己的实现。
  • document.tsx导出Document组件,负责 HTML 壳:<html>metafavicon<title>(默认值来自模板占位符%%RMX_APP_DISPLAY_NAME_URI_COMPONENT%%,未被替换时回退为 "Remix App")、字体 preconnect,以及关键的入口脚本注入:
{entryPreloads.map((href) => ( <link key={href} rel="modulepreload" href={href} /> ))} <script type="module" src={entryHref}></script>

entryHrefentryPreloads均来自app/assets.ts,即浏览器入口的 URL 与预加载清单由服务端资产管线统一产出。

3.app/actions/public/:浏览器运行时入口与交互组件

该目录是浏览器端专属代码的约定位置,包含两个文件:

  • entry.ts:调用remix/uirun()启动浏览器运行时,并在import.meta.hot存在时订阅server:update事件,收到服务端更新后重新加载顶层 frame。
  • prompt-button.tsx:一个**独立水合(clientEntry)**的"复制提示词到剪贴板"按钮,组件头注释点明 "This component hydrates independently; the rest of the page stays static HTML"——页面的其余部分保持静态 HTML,只有这个交互按钮被单独激活,这也是 Remix 逐组件水合思路的落地示例。

4.app/routes.ts:共享路由契约与类型安全 href

// template/app/routes.ts import { get, route } from 'remix/routes' export const routes = route({ assets: get('/assets/*path'), home: '/', })

README 强调它 "defines the shared route contract used by server and browser modules for type-safe hrefs"。因为服务端与浏览器模块都从同一份routes.ts导入,路由名与路径模式是单一事实来源,拼写错误会在编译期暴露。

5.app/router.ts:路由→处理器绑定与标准渲染中间件

// template/app/router.ts import { createRouter, type MiddlewareContext } from 'remix/router' import { render } from 'remix/middleware/render' import { staticFiles } from 'remix/middleware/static' import controller from './actions/controller.tsx' import { assets } from './assets.ts' import { routes } from './routes.ts' const renderMiddleware = render({ assets }) type AppContext = MiddlewareContext<[typeof renderMiddleware]> declare module 'remix/router' { interface RouterTypes { context: AppContext } } export const router = createRouter<AppContext>({ middleware: [staticFiles('./public', { index: false }), renderMiddleware], }) router.map(routes, controller)

这里演示了两个官方推荐的标准中间件:

  • staticFiles('./public', { index: false }):将public/目录下的静态文件原样服务(index: false表示不做目录索引),对应 README 第 7 条 "Rootpublic/contains static files served unchanged from the app root"。
  • render({ assets }):标准 Remix UI 渲染器,把context.render()的结果渲染成完整 HTML;MiddlewareContext<[typeof renderMiddleware]>把渲染中间件追加的上下文类型化,再通过declare module注入全局RouterTypes.context,让所有 action 的context.render拥有类型。

6.app/assets.ts:服务端资产管线

// template/app/assets.ts import { createAssetServer } from 'remix/assets' import { uiHmr } from 'remix/ui-hmr/assets' const rootDir = process.cwd() const nodeEnv = process.env.NODE_ENV ?? 'development' const isDevelopment = nodeEnv === 'development' const isHmr = Boolean(isDevelopment && process.env.REMIX_NODE_HMR) export const assets = createAssetServer({ basePath: '/assets', rootDir, allowFiles: ['app/routes.ts', 'app/**/public/**'], allowPackages: ['remix'], denyFiles: ['app/**/*.test.*'], sourceMaps: isDevelopment ? 'external' : undefined, minify: !isDevelopment, watch: isDevelopment, hmr: isHmr ? async () => (await import('remix/node-hmr/runtime')).createBrowserHmrChannel() : undefined, scripts: { loaders: isHmr ? [uiHmr()] : undefined }, }) const entry = 'app/actions/public/entry.ts' export const entryHref = await assets.getHref(entry) export const entryPreloads = await assets.getPreloads(entry)

关键配置项一览(createAssetServer定义于 packages/assets/src/lib/asset-server.ts):

配置项模板取值含义
basePath/assets资产统一挂载的 URL 前缀
rootDirprocess.cwd()资产编译的工作根目录
allowFiles['app/routes.ts', 'app/**/public/**']允许被服务/编译的文件白名单(浏览器端代码约定放public/子目录)
allowPackages['remix']允许被引用的包白名单
denyFiles['app/**/*.test.*']测试文件禁止进入浏览器产物
sourceMaps开发环境'external'仅开发时产出外部 source map
minify生产环境为true仅生产时压缩
watch开发环境为true开发时监听文件变更
hmr/scripts.loaders仅 HMR 模式启用注入浏览器 HMR 通道与uiHmr()loader

7.public/:静态文件原样服务

public/favicon.svg是模板唯一静态资源,document.tsx中以/favicon.svg引用,由staticFiles中间件原样提供。

Growing The App:官方推荐的生长路径

README 的 "Growing The App" 一节给出了四条可操作的扩展约定,这是模板最有实战价值的部分:

  1. 顶层路由 actions 放app/actions/controller.tsx——新路由的处理器继续追加到现有 controller 的actions中。
  2. 嵌套路由地图需要自己的 actions 或中间件时,新建app/actions/<route-key>/controller.tsx——即按路由键划分子目录,每个子 controller 可声明自己的middlewareactions
  3. 确实需要时才新增目录,如app/data/test/——模板刻意不预置,避免空目录噪音;仓库中其他 demo 目录(如 demos/bookstore/、demos/timeboxer/)正是按此约定生长后的完整样例。
  4. 共享 UI 超过一个路由需要时才提升到app/ui/——保持就近维护,避免过早抽象。

入口与服务:server.ts 与 hmr.ts

标准服务进程server.ts

template/server.ts 使用node:http+createRequestListener把 Remix router 暴露为 HTTP 服务:

  • 端口默认44100,可用环境变量PORT覆盖;HMR 场景下还支持HMR_PROXY_PORT
  • 请求处理直接router.fetch(request),异常时返回 500;SIGINT/SIGTERM触发优雅关闭(server.close+closeAllConnections)。

开发期 HMR 代理hmr.ts

template/hmr.ts 是一个独立 HMR 代理进程,端口分配如下:

端口来源默认值用途
PORT(代理)44100HMR 代理对外端口
HMR_PORT代理端口 + 1浏览器 HMR 事件通道
APP_PORT事件端口 + 1实际应用进程端口

它用run('server.ts', ...)以额外参数(--import remix/node-tsx--import remix/ui-hmr/node)拉起应用进程,再通过createFetchProxy转发请求并注入xForwardedHeaders,配合createHmrReadyFetch实现"服务就绪前排队、就绪后转发",从而获得完整的服务端 + 浏览器端热更新体验。

命令速查:package.json 的六个脚本

README 的 Commands 一节给出的命令在 template/package.json 中有完整定义,逐条解析其真实行为:

npm i # 安装依赖(本仓库为 pnpm workspace,安装时执行 workspace 协议解析) npm run dev # NODE_ENV=development node --watch --import remix/node-tsx server.ts # → node --watch 监听改动自动重启;node-tsx 提供 TS 直接执行 npm run hmr # NODE_ENV=development node hmr.ts # → 启动 HMR 代理进程,获得浏览器/服务端热更新 npm run start # NODE_ENV=production node --import remix/node-tsx server.ts # → 生产模式启动(无 --watch,资产管线自动开启 minify) npm test # NODE_ENV=test node --import remix/node-tsx --test # → 使用 Node 内置 test runner 执行测试 npm run typecheck # tsc --noEmit → 全量类型检查(strict 模式)

几点实操要点:

  • Node 版本要求package.jsonengines声明node >= 24.3.0,这是模板的硬性前提(依赖node --watch、内置 test runner 与较新的--import链)。
  • 唯一运行时依赖dependenciesremix一个包(workspace 协议workspace:^);TypeScript 与@types/node走 devDependencies 的catalog:版本目录。
  • dev 与 hmr 的取舍dev简单可靠(进程级热重启),hmr体验更好但多一个代理进程;二选一即可。
  • 类型安全基础:template/tsconfig.json 采用strict: truemodule/moduleResolution: NodeNextjsx: react-jsx+jsxImportSource: remix/uiallowImportingTsExtensions+rewriteRelativeImportExtensions,这解释了模板源码里随处可见的import ... from '../assets.ts'(带.ts后缀的导入写法)能被正常编译。

从模板到真实项目:可复制的三条路径

  1. 本地快速起步:把template/复制为你的应用根目录,按npm i && npm run dev启动,浏览器访问http://localhost:44100即可看到首页;之后按 "Growing The App" 约定替换home-page.tsx、扩展routes.ts与 controller。
  2. 对照成熟样例学习增长路径:仓库 demos/ 下多个 demo 展示了模板约定的放大形态——demos/bookstore/ 有完整的数据层与中间件、demos/timeboxer/ 有数据库迁移与安全测试、demos/social-auth/ 有认证流程,可作为"模板长大后长什么样"的参照。
  3. 理解底层机制:controller 的类型约束见 packages/fetch-router/src/lib/controller.ts,资产管线的配置语义见 packages/assets/src/lib/asset-server.ts,渲染中间件见 packages/render-middleware/src。

小结

template/README.md虽短,却浓缩了 Remix 项目官方约定的最小骨架:app/actions/承载路由处理器、app/routes.ts提供类型安全路由契约、app/router.ts装配中间件、app/assets.ts统一管理浏览器资产,外加一套覆盖开发、HMR、生产、测试与类型检查的命令体系。以它为起点,配合 "Growing The App" 的生长约定,即可稳健地把最小应用扩展成完整全栈项目。

【免费下载链接】remixThe fully-stacked web framework项目地址: https://gitcode.com/GitHub_Trending/re/remix

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 16:37:38

车载显示盖板玻璃检测标准与关键技术解析

1. 车载显示盖板玻璃检测标准解读GB/T 46022-2025作为即将实施的新版国家标准&#xff0c;专门针对车载显示用盖板玻璃的质量检测提出了系统化要求。这个标准出台的背景是随着智能座舱和车载显示技术的快速发展&#xff0c;对前装显示器的可靠性要求越来越高。我在汽车电子行业…

作者头像 李华
网站建设 2026/9/11 16:35:02

Django协同过滤推荐系统全链路实现指南

简介&#xff1a;本资源是一套完整的高分Python毕业设计项目&#xff0c;面向计算机专业本科生及初学者&#xff0c;聚焦推荐系统核心算法实践&#xff0c;提供基于Django框架实现的协同过滤电影推荐系统源码与配套论文。项目已通过本地完整编译与功能验证&#xff0c;评审得分…

作者头像 李华
网站建设 2026/9/11 16:30:14

5分钟从零到一:OpenProject 开源项目管理软件快速上手

5分钟从零到一&#xff1a;OpenProject 开源项目管理软件快速上手 【免费下载链接】openproject OpenProject is the leading open source project management software for product, project and portfolio management. A powerful Jira alternative with agile planning, iss…

作者头像 李华