Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions packages/next/build/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,14 +160,10 @@ export function createEntrypoints(
name: '[name].js',
value: `next-middleware-ssr-loader?${stringify({
page,
absoluteAppPath: pages['/_app'],
absoluteDocumentPath: pages['/_document'],
absoluteErrorPath: pages['/500'] || pages['/_error'],
absolute500Path: pages['/500'] || '',
absolutePagePath,
isServerComponent: isFlight,
buildId,
basePath: config.basePath,
assetPrefix: config.assetPrefix,
...defaultServerlessOptions,
} as any)}!`,
isServer: false,
isServerWeb: true,
Expand Down
186 changes: 44 additions & 142 deletions packages/next/build/webpack/loaders/next-middleware-ssr-loader/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,159 +5,61 @@ export default async function middlewareRSCLoader(this: any) {
absolutePagePath,
absoluteAppPath,
absoluteDocumentPath,
absolute500Path,
absoluteErrorPath,
basePath,
isServerComponent: isServerComponentQuery,
assetPrefix,
buildId,
isServerComponent,
...restRenderOpts
} = this.getOptions()

const isServerComponent = isServerComponentQuery === 'true'
const stringifiedAbsolutePagePath = stringifyRequest(this, absolutePagePath)
const stringifiedAbsoluteAppPath = stringifyRequest(this, absoluteAppPath)
const stringifiedAbsoluteErrorPath = stringifyRequest(this, absoluteErrorPath)
const stringifiedAbsolute500PagePath = stringifyRequest(
this,
absolute500Path || absoluteErrorPath
)
const stringifiedAbsoluteDocumentPath = stringifyRequest(
this,
absoluteDocumentPath
)

const transformed = `
import { adapter } from 'next/dist/server/web/adapter'
import { RouterContext } from 'next/dist/shared/lib/router-context'
import { renderToHTML } from 'next/dist/server/web/render'

import App from ${stringifiedAbsoluteAppPath}
import Document from ${stringifiedAbsoluteDocumentPath}
const errorMod = require(${stringifiedAbsoluteErrorPath})

const {
default: Page,
config,
getStaticProps,
getServerSideProps,
getStaticPaths
} = require(${stringifiedAbsolutePagePath})

const buildManifest = self.__BUILD_MANIFEST
const reactLoadableManifest = self.__REACT_LOADABLE_MANIFEST
const rscManifest = self.__RSC_MANIFEST

if (typeof Page !== 'function') {
throw new Error('Your page must export a \`default\` component')
}

const Component = Page

async function render(request) {
const url = request.nextUrl
const { pathname, searchParams } = url
const query = Object.fromEntries(searchParams)

// Preflight request
if (request.method === 'HEAD') {
return new Response('OK.', {
headers: { 'x-middleware-ssr': '1' }
})
}

const renderServerComponentData = ${
isServerComponent ? `query.__flight__ !== undefined` : 'false'
}
delete query.__flight__

const req = { url: pathname }
const renderOpts = {
Component,
pageConfig: config || {},
// Locales are not supported yet.
// locales: i18n?.locales,
// locale: detectedLocale,
// defaultLocale,
// domainLocales: i18n?.domains,
dev: process.env.NODE_ENV !== 'production',
App,
Document,
buildManifest,
getStaticProps,
getServerSideProps,
getStaticPaths,
reactLoadableManifest,
buildId: ${JSON.stringify(buildId)},
assetPrefix: ${JSON.stringify(assetPrefix || '')},
env: process.env,
basePath: ${JSON.stringify(basePath || '')},
supportsDynamicHTML: true,
concurrentFeatures: true,
renderServerComponentData,
serverComponentManifest: ${
isServerComponent ? 'rscManifest' : 'null'
},
}

const transformStream = new TransformStream()
const writer = transformStream.writable.getWriter()
const encoder = new TextEncoder()
let result
let renderError
let statusCode = 200
try {
result = await renderToHTML(
req,
{},
pathname,
query,
renderOpts
)
} catch (err) {
renderError = err
statusCode = 500
}
if (renderError) {
try {
const errorRes = { statusCode, err: renderError }
result = await renderToHTML(
req,
errorRes,
'/_error',
query,
{
...renderOpts,
Component: errorMod.default,
getStaticProps: errorMod.getStaticProps,
getServerSideProps: errorMod.getServerSideProps,
getStaticPaths: errorMod.getStaticPaths,
}
)
} catch (err) {
return new Response(
(err || 'An error occurred while rendering ' + pathname + '.').toString(),
{
status: 500,
headers: { 'x-middleware-ssr': '1' }
}
)
}
}

result.pipe({
write: str => writer.write(encoder.encode(str)),
end: () => writer.close(),
// Not implemented: cork/uncork/on/removeListener
})

return new Response(transformStream.readable, {
headers: { 'x-middleware-ssr': '1' },
status: statusCode
})
}

export default function rscMiddleware(opts) {
return adapter({
...opts,
handler: render
})
}
`
import { adapter } from 'next/dist/server/web/adapter'
import { RouterContext } from 'next/dist/shared/lib/router-context'

import App from ${stringifiedAbsoluteAppPath}
import Document from ${stringifiedAbsoluteDocumentPath}

import { getRender } from 'next/dist/build/webpack/loaders/next-middleware-ssr-loader/render'

const pageMod = require(${stringifiedAbsolutePagePath})
const errorMod = require(${stringifiedAbsolute500PagePath})

const buildManifest = self.__BUILD_MANIFEST
const reactLoadableManifest = self.__REACT_LOADABLE_MANIFEST
const rscManifest = self.__RSC_MANIFEST

if (typeof pageMod.default !== 'function') {
throw new Error('Your page must export a \`default\` component')
}

const render = getRender({
App,
Document,
pageMod,
errorMod,
buildManifest,
reactLoadableManifest,
rscManifest,
isServerComponent: ${JSON.stringify(isServerComponent)},
restRenderOpts: ${JSON.stringify(restRenderOpts)}
})

export default function rscMiddleware(opts) {
return adapter({
...opts,
handler: render
})
}`

return transformed
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { NextRequest } from '../../../../server/web/spec-extension/request'
import { renderToHTML } from '../../../../server/web/render'
import RenderResult from '../../../../server/render-result'

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 url = request.nextUrl
const { pathname, searchParams } = url

const query = Object.fromEntries(searchParams)

// Preflight request
if (request.method === 'HEAD') {
return new Response('OK.', {
headers: { 'x-middleware-ssr': '1' },
})
}

const renderServerComponentData = isServerComponent
? query.__flight__ !== undefined
: false
delete query.__flight__

const req = { url: pathname }
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,
renderServerComponentData,
serverComponentManifest: isServerComponent ? rscManifest : null,
ComponentMod: null,
}

const transformStream = new TransformStream()
const writer = transformStream.writable.getWriter()
const encoder = new TextEncoder()

let result: RenderResult | null
try {
result = await renderToHTML(
req as any,
{} as any,
pathname,
query,
renderOpts
)
} catch (err: any) {
const errorRes = { statusCode: 500, err }
try {
result = await renderToHTML(
req as any,
errorRes as any,
'/_error',
query,
{
...renderOpts,
Component: errorMod.default,
getStaticProps: errorMod.getStaticProps,
getServerSideProps: errorMod.getServerSideProps,
getStaticPaths: errorMod.getStaticPaths,
}
)
} catch (err2: any) {
return new Response(
(
err2 || 'An error occurred while rendering ' + pathname + '.'
).toString(),
{
status: 500,
headers: { 'x-middleware-ssr': '1' },
}
)
}
}

if (!result) {
return new Response(
'An error occurred while rendering ' + pathname + '.',
{
status: 500,
headers: { 'x-middleware-ssr': '1' },
}
)
}

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: { 'x-middleware-ssr': '1' },
status: 200,
})
}
}
6 changes: 6 additions & 0 deletions packages/next/server/dev/hot-reloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -529,11 +529,17 @@ export default class HotReloader {
absoluteAppPath: this.pagesMapping['/_app'],
absoluteDocumentPath: this.pagesMapping['/_document'],
absoluteErrorPath: this.pagesMapping['/_error'],
absolute404Path: this.pagesMapping['/404'] || '',
absolutePagePath,
isServerComponent,
buildId: this.buildId,
basePath: this.config.basePath,
assetPrefix: this.config.assetPrefix,
generateEtags: this.config.generateEtags,
poweredByHeader: this.config.poweredByHeader,
canonicalBase: this.config.amp.canonicalBase,
i18n: this.config.i18n,
previewProps: this.previewProps,
} as any)}!`,
isServer: false,
isServerWeb: true,
Expand Down