Remix项目架构 #
一、推荐目录结构 #
text
app/
├── components/ # 共享组件
│ ├── ui/ # 基础UI组件
│ │ ├── Button.tsx
│ │ ├── Input.tsx
│ │ └── Modal.tsx
│ └── features/ # 功能组件
│ ├── SearchBar.tsx
│ └── UserMenu.tsx
├── routes/ # 路由文件
│ ├── _index.tsx
│ ├── posts._index.tsx
│ ├── posts.$id.tsx
│ └── admin.tsx
├── models/ # 数据模型
│ ├── user.server.ts
│ └── post.server.ts
├── services/ # 业务逻辑
│ ├── auth.server.ts
│ └── email.server.ts
├── utils/ # 工具函数
│ ├── format.ts
│ └── validation.ts
├── hooks/ # 自定义Hooks
│ ├── useUser.ts
│ └── useDebounce.ts
├── styles/ # 全局样式
│ ├── global.css
│ └── variables.css
├── root.tsx # 根组件
├── entry.client.tsx # 客户端入口
└── entry.server.tsx # 服务端入口
二、路由组织 #
2.1 按功能分组 #
text
app/routes/
├── _index.tsx
├── auth/
│ ├── login.tsx
│ ├── register.tsx
│ └── forgot-password.tsx
├── posts/
│ ├── _index.tsx
│ ├── $id.tsx
│ └── new.tsx
└── admin/
├── _index.tsx
├── users.tsx
└── settings.tsx
2.2 布局路由 #
text
app/routes/
├── _index.tsx
├── _layout.tsx # 公共布局
├── _layout.about.tsx
├── _layout.contact.tsx
├── admin.tsx # 管理布局
├── admin._index.tsx
└── admin.users.tsx
三、数据层组织 #
3.1 Model层 #
tsx
export interface User {
id: string;
email: string;
name: string;
}
export async function getUser(id: string): Promise<User | null> {
return db.user.findUnique({ where: { id } });
}
export async function getUserByEmail(email: string): Promise<User | null> {
return db.user.findUnique({ where: { email } });
}
export async function createUser(data: CreateUserInput): Promise<User> {
return db.user.create({ data });
}
3.2 Service层 #
tsx
import { getUserByEmail, createUser } from "~/models/user.server";
import { hash, compare } from "~/utils/crypto";
export async function registerUser(email: string, password: string) {
const existingUser = await getUserByEmail(email);
if (existingUser) {
throw new Error("用户已存在");
}
const hashedPassword = await hash(password);
return createUser({
email,
password: hashedPassword,
});
}
export async function authenticateUser(email: string, password: string) {
const user = await getUserByEmail(email);
if (!user) {
throw new Error("用户不存在");
}
const isValid = await compare(password, user.password);
if (!isValid) {
throw new Error("密码错误");
}
return user;
}
四、组件组织 #
4.1 组件结构 #
tsx
import type { LinksFunction } from "@remix-run/node";
import styles from "./Button.css?url";
export const links: LinksFunction = () => [
{ rel: "stylesheet", href: styles },
];
interface ButtonProps {
variant?: "primary" | "secondary";
size?: "sm" | "md" | "lg";
disabled?: boolean;
children: React.ReactNode;
}
export function Button({
variant = "primary",
size = "md",
disabled,
children,
}: ButtonProps) {
return (
<button
className={`btn btn-${variant} btn-${size}`}
disabled={disabled}
>
{children}
</button>
);
}
4.2 组件导出 #
tsx
export { Button } from "./Button";
export { Input } from "./Input";
export { Modal } from "./Modal";
五、工具函数 #
5.1 格式化工具 #
tsx
export function formatDate(date: Date | string): string {
return new Date(date).toLocaleDateString("zh-CN", {
year: "numeric",
month: "long",
day: "numeric",
});
}
export function formatCurrency(amount: number): string {
return new Intl.NumberFormat("zh-CN", {
style: "currency",
currency: "CNY",
}).format(amount);
}
5.2 验证工具 #
tsx
import { z } from "zod";
export const emailSchema = z.string().email("无效的邮箱地址");
export const passwordSchema = z.string()
.min(8, "密码至少8个字符")
.regex(/[A-Z]/, "密码需要包含大写字母");
export function validateEmail(email: string) {
return emailSchema.safeParse(email);
}
六、配置管理 #
6.1 环境变量 #
tsx
export const config = {
database: {
url: process.env.DATABASE_URL!,
},
session: {
secret: process.env.SESSION_SECRET!,
maxAge: 60 * 60 * 24 * 7,
},
email: {
host: process.env.EMAIL_HOST,
port: parseInt(process.env.EMAIL_PORT || "587"),
},
};
6.2 类型定义 #
tsx
declare global {
namespace NodeJS {
interface ProcessEnv {
DATABASE_URL: string;
SESSION_SECRET: string;
EMAIL_HOST: string;
EMAIL_PORT: string;
}
}
}
export {};
七、最佳实践 #
7.1 文件命名 #
- 组件:PascalCase (Button.tsx)
- 工具:camelCase (formatDate.ts)
- 路由:kebab-case (user-profile.tsx)
- 服务端:.server.ts 后缀
7.2 导入顺序 #
tsx
import { json, redirect } from "@remix-run/node";
import { useLoaderData, Form } from "@remix-run/react";
import { useState, useEffect } from "react";
import { getUser } from "~/models/user.server";
import { formatDate } from "~/utils/format";
import { Button } from "~/components/ui/Button";
7.3 代码注释 #
tsx
/**
* 获取用户信息
* @param userId 用户ID
* @returns 用户信息或null
*/
export async function getUser(userId: string) {
// ...
}
八、总结 #
本章我们学习了:
- 目录结构:推荐的项目组织方式
- 路由组织:按功能和布局分组
- 数据层:Model和Service分离
- 组件组织:组件结构和导出
- 最佳实践:命名、导入、注释
核心要点:
- 保持清晰的目录结构
- 分离数据层和业务逻辑
- 统一命名规范
最后更新:2026-03-28