Remix中间件 #
一、中间件概述 #
Remix 没有传统的中间件系统,但可以通过 entry.server.tsx 和 loader/action 实现类似功能。
二、entry.server中间件 #
2.1 请求处理 #
tsx
import type { EntryContext } from "@remix-run/node";
import { RemixServer } from "@remix-run/react";
import { renderToString } from "react-dom/server";
export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
// 请求前处理
console.log(`[${new Date().toISOString()}] ${request.method} ${request.url}`);
// 添加安全头
responseHeaders.set("X-Content-Type-Options", "nosniff");
responseHeaders.set("X-Frame-Options", "DENY");
responseHeaders.set("X-XSS-Protection", "1; mode=block");
const markup = renderToString(
<RemixServer context={remixContext} url={request.url} />
);
responseHeaders.set("Content-Type", "text/html");
return new Response("<!DOCTYPE html>" + markup, {
status: responseStatusCode,
headers: responseHeaders,
});
}
2.2 响应处理 #
tsx
export default async function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
const response = await handleRequestInternal(
request,
responseStatusCode,
responseHeaders,
remixContext
);
// 响应后处理
console.log(`Response: ${response.status}`);
return response;
}
三、Loader中间件 #
3.1 认证中间件 #
tsx
export async function requireAuth(request: Request) {
const session = await getSession(request.headers.get("Cookie"));
const userId = session.get("userId");
if (!userId) {
throw redirect("/login");
}
return userId;
}
export async function loader({ request }: LoaderFunctionArgs) {
const userId = await requireAuth(request);
const data = await getData(userId);
return json({ data });
}
3.2 日志中间件 #
tsx
export function withLogging<T>(
loader: (args: LoaderFunctionArgs) => Promise<T>
) {
return async (args: LoaderFunctionArgs) => {
const start = Date.now();
try {
const result = await loader(args);
console.log(`[SUCCESS] ${args.request.url} (${Date.now() - start}ms)`);
return result;
} catch (error) {
console.error(`[ERROR] ${args.request.url}`, error);
throw error;
}
};
}
export const loader = withLogging(async ({ params }: LoaderFunctionArgs) => {
const data = await getData(params.id);
return json({ data });
});
3.3 错误处理中间件 #
tsx
export function withErrorHandling<T>(
loader: (args: LoaderFunctionArgs) => Promise<T>
) {
return async (args: LoaderFunctionArgs) => {
try {
return await loader(args);
} catch (error) {
if (error instanceof Response) {
throw error;
}
console.error("Loader error:", error);
throw new Response("服务器错误", { status: 500 });
}
};
}
四、组合中间件 #
4.1 中间件组合器 #
tsx
type LoaderMiddleware = (
next: (args: LoaderFunctionArgs) => Promise<Response>
) => (args: LoaderFunctionArgs) => Promise<Response>;
export function compose(...middlewares: LoaderMiddleware[]) {
return (
loader: (args: LoaderFunctionArgs) => Promise<Response>
): ((args: LoaderFunctionArgs) => Promise<Response>) => {
return middlewares.reduceRight(
(next, middleware) => middleware(next),
loader
);
};
}
4.2 使用组合中间件 #
tsx
const withAuth: LoaderMiddleware = (next) => async (args) => {
const userId = await requireAuth(args.request);
args.context = { ...args.context, userId };
return next(args);
};
const withLogging: LoaderMiddleware = (next) => async (args) => {
const start = Date.now();
const result = await next(args);
console.log(`${args.request.url}: ${Date.now() - start}ms`);
return result;
};
export const loader = compose(
withAuth,
withLogging
)(async ({ params, context }) => {
const data = await getData(params.id, context.userId);
return json({ data });
});
五、全局中间件 #
5.1 根Loader中间件 #
tsx
export async function loader({ request }: LoaderFunctionArgs) {
// 全局数据
const user = await getUser(request);
const settings = await getSettings();
const notifications = user ? await getNotifications(user.id) : [];
return json({ user, settings, notifications });
}
5.2 布局中间件 #
tsx
export async function loader({ request }: LoaderFunctionArgs) {
// 布局级中间件
const user = await requireUser(request);
const permissions = await getPermissions(user.id);
return json({ user, permissions });
}
六、请求上下文 #
6.1 传递上下文 #
tsx
export async function loader({ request, context }: LoaderFunctionArgs) {
// 从中间件获取上下文
const userId = context.userId;
const data = await getData(userId);
return json({ data });
}
6.2 设置上下文 #
tsx
export async function loader({ request, context }: LoaderFunctionArgs) {
const user = await getUser(request);
// 设置上下文供子路由使用
context.user = user;
return json({ user });
}
七、最佳实践 #
7.1 中间件命名 #
tsx
// 推荐
export const withAuth = ...
export const withLogging = ...
export const withRateLimit = ...
// 不推荐
export const auth = ...
export const logging = ...
7.2 错误处理 #
tsx
export function withErrorBoundary<T>(
loader: (args: LoaderFunctionArgs) => Promise<T>
) {
return async (args: LoaderFunctionArgs) => {
try {
return await loader(args);
} catch (error) {
// 记录错误
logError(error, args.request);
// 重新抛出或返回错误响应
if (error instanceof Response) throw error;
throw new Response("服务器错误", { status: 500 });
}
};
}
7.3 性能监控 #
tsx
export function withPerformance<T>(
loader: (args: LoaderFunctionArgs) => Promise<T>
) {
return async (args: LoaderFunctionArgs) => {
const start = performance.now();
const result = await loader(args);
const duration = performance.now() - start;
if (duration > 1000) {
console.warn(`Slow loader: ${args.request.url} took ${duration}ms`);
}
return result;
};
}
八、总结 #
本章我们学习了:
- entry.server中间件:请求和响应处理
- Loader中间件:认证、日志、错误处理
- 中间件组合:compose函数
- 请求上下文:传递和设置上下文
- 最佳实践:命名、错误处理、性能监控
核心要点:
- 使用函数包装实现中间件
- 组合多个中间件
- 统一处理错误和日志
最后更新:2026-03-28