rsnext/packages/next/build/webpack/loaders/next-middleware-ssr-loader/render.ts
Jiachi Liu c5291406aa
Escape from next head in rsc _error page (#32624)
* Don't render `next/head` in _error for RSC
* Save an error in `__next_s` for client to be aware RSC rendering is errored
* Fix `req.url` when errored
* Refactor some changes in middleware ssr loader
2021-12-21 19:24:57 +00:00

162 lines
4.2 KiB
TypeScript

import { NextRequest } from '../../../../server/web/spec-extension/request'
import { renderToHTML } from '../../../../server/web/render'
import RenderResult from '../../../../server/render-result'
import { toNodeHeaders } from '../../../../server/web/utils'
const createHeaders = (args?: any) => ({
...args,
'x-middleware-ssr': '1',
})
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({
App,
Document,
pageMod,
errorMod,
rscManifest,
buildManifest,
reactLoadableManifest,
isServerComponent,
restRenderOpts,
}: {
App: any
Document: any
pageMod: any
errorMod: any
rscManifest: object
buildManifest: any
reactLoadableManifest: any
isServerComponent: boolean
restRenderOpts: any
}) {
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(),
})
}
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
delete query.__flight__
delete query.__props__
const renderOpts = {
...restRenderOpts,
// Locales are not supported yet.
// locales: i18n?.locales,
// locale: detectedLocale,
// defaultLocale,
// domainLocales: i18n?.domains,
dev: process.env.NODE_ENV !== 'production',
App,
Document,
buildManifest,
Component: pageMod.default,
pageConfig: pageMod.config || {},
getStaticProps: pageMod.getStaticProps,
getServerSideProps: pageMod.getServerSideProps,
getStaticPaths: pageMod.getStaticPaths,
reactLoadableManifest,
env: process.env,
supportsDynamicHTML: true,
concurrentFeatures: true,
// When streaming, opt-out the `defer` behavior for script tags.
disableOptimizedLoading: true,
renderServerComponentData,
serverComponentProps,
serverComponentManifest: isServerComponent ? rscManifest : null,
ComponentMod: null,
}
const transformStream = new TransformStream()
const writer = transformStream.writable.getWriter()
const encoder = new TextEncoder()
let result: RenderResult | null
let renderError: any
try {
result = await renderToHTML(
req as any,
{} as any,
pathname,
query,
renderOpts
)
} catch (err: any) {
console.error(
'An error occurred while rendering the initial result:',
err
)
const errorRes = { statusCode: 500, err }
renderError = err
try {
req.url = '/_error'
result = await renderToHTML(
req as any,
errorRes as any,
'/_error',
query,
{
...renderOpts,
err,
Component: errorMod.default,
getStaticProps: errorMod.getStaticProps,
getServerSideProps: errorMod.getServerSideProps,
getStaticPaths: errorMod.getStaticPaths,
}
)
} catch (err2: any) {
return sendError(req, err2)
}
}
if (!result) {
return sendError(req, new Error('No result returned from render.'))
}
result.pipe({
write: (str: string) => writer.write(encoder.encode(str)),
end: () => writer.close(),
// Not implemented: cork/uncork/on/removeListener
} as any)
return new Response(transformStream.readable, {
headers: createHeaders(),
status: renderError ? 500 : 200,
})
}
}