rsnext/packages/next/build/webpack/loaders/next-middleware-ssr-loader/render.ts
Shu Ding 99d4d6c5a4
Implement web server as the request handler for edge SSR (#33635)
(#31506 for context)

This PR implements the minimum viable web server on top of the Next.js base server, and integrates it into our middleware (edge) SSR runtime to handle all the requests.

This also addresses problems like missing dynamic routes support in our current handler.

Note that this is the initial implementation with the assumption that the web server is running under minimal mode. Also later we can refactor the `__server_context` environment to properly passing the context via the constructor or methods.
2022-01-26 06:22:11 +00:00

86 lines
2.3 KiB
TypeScript

import type { NextConfig } from '../../../../server/config-shared'
import { NextRequest } from '../../../../server/web/spec-extension/request'
import { toNodeHeaders } from '../../../../server/web/utils'
import WebServer from '../../../../server/web-server'
import { WebNextRequest, WebNextResponse } from '../../../../server/base-http'
const createHeaders = (args?: any) => ({
...args,
'x-middleware-ssr': '1',
'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
})
function sendError(req: any, error: Error) {
const defaultMessage = 'An error occurred while rendering ' + req.url + '.'
return new Response((error && error.message) || defaultMessage, {
status: 500,
headers: createHeaders(),
})
}
export function getRender({
Document,
isServerComponent,
config,
}: {
Document: any
isServerComponent: boolean
config: NextConfig
}) {
// Polyfilled for `path-browserify`.
process.cwd = () => ''
const server = new WebServer({
conf: config,
minimalMode: true,
})
const requestHandler = server.getRequestHandler()
return async function render(request: NextRequest) {
const { nextUrl: url, cookies, headers } = request
const { pathname, searchParams } = url
const query = Object.fromEntries(searchParams)
const req = {
url: pathname,
cookies,
headers: toNodeHeaders(headers),
}
// Preflight request
if (request.method === 'HEAD') {
return new Response(null, {
headers: createHeaders(),
})
}
// @TODO: We should move this into server/render.
if (Document.getInitialProps) {
const err = new Error(
'`getInitialProps` in Document component is not supported with `concurrentFeatures` enabled.'
)
return sendError(req, err)
}
const renderServerComponentData = isServerComponent
? query.__flight__ !== undefined
: false
const serverComponentProps =
isServerComponent && query.__props__
? JSON.parse(query.__props__)
: undefined
// Extend the context.
Object.assign((self as any).__server_context, {
renderServerComponentData,
serverComponentProps,
})
const extendedReq = new WebNextRequest(request)
const extendedRes = new WebNextResponse()
requestHandler(extendedReq, extendedRes)
return await extendedRes.toResponse()
}
}