Terminology
App Router → New routing system using /app (Next.js 13+)
Pages Router → Legacy routing using /pages
SSR → Server-Side Rendering
SSG → Static Site Generation
ISR → Incremental Static Regeneration
CSR → Client-Side Rendering
RSC → React Server Components
SPA → Single Page Application
Beginning of everything: renderImpl
Source code path: packages/next/src/server/render.tsx
https://github.com/vercel/next.js
handleRequest
↓
handleRequestImpl
↓
handleCatchallRenderRequest
↓
render
↓
// response html according to client end's request.
async renderImpl(req, res, pathname, query = {}, parsedUrl, internalRender = false) {
return this.pipe((ctx) => this.renderToResponse(ctx), {
req, // request instance
res, // response instance
pathname, // request page path from client end, '/page/a', '/page/c', etc
query // params in request url, '?start=2&side=10'.
});
}
↓
renderToResponse
↓
renderToResponseImpl
↓
renderToResponseImpl
↓
const result = await this.renderPageComponent(
{
...ctx,
pathname: match.definition.pathname, // serialize current request path, and get coordinating Component,
renderOpts: {
...ctx.renderOpts,
params: match.params,
},
},
bubbleNoFallback
)
↓
renderPageComponent
↓
return await this.renderToResponseWithComponents(ctx, result);
↓
renderToResponseWithComponentsImpl
↓
await components.ComponentMod.handler(handlerReq, handlerRes, {
waitUntil: this.getWaitUntil()
});
get page component findPageComponents
The rendering of a NextJs Page start with a path, Let's reusing the example on NextJs Doc website for demonstration,

it might be "/page", it will trigger renderImpl to analyze its path, and find target component path in renderPageComponent, invoke the function findPageComponentsImpl at packages/next/src/server/next-server.ts
protected async renderPageComponent(
ctx: RequestContext<ServerRequest, ServerResponse>
) {
// omitted.........
const { query, pathname } = ctx
const appPaths = this.getOriginalAppPaths(pathname)
let page = pathname
const result = await this.findPageComponents({ // get coordinate component by current request path.
locale: getRequestMeta(ctx.req, 'locale'),
page,
query,
params: ctx.renderOpts.params || {},
// ................
})
if (result) {
return await this.renderToResponseWithComponents(ctx, result) // render component and set result on response instance.
}
return false
}
It will keeping invoke function loadComponentsImpl at packages/next/src/server/load-components.ts.
we can see NextJs built server-side bundles under .next, it will be files in dev/server/app that request files under /chunks/ssr(server-side-only) to consist of a intact resource.
using component to sendRenderResult
After found target page component, lest keeping on plumbing logic of renderToResponseWithComponents
renderToResponseWithComponents > renderToResponseWithComponentsImpl
private async renderToResponseWithComponentsImpl()
{
// omitted.................
// propagate the request context for dev
setRequestMeta(request, getRequestMeta(req))
addRequestMeta(request, 'distDir', this.distDir)
addRequestMeta..... omitted
// use previous required component to render result on response instance
await components.ComponentMod.handler(handlerReq, handlerRes, {
waitUntil: this.getWaitUntil(),
})
}
handler is locate at packages\next\src\build\templates\app-page.ts
export async function handler(){
const prepareResult = await routeModule.prepare(req, res, {
srcPage,
multiZoneDraftMode,
})
// omitted..............
await handleResponse(activeSpan)
}
We can just focus on handleResponse
const handleResponse = () => {
const cacheEntry = await routeModule.handleResponse({
cacheKey: ssgCacheKey,
responseGenerator: (c) =>
responseGenerator({
span,
...c,
}),
routeKind: RouteKind.APP_PAGE,
prerenderManifest,
// omitted...........
})
const { value: cachedData } = cacheEntry;
// This is a request for HTML data.
const body = cachedData.html;
// If there's no postponed state, we should just serve the HTML. This
// should also be the case for a resume request because it's completed
// as a server render (rather than a static render).
if (!didPostpone || isMinimalMode || isRSCRequest) {
// normally, for a PPR request, it will come to there to sendRenderResult.
return sendRenderResult({
req,
res,
generateEtags: nextConfig.generateEtags,
poweredByHeader: nextConfig.poweredByHeader,
result: body,
cacheControl: cacheEntry.cacheControl,
})
}
}
As we can see, handleResponse will require cached data from routeModule, which would be reused in subsequent sendRenderResult
Summary
We traced the Next.js render pipeline starting from the server request handler down to renderImpl. We followed how Next.js transforms an incoming HTTP request into a rendered result by resolving page components, executing the rendering logic, and finally returning HTML through sendRenderResult.
Next post we will still focus on NextJS codebase, could pay attention if interested, and please feel free to point out any errors or omissions.






Top comments (0)