如何让DI容器在Next.js14 App Router服务端组件与服务端动作中可用
在Next.js中为服务端组件和服务端动作共享Awilix DI容器
要让服务端组件(Server Components)和服务端动作(Server Actions)都能访问请求作用域的Awilix容器,可通过**Next.js请求存储(Request Storage)**结合Express中间件实现,具体方案如下:
1. 启用请求存储
在next.config.js中开启Next.js的实验性请求存储功能:
const nextConfig = { experimental: { requestStorage: true, }, }; module.exports = nextConfig;
2. 改造自定义server.ts
在Express中间件中,将Awilix的请求作用域容器存入请求存储:
import express from 'express'; import next from 'next'; import { createContainer } from 'awilix'; import { scopePerRequest, getScope } from 'awilix-express'; import { configureVehicleProfileManagementContainer } from './features/vehicleProfileManagement/diContainerConfig'; import { requestStorage } from 'next/dist/client/components/request-storage'; const dev = process.env.NODE_ENV !== 'production'; const hostname = 'localhost'; const port = 3000; const app = next({ dev, hostname, port }); const handle = app.getRequestHandler(); // 初始化根容器 const container = createContainer({ strict: true }); configureVehicleProfileManagementContainer(container); const server = express(); // 启用请求作用域容器 server.use(scopePerRequest(container)); // 将请求作用域容器存入Next.js请求存储 server.use((req, res, next) => { const scope = getScope(req); if (scope) { requestStorage.set('di-container', scope); } next(); }); app.prepare().then(() => { server.get('*', (req, res) => handle(req, res)); server.listen(port, (err?: Error) => { if (err) throw err; console.log(`> Ready on http://${hostname}:${port}`); }); });
3. 封装容器获取工具
新建utils/di-utils.ts,统一容器获取逻辑:
import { requestStorage } from 'next/dist/client/components/request-storage'; import { AwilixContainer } from 'awilix'; // 通用获取容器方法(服务端组件、服务端动作均可调用) export function getDiContainer(): AwilixContainer { const container = requestStorage.get('di-container'); if (!container) throw new Error('DI容器未初始化,请检查中间件配置'); return container; }
4. 在服务端组件中使用
// app/vehicles/page.tsx import { getDiContainer } from '@/utils/di-utils'; export default async function VehiclePage() { const container = getDiContainer(); const vehicleService = container.resolve('vehicleService'); const vehicles = await vehicleService.getAllVehicles(); return ( <div> <h1>车辆列表</h1> <ul>{vehicles.map(v => <li key={v.id}>{v.name}</li>)}</ul> </div> ); }
5. 在服务端动作中使用
// app/actions/vehicleActions.ts 'use server'; import { getDiContainer } from '@/utils/di-utils'; export async function createVehicle(vehicleData: { name: string }) { const container = getDiContainer(); const vehicleService = container.resolve('vehicleService'); await vehicleService.createVehicle(vehicleData); }
稳定替代方案(无需实验性API)
如果不想依赖实验性功能,可采用请求ID映射:
- 在Express中间件生成唯一请求ID,存入请求头或Cookie
- 创建全局
Map<string, AwilixContainer>,将请求ID与容器关联 - 服务端组件通过
cookies()获取请求ID,从Map中取容器;服务端动作从request对象直接取容器或请求ID - 需在请求结束后清理Map中的过期条目,避免内存泄漏
内容的提问来源于stack exchange,提问作者James Hunter
相关产品推荐
相关产品推荐

