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;
  };
}

八、总结 #

本章我们学习了:

  1. entry.server中间件:请求和响应处理
  2. Loader中间件:认证、日志、错误处理
  3. 中间件组合:compose函数
  4. 请求上下文:传递和设置上下文
  5. 最佳实践:命名、错误处理、性能监控

核心要点:

  • 使用函数包装实现中间件
  • 组合多个中间件
  • 统一处理错误和日志
最后更新:2026-03-28