.server
模块仅在服务器上运行,并从客户端包中排除的仅服务器模块。
// This would expose secrets on the client if not exported from a server-only module
export const JWT_SECRET = process.env.JWT_SECRET;
export function validateToken(token: string) {
// Server-only authentication logic
}
.server
模块是明确将整个模块标记为仅限服务器使用的一种好方法。如果 .server
文件或 .server
目录中的任何代码意外地出现在客户端模块图中,构建将会失败。
路由模块不应标记为 .server
或 .client
,因为它们有特殊处理,需要在服务器和客户端模块图中引用。尝试这样做会导致构建错误。
如果您需要对客户端/服务器包中包含的内容进行更精细的控制,请查看 vite-env-only
插件。
通过在文件名中添加 .server
将单个文件标记为仅服务器使用
app/
├── auth.server.ts 👈 server-only file
├── database.server.ts
├── email.server.ts
└── root.tsx
通过在目录名称中使用 .server
将整个目录标记为仅服务器使用
app/
├── .server/ 👈 entire directory is server-only
│ ├── auth.ts
│ ├── database.ts
│ └── email.ts
├── components/
└── root.tsx
import { PrismaClient } from "@prisma/client";
// This would expose database credentials on the client
const db = new PrismaClient({
datasources: {
db: {
url: process.env.DATABASE_URL,
},
},
});
export { db };
import jwt from "jsonwebtoken";
import bcrypt from "bcryptjs";
const JWT_SECRET = process.env.JWT_SECRET!;
export function hashPassword(password: string) {
return bcrypt.hash(password, 10);
}
export function verifyPassword(
password: string,
hash: string
) {
return bcrypt.compare(password, hash);
}
export function createToken(userId: string) {
return jwt.sign({ userId }, JWT_SECRET, {
expiresIn: "7d",
});
}
export function verifyToken(token: string) {
return jwt.verify(token, JWT_SECRET) as {
userId: string;
};
}
import type { ActionFunctionArgs } from "react-router";
import { redirect } from "react-router";
import {
hashPassword,
createToken,
} from "../utils/auth.server";
import { db } from "../utils/db.server";
export async function action({
request,
}: ActionFunctionArgs) {
const formData = await request.formData();
const email = formData.get("email") as string;
const password = formData.get("password") as string;
// Server-only operations
const hashedPassword = await hashPassword(password);
const user = await db.user.create({
data: { email, password: hashedPassword },
});
const token = createToken(user.id);
return redirect("/dashboard", {
headers: {
"Set-Cookie": `token=${token}; HttpOnly; Secure; SameSite=Strict`,
},
});
}
export default function Login() {
return (
<form method="post">
<input name="email" type="email" required />
<input name="password" type="password" required />
<button type="submit">Login</button>
</form>
);
}