Remix项目结构 #
一、项目目录概览 #
一个标准的 Remix 项目结构如下:
text
my-remix-app/
├── app/ # 应用源代码
│ ├── components/ # 共享组件
│ ├── routes/ # 路由文件
│ ├── models/ # 数据模型
│ ├── services/ # 服务层
│ ├── utils/ # 工具函数
│ ├── styles/ # 样式文件
│ ├── root.tsx # 根组件
│ ├── entry.client.tsx # 客户端入口
│ └── entry.server.tsx # 服务端入口
├── public/ # 静态资源
├── build/ # 构建输出(gitignore)
├── node_modules/ # 依赖包
├── package.json # 项目配置
├── vite.config.ts # Vite配置
├── tsconfig.json # TypeScript配置
└── .gitignore # Git忽略配置
二、app目录详解 #
2.1 root.tsx - 根组件 #
根组件是应用的入口,定义了 HTML 文档结构:
tsx
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "@remix-run/react";
import type { LinksFunction } from "@remix-run/node";
export const links: LinksFunction = () => [
{ rel: "stylesheet", href: "/styles/global.css" },
];
export default function App() {
return (
<html lang="zh-CN">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<Outlet />
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
}
关键组件说明:
| 组件 | 作用 |
|---|---|
<Meta /> |
渲染页面元数据 |
<Links /> |
渲染样式表等资源链接 |
<Outlet /> |
渲染子路由内容 |
<Scripts /> |
加载JavaScript脚本 |
<ScrollRestoration /> |
恢复滚动位置 |
<LiveReload /> |
开发模式热更新 |
2.2 entry.client.tsx - 客户端入口 #
客户端入口文件负责hydration(注水):
tsx
import { RemixBrowser } from "@remix-run/react";
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";
startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<RemixBrowser />
</StrictMode>
);
});
2.3 entry.server.tsx - 服务端入口 #
服务端入口负责渲染HTML:
tsx
import { PassThrough } from "node:stream";
import type { AppLoadContext, EntryContext } from "@remix-run/node";
import { createReadableStreamFromReadable } from "@remix-run/node";
import { RemixServer } from "@remix-run/react";
import { isbot } from "isbot";
import { renderToPipeableStream } from "react-dom/server";
const ABORT_DELAY = 5_000;
export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext,
loadContext: AppLoadContext
) {
return isbot(request.headers.get("user-agent") || "")
? handleBotRequest(
request,
responseStatusCode,
responseHeaders,
remixContext
)
: handleBrowserRequest(
request,
responseStatusCode,
responseHeaders,
remixContext
);
}
三、routes目录详解 #
3.1 路由文件命名规则 #
| 文件名 | URL路径 | 说明 |
|---|---|---|
_index.tsx |
/ |
首页 |
about.tsx |
/about |
关于页面 |
posts._index.tsx |
/posts |
文章列表 |
posts.$id.tsx |
/posts/:id |
文章详情 |
posts.new.tsx |
/posts/new |
新建文章 |
admin._index.tsx |
/admin |
管理首页 |
admin.users.tsx |
/admin/users |
用户管理 |
_layout.tsx |
- | 布局组件(不生成URL) |
3.2 路由文件结构 #
一个完整的路由文件可以包含以下导出:
tsx
import type {
LoaderFunctionArgs,
ActionFunctionArgs,
MetaFunction,
LinksFunction,
} from "@remix-run/node";
import { json, redirect } from "@remix-run/node";
import { useLoaderData, Form, Link } from "@remix-run/react";
// 1. Meta - 页面元数据
export const meta: MetaFunction = () => {
return [
{ title: "页面标题" },
{ name: "description", content: "页面描述" },
];
};
// 2. Links - 资源链接
export const links: LinksFunction = () => {
return [
{ rel: "stylesheet", href: stylesUrl },
];
};
// 3. Loader - 数据加载
export async function loader({ params, request }: LoaderFunctionArgs) {
const data = await fetchData();
return json({ data });
}
// 4. Action - 数据变更
export async function action({ request, params }: ActionFunctionArgs) {
const formData = await request.formData();
await saveData(formData);
return redirect("/success");
}
// 5. ErrorBoundary - 错误边界
export function ErrorBoundary() {
const error = useRouteError();
return <div>错误: {error.message}</div>;
}
// 6. default - 页面组件
export default function Page() {
const { data } = useLoaderData<typeof loader>();
return <div>{data}</div>;
}
3.3 嵌套路由 #
使用目录结构创建嵌套路由:
text
app/routes/
├── posts._index.tsx # /posts
├── posts.$id.tsx # /posts/:id
├── posts.new.tsx # /posts/new
├── posts.edit.$id.tsx # /posts/:id/edit
└── posts/
└── comments.$id.tsx # /posts/comments/:id
3.4 布局路由 #
使用 _layout.tsx 创建布局:
text
app/routes/
├── _index.tsx # /
├── about.tsx # /about
├── admin._index.tsx # /admin
├── admin.users.tsx # /admin/users
└── admin.tsx # admin布局
admin.tsx 布局文件:
tsx
import { Outlet } from "@remix-run/react";
import AdminNav from "~/components/admin-nav";
export default function AdminLayout() {
return (
<div className="flex">
<AdminNav />
<main>
<Outlet />
</main>
</div>
);
}
四、其他目录 #
4.1 components目录 #
存放可复用的UI组件:
text
app/components/
├── Button.tsx
├── Card.tsx
├── Header.tsx
├── Footer.tsx
└── ui/
├── Input.tsx
├── Select.tsx
└── Modal.tsx
4.2 models目录 #
存放数据模型和数据访问逻辑:
text
app/models/
├── user.server.ts
├── post.server.ts
└── comment.server.ts
4.3 services目录 #
存放业务逻辑和服务:
text
app/services/
├── auth.server.ts
├── email.server.ts
└── storage.server.ts
4.4 utils目录 #
存放工具函数:
text
app/utils/
├── format.ts
├── validation.ts
└── constants.ts
五、public目录 #
存放静态资源:
text
public/
├── favicon.ico
├── images/
│ ├── logo.png
│ └── hero.jpg
└── fonts/
└── custom-font.woff2
访问方式:/images/logo.png
六、路径别名 #
Remix 默认支持 ~ 路径别名:
tsx
import Button from "~/components/Button";
import { getUser } from "~/models/user.server";
import { formatDate } from "~/utils/format";
七、环境变量 #
7.1 定义环境变量 #
创建 .env 文件:
text
DATABASE_URL="postgresql://..."
SESSION_SECRET="your-secret-key"
API_KEY="your-api-key"
7.2 使用环境变量 #
服务端代码可以直接访问:
tsx
export async function loader() {
const apiKey = process.env.API_KEY;
// ...
}
7.3 客户端环境变量 #
以 PUBLIC_ 开头的变量可以暴露给客户端:
text
PUBLIC_API_URL="https://api.example.com"
客户端使用:
tsx
const apiUrl = window.ENV.PUBLIC_API_URL;
八、配置文件 #
8.1 vite.config.ts #
Vite 构建配置:
typescript
import { defineConfig } from "vite";
import remix from "@remix-run/dev/vite";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
plugins: [remix(), tsconfigPaths()],
});
8.2 tsconfig.json #
TypeScript 配置:
json
{
"include": ["env.d.ts", "**/*.ts", "**/*.tsx"],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"types": ["@remix-run/node", "vite/client"],
"isolatedModules": true,
"esModuleInterop": true,
"jsx": "react-jsx",
"module": "ESNext",
"moduleResolution": "Bundler",
"resolveJsonModule": true,
"target": "ES2022",
"strict": true,
"noEmit": true,
"paths": {
"~/*": ["./app/*"]
}
}
}
九、最佳实践 #
9.1 文件命名规范 #
- 使用小写字母和连字符
- 路由文件使用点号分隔
- 服务端文件添加
.server.ts后缀
9.2 目录组织建议 #
text
app/
├── components/ # 共享组件
│ ├── ui/ # 基础UI组件
│ └── features/ # 功能组件
├── routes/ # 路由文件
├── models/ # 数据模型
├── services/ # 服务层
├── hooks/ # 自定义Hooks
├── utils/ # 工具函数
└── styles/ # 全局样式
9.3 关注点分离 #
.server.ts文件只在服务端运行- 客户端代码避免引入服务端依赖
- 使用 loader/action 处理敏感数据
十、总结 #
本章我们学习了:
- 项目结构:app、public、build等目录的作用
- 路由文件:命名规则和导出项
- 根组件:root.tsx 的结构和组件
- 配置文件:vite.config.ts 和 tsconfig.json
理解项目结构是开发的基础,让我们继续深入学习路由系统!
最后更新:2026-03-28