Use DI in Server Components
Use createNextDi() when Server Components in one render pass should share
scoped dependencies. This guide assumes an App Router project and a server-only
composition module.
-
Create the server composition module
Pass React’s
cachefunction to the adapter. React owns render memoization;di-craftowns the dependency graph.app/di.server.ts import "server-only";import { cache } from "react";import { createNextDi } from "di-craft/next/server";export const { getRequestContainer } = createNextDi({cache,providers,requestProviders,}); -
Resolve at the Server Component boundary
import { getRequestContainer } from "./di.server";export default async function Page() {const users = await getRequestContainer().get(USERS_SERVICE).list();return <UsersView users={users} />;} -
Reuse the scope in nested Server Components
Repeated calls to
getRequestContainer()return the same child container during the current render pass.
import { createToken, provideFactory, provideValue, Scopes } from "di-craft";import { createNextDi } from "di-craft/next/server";
type RequestCache = <T>(factory: () => T) => () => T;
// Typed docs use a tiny cache stand-in so this file can be checked without// installing React. In a real Next app, pass `cache` from "react".const createRequestCache = (): RequestCache => { let cached: unknown; let hasCachedValue = false;
return (factory) => { return () => { if (!hasCachedValue) { cached = factory(); hasCachedValue = true; }
return cached as ReturnType<typeof factory>; }; };};
type RequestContext = { readonly requestId: string;};
class UsersService { private readonly requestContext: RequestContext;
constructor(requestContext: RequestContext) { this.requestContext = requestContext; }
list(): readonly string[] { return ["Ada", "Grace", this.requestContext.requestId]; }}
const REQUEST_CONTEXT = createToken<RequestContext>("REQUEST_CONTEXT");const USERS_SERVICE = createToken<UsersService>("USERS_SERVICE");
const { getRequestContainer } = createNextDi({ cache: createRequestCache(), providers: [ provideFactory(USERS_SERVICE, { scope: Scopes.Scoped, deps: { requestContext: REQUEST_CONTEXT }, useFactory: ({ requestContext }) => new UsersService(requestContext), }), ], requestProviders: () => [ provideValue(REQUEST_CONTEXT, { requestId: crypto.randomUUID(), }), ],});
type ViewModel = { readonly title: string; readonly users: readonly string[]; readonly count: number;};
const UsersList = async (): Promise<readonly string[]> => { return getRequestContainer().get(USERS_SERVICE).list();};
const UsersCount = async (): Promise<number> => { return getRequestContainer().get(USERS_SERVICE).list().length;};
const Page = async (): Promise<ViewModel> => { const [users, count] = await Promise.all([UsersList(), UsersCount()]);
return { title: "Users", users, count, };};
await Page();For Route Handlers, Server Actions, transactions, temporary files, and similar resources, use an explicit request scope.