Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use the serverless page handler in the web runtime #31807

Closed
wants to merge 13 commits into from
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { stringifyRequest } from '../../stringify-request'

export default async function middlewareRSCLoader(this: any) {
export default async function middlewareSSRLoader(this: any) {
const {
absolutePagePath,
absoluteAppPath,
Expand All @@ -27,10 +27,10 @@ export default async function middlewareRSCLoader(this: any) {
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 documentMod = require(${stringifiedAbsoluteDocumentPath})
const pageMod = require(${stringifiedAbsolutePagePath})
const errorMod = require(${stringifiedAbsolute500PagePath})

Expand All @@ -44,7 +44,7 @@ export default async function middlewareRSCLoader(this: any) {

const render = getRender({
App,
Document,
documentMod,
pageMod,
errorMod,
buildManifest,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import escapeRegexp from 'next/dist/compiled/escape-string-regexp'

shuding marked this conversation as resolved.
Show resolved Hide resolved
import { NextRequest } from '../../../../server/web/spec-extension/request'
import { getPageHandler } from '../next-serverless-loader/page-handler'
import { isDynamicRoute } from '../../../../shared/lib/router/utils'
import { __ApiPreviewProps } from '../../../../server/api-utils'
import {
WebIncomingMessage,
WebServerResponse,
} from '../../../../server/web/http-adapter'
import { renderToHTML } from '../../../../server/web/render'
import RenderResult from '../../../../server/render-result'

Expand All @@ -9,7 +18,7 @@ const createHeaders = (args?: any) => ({

export function getRender({
App,
Document,
documentMod,
pageMod,
errorMod,
rscManifest,
Expand All @@ -19,7 +28,7 @@ export function getRender({
restRenderOpts,
}: {
App: any
Document: any
documentMod: any
pageMod: any
errorMod: any
rscManifest: object
Expand All @@ -28,8 +37,14 @@ export function getRender({
isServerComponent: boolean
restRenderOpts: any
}) {
const { page, buildId, previewProps, runtimeConfig } = restRenderOpts

const pageIsDynamicRoute = isDynamicRoute(page)
const escapedBuildId = escapeRegexp(buildId)
const encodedPreviewProps = JSON.parse(previewProps) as __ApiPreviewProps

return async function render(request: NextRequest) {
const { nextUrl: url, cookies, headers } = request
const { nextUrl: url } = request
const { pathname, searchParams } = url

const query = Object.fromEntries(searchParams)
Expand All @@ -46,11 +61,6 @@ export function getRender({
: false
delete query.__flight__

const req = {
url: pathname,
cookies,
headers,
}
const renderOpts = {
...restRenderOpts,
// Locales are not supported yet.
Expand All @@ -60,7 +70,7 @@ export function getRender({
// domainLocales: i18n?.domains,
dev: process.env.NODE_ENV !== 'production',
App,
Document,
Document: documentMod.default,
buildManifest,
Component: pageMod.default,
pageConfig: pageMod.config || {},
Expand All @@ -76,25 +86,69 @@ export function getRender({
ComponentMod: null,
}

const transformStream = new TransformStream()
const writer = transformStream.writable.getWriter()
const encoder = new TextEncoder()
const pageHandler = getPageHandler({
pageModule: pageMod,
pageComponent: pageMod.default,
pageConfig: pageMod.config || {},
appModule: App,
documentModule: documentMod,
errorModule: errorMod,
// notFoundModule: ${
// absolute404Path
// ? `require(${stringifyRequest(this, absolute404Path)})`
// : undefined
// },
shuding marked this conversation as resolved.
Show resolved Hide resolved
pageGetStaticProps: pageMod.getStaticProps,
pageGetStaticPaths: pageMod.getStaticPaths,
pageGetServerSideProps: pageMod.getServerSideProps,

assetPrefix: restRenderOpts.assetPrefix,
canonicalBase: restRenderOpts.canonicalBase,
generateEtags: restRenderOpts.generateEtags || false,
poweredByHeader: restRenderOpts.poweredByHeader || false,

runtimeConfig,
buildManifest,
reactLoadableManifest,

// rewrites: combinedRewrites,
shuding marked this conversation as resolved.
Show resolved Hide resolved
rewrites: [],
i18n: restRenderOpts.i18n,
page,
buildId,
escapedBuildId: escapedBuildId,
basePath: restRenderOpts.basePath,
pageIsDynamic: pageIsDynamicRoute,
encodedPreviewProps,
distDir: restRenderOpts.distDir,
})

const req = new WebIncomingMessage(request)
const res = new WebServerResponse()
let result: null | string | RenderResult = null
let statusCode = 200

let result: RenderResult | null
try {
result = await renderToHTML(
req as any,
{} as any,
pathname,
query,
renderOpts
const rendered = await pageHandler.renderReqToHTML(
req,
res,
'passthrough',
{
supportsDynamicHTML: true,
...renderOpts,
}
)
} catch (err: any) {
const errorRes = { statusCode: 500, err }
if (typeof rendered === 'string') {
result = rendered
} else if (rendered) {
result = rendered.html
}
} catch (err) {
statusCode = 500
try {
result = await renderToHTML(
req as any,
errorRes as any,
{ statusCode: 500, err } as any,
'/_error',
query,
{
Expand All @@ -118,6 +172,17 @@ export function getRender({
}
}

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

if (typeof result === 'string') {
return new Response(result, {
headers: { 'x-middleware-ssr': '1' },
shuding marked this conversation as resolved.
Show resolved Hide resolved
status: statusCode,
})
}

if (!result) {
return new Response(
'An error occurred while rendering ' + pathname + '.',
Expand All @@ -136,7 +201,7 @@ export function getRender({

return new Response(transformStream.readable, {
headers: createHeaders(),
status: 200,
status: statusCode,
})
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import getRouteNoAssetPath from '../../../../shared/lib/router/utils/get-route-f
import { PERMANENT_REDIRECT_STATUS } from '../../../../shared/lib/constants'
import RenderResult from '../../../../server/render-result'
import isError from '../../../../lib/is-error'
import {
WebIncomingMessage,
WebServerResponse,
} from '../../../../server/web/http-adapter'

export function getPageHandler(ctx: ServerlessHandlerCtx) {
const {
Expand Down Expand Up @@ -58,8 +62,8 @@ export function getPageHandler(ctx: ServerlessHandlerCtx) {
} = getUtils(ctx)

async function renderReqToHTML(
req: IncomingMessage,
res: ServerResponse,
req: IncomingMessage | WebIncomingMessage,
res: ServerResponse | WebServerResponse,
renderMode?: 'export' | 'passthrough' | true,
_renderOpts?: any,
_params?: any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ import cookie from 'next/dist/compiled/cookie'
import { TEMPORARY_REDIRECT_STATUS } from '../../../../shared/lib/constants'
import { NextConfig } from '../../../../server/config'
import { addRequestMeta } from '../../../../server/request-meta'
import {
WebIncomingMessage,
WebServerResponse,
} from '../../../../server/web/http-adapter'

const getCustomRouteMatcher = pathMatch(true)

Expand Down Expand Up @@ -85,7 +89,10 @@ export function getUtils({
defaultRouteMatches = dynamicRouteMatcher(page) as ParsedUrlQuery
}

function handleRewrites(req: IncomingMessage, parsedUrl: UrlWithParsedQuery) {
function handleRewrites(
req: IncomingMessage | WebIncomingMessage,
huozhi marked this conversation as resolved.
Show resolved Hide resolved
parsedUrl: UrlWithParsedQuery
) {
for (const rewrite of rewrites) {
const matcher = getCustomRouteMatcher(rewrite.source)
let params = matcher(parsedUrl.pathname)
Expand Down Expand Up @@ -150,15 +157,18 @@ export function getUtils({
return parsedUrl
}

function handleBasePath(req: IncomingMessage, parsedUrl: UrlWithParsedQuery) {
function handleBasePath(
req: IncomingMessage | WebIncomingMessage,
parsedUrl: UrlWithParsedQuery
) {
// always strip the basePath if configured since it is required
req.url = req.url!.replace(new RegExp(`^${basePath}`), '') || '/'
parsedUrl.pathname =
parsedUrl.pathname!.replace(new RegExp(`^${basePath}`), '') || '/'
}

function getParamsFromRouteMatches(
req: IncomingMessage,
req: IncomingMessage | WebIncomingMessage,
renderOpts?: any,
detectedLocale?: string
) {
Expand Down Expand Up @@ -269,7 +279,10 @@ export function getUtils({
return pathname
}

function normalizeVercelUrl(req: IncomingMessage, trustQuery: boolean) {
function normalizeVercelUrl(
req: IncomingMessage | WebIncomingMessage,
trustQuery: boolean
) {
// make sure to normalize req.url on Vercel to strip dynamic params
// from the query which are added during routing
if (pageIsDynamic && trustQuery && defaultRouteRegex) {
Expand Down Expand Up @@ -345,8 +358,8 @@ export function getUtils({
}

function handleLocale(
req: IncomingMessage,
res: ServerResponse,
req: IncomingMessage | WebIncomingMessage,
res: ServerResponse | WebServerResponse,
parsedUrl: UrlWithParsedQuery,
routeNoAssetPath: string,
shouldNotRedirect: boolean
Expand Down
8 changes: 6 additions & 2 deletions packages/next/server/api-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import { sendEtagResponse } from './send-payload'
import generateETag from 'etag'
import isError from '../lib/is-error'
import { interopDefault } from '../lib/interop-default'
import {
WebIncomingMessage,
WebServerResponse,
} from '../server/web/http-adapter'

export type NextApiRequestCookies = { [key: string]: string }
export type NextApiRequestQuery = { [key: string]: string | string[] }
Expand Down Expand Up @@ -338,8 +342,8 @@ export const SYMBOL_PREVIEW_DATA = Symbol(COOKIE_NAME_PRERENDER_DATA)
const SYMBOL_CLEARED_COOKIES = Symbol(COOKIE_NAME_PRERENDER_BYPASS)

export function tryGetPreviewData(
req: IncomingMessage,
res: ServerResponse,
req: IncomingMessage | WebIncomingMessage,
res: ServerResponse | WebServerResponse,
options: __ApiPreviewProps
): PreviewData {
// Read cached preview data if present
Expand Down
13 changes: 12 additions & 1 deletion packages/next/server/dev/hot-reloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,10 @@ export default class HotReloader {
}
} else if (isServerWebCompilation) {
if (!isReserved) {
const hasRuntimeConfig =
Object.keys(this.config.publicRuntimeConfig).length > 0 ||
Object.keys(this.config.serverRuntimeConfig).length > 0

entrypoints[bundlePath] = finalizeEntrypoint({
name: '[name].js',
value: `next-middleware-ssr-loader?${stringify({
Expand All @@ -539,7 +543,14 @@ export default class HotReloader {
poweredByHeader: this.config.poweredByHeader,
canonicalBase: this.config.amp.canonicalBase,
i18n: this.config.i18n,
previewProps: this.previewProps,
previewProps: JSON.stringify(this.previewProps),
runtimeConfig: hasRuntimeConfig
? JSON.stringify({
publicRuntimeConfig: this.config.publicRuntimeConfig,
serverRuntimeConfig: this.config.serverRuntimeConfig,
})
: '',
distDir: this.config.distDir,
} as any)}!`,
isServer: false,
isServerWeb: true,
Expand Down
5 changes: 3 additions & 2 deletions packages/next/server/render-result.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { ServerResponse } from 'http'
import type { Writable } from 'stream'
import { WebServerResponse } from './web/http-adapter'

export type NodeWritablePiper = (
res: Writable,
res: Writable | WebServerResponse,
next: (err?: Error) => void
) => void

Expand All @@ -22,7 +23,7 @@ export default class RenderResult {
return this._result
}

pipe(res: ServerResponse): Promise<void> {
pipe(res: ServerResponse | WebServerResponse): Promise<void> {
if (typeof this._result === 'string') {
throw new Error(
'invariant: static responses cannot be piped. This is a bug in Next.js'
Expand Down
Loading