rsnext/packages/next/next-server/server/next-server.ts

1819 lines
53 KiB
TypeScript
Raw Normal View History

2020-03-29 00:56:38 +01:00
import compression from 'next/dist/compiled/compression'
import fs from 'fs'
import chalk from 'next/dist/compiled/chalk'
2019-03-01 20:51:13 +01:00
import { IncomingMessage, ServerResponse } from 'http'
2020-03-29 01:30:34 +01:00
import Proxy from 'next/dist/compiled/http-proxy'
import { join, relative, resolve, sep } from 'path'
import {
parse as parseQs,
stringify as stringifyQs,
ParsedUrlQuery,
} from 'querystring'
import { format as formatUrl, parse as parseUrl, UrlWithParsedQuery } from 'url'
import { PrerenderManifest } from '../../build'
import {
getRedirectStatus,
Header,
Redirect,
Rewrite,
RouteType,
CustomRoutes,
} from '../../lib/load-custom-routes'
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
import { withCoalescedInvoke } from '../../lib/coalesced-function'
2019-03-01 20:51:13 +01:00
import {
BUILD_ID_FILE,
2019-05-03 18:57:47 +02:00
CLIENT_PUBLIC_FILES_PATH,
2019-03-01 20:51:13 +01:00
CLIENT_STATIC_FILES_PATH,
CLIENT_STATIC_FILES_RUNTIME,
2019-05-03 18:57:47 +02:00
PAGES_MANIFEST,
PHASE_PRODUCTION_SERVER,
PRERENDER_MANIFEST,
ROUTES_MANIFEST,
SERVERLESS_DIRECTORY,
SERVER_DIRECTORY,
} from '../lib/constants'
2019-05-31 11:27:56 +02:00
import {
getRouteMatcher,
getRouteRegex,
getSortedRoutes,
2019-06-20 20:41:02 +02:00
isDynamicRoute,
2019-05-31 11:27:56 +02:00
} from '../lib/router/utils'
import * as envConfig from '../lib/runtime-config'
import { isResSent, NextApiRequest, NextApiResponse } from '../lib/utils'
import {
apiResolver,
setLazyProp,
getCookieParser,
tryGetPreviewData,
__ApiPreviewProps,
} from './api-utils'
import loadConfig, { isTargetLikeServerless } from './config'
import pathMatch from '../lib/router/utils/path-match'
import { recursiveReadDirSync } from './lib/recursive-readdir-sync'
import { loadComponents, LoadComponentsReturnType } from './load-components'
import { normalizePagePath } from './normalize-page-path'
import { RenderOpts, RenderOptsPartial, renderToHTML } from './render'
import { getPagePath, requireFontManifest } from './require'
import Router, {
DynamicRoutes,
PageChecker,
Params,
route,
Route,
} from './router'
import prepareDestination from '../lib/router/utils/prepare-destination'
import { sendPayload } from './send-payload'
import { serveStatic } from './serve-static'
import { IncrementalCache } from './incremental-cache'
import { execOnce } from '../lib/utils'
import { isBlockedPage } from './utils'
import { compile as compilePathToRegex } from 'next/dist/compiled/path-to-regexp'
import { loadEnvConfig } from '@next/env'
import './node-polyfill-fetch'
import { PagesManifest } from '../../build/webpack/plugins/pages-manifest-plugin'
import { removePathTrailingSlash } from '../../client/normalize-trailing-slash'
import getRouteFromAssetPath from '../lib/router/utils/get-route-from-asset-path'
import { FontManifest } from './font-utils'
import { denormalizePagePath } from './denormalize-page-path'
import accept from '@hapi/accept'
import { normalizeLocalePath } from '../lib/i18n/normalize-locale-path'
import { detectLocaleCookie } from '../lib/i18n/detect-locale-cookie'
import * as Log from '../../build/output/log'
import { imageOptimizer } from './image-optimizer'
import { detectDomainLocale } from '../lib/i18n/detect-domain-locale'
import cookie from 'next/dist/compiled/cookie'
2019-11-09 23:34:53 +01:00
const getCustomRouteMatcher = pathMatch(true)
type NextConfig = any
type Middleware = (
req: IncomingMessage,
res: ServerResponse,
next: (err?: Error) => void
) => void
type FindComponentsResult = {
components: LoadComponentsReturnType
query: ParsedUrlQuery
}
export type ServerConstructor = {
/**
* Where the Next project is located - @default '.'
*/
2019-03-01 20:51:13 +01:00
dir?: string
/**
* Hide error messages containing server information - @default false
*/
2019-03-01 20:51:13 +01:00
quiet?: boolean
/**
* Object what you would use in next.config.js - @default {}
*/
conf?: NextConfig
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
dev?: boolean
customServer?: boolean
}
2016-10-06 01:52:50 +02:00
export default class Server {
dir: string
quiet: boolean
nextConfig: NextConfig
distDir: string
pagesDir?: string
2019-05-03 18:57:47 +02:00
publicDir: string
hasStaticDir: boolean
serverBuildDir: string
pagesManifest?: PagesManifest
buildId: string
renderOpts: {
poweredByHeader: boolean
2019-03-01 20:51:13 +01:00
buildId: string
generateEtags: boolean
runtimeConfig?: { [key: string]: any }
assetPrefix?: string
canonicalBase: string
dev?: boolean
previewProps: __ApiPreviewProps
customServer?: boolean
ampOptimizerConfig?: { [key: string]: any }
basePath: string
optimizeFonts: boolean
images: string
fontManifest: FontManifest
optimizeImages: boolean
locale?: string
locales?: string[]
defaultLocale?: string
}
private compression?: Middleware
private onErrorMiddleware?: ({ err }: { err: Error }) => Promise<void>
private incrementalCache: IncrementalCache
router: Router
protected dynamicRoutes?: DynamicRoutes
protected customRoutes: CustomRoutes
2019-03-01 20:51:13 +01:00
public constructor({
dir = '.',
quiet = false,
conf = null,
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
dev = false,
customServer = true,
2019-03-01 20:51:13 +01:00
}: ServerConstructor = {}) {
2016-10-06 13:05:52 +02:00
this.dir = resolve(dir)
this.quiet = quiet
const phase = this.currentPhase()
loadEnvConfig(this.dir, dev, Log)
this.nextConfig = loadConfig(phase, this.dir, conf)
2018-06-13 20:30:55 +02:00
this.distDir = join(this.dir, this.nextConfig.distDir)
2019-05-03 18:57:47 +02:00
this.publicDir = join(this.dir, CLIENT_PUBLIC_FILES_PATH)
this.hasStaticDir = fs.existsSync(join(this.dir, 'static'))
// Only serverRuntimeConfig needs the default
// publicRuntimeConfig gets it's default in client/index.js
2019-03-01 20:51:13 +01:00
const {
serverRuntimeConfig = {},
publicRuntimeConfig,
assetPrefix,
generateEtags,
compress,
2019-03-01 20:51:13 +01:00
} = this.nextConfig
this.buildId = this.readBuildId()
this.renderOpts = {
poweredByHeader: this.nextConfig.poweredByHeader,
canonicalBase: this.nextConfig.amp.canonicalBase,
buildId: this.buildId,
generateEtags,
previewProps: this.getPreviewProps(),
customServer: customServer === true ? true : undefined,
ampOptimizerConfig: this.nextConfig.experimental.amp?.optimizer,
basePath: this.nextConfig.basePath,
images: JSON.stringify(this.nextConfig.images),
optimizeFonts: this.nextConfig.experimental.optimizeFonts && !dev,
fontManifest:
this.nextConfig.experimental.optimizeFonts && !dev
? requireFontManifest(this.distDir, this._isLikeServerless)
: null,
optimizeImages: this.nextConfig.experimental.optimizeImages,
defaultLocale: this.nextConfig.experimental.i18n?.defaultLocale,
}
2018-02-27 17:50:14 +01:00
// Only the `publicRuntimeConfig` key is exposed to the client side
// It'll be rendered as part of __NEXT_DATA__ on the client side
if (Object.keys(publicRuntimeConfig).length > 0) {
2018-02-27 17:50:14 +01:00
this.renderOpts.runtimeConfig = publicRuntimeConfig
}
if (compress && this.nextConfig.target === 'server') {
this.compression = compression() as Middleware
}
2018-02-27 17:50:14 +01:00
// Initialize next/config with the environment configuration
envConfig.setConfig({
serverRuntimeConfig,
publicRuntimeConfig,
})
2018-02-27 17:50:14 +01:00
this.serverBuildDir = join(
this.distDir,
this._isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY
)
const pagesManifestPath = join(this.serverBuildDir, PAGES_MANIFEST)
if (!dev) {
this.pagesManifest = require(pagesManifestPath)
}
this.customRoutes = this.getCustomRoutes()
2019-11-09 23:34:53 +01:00
this.router = new Router(this.generateRoutes())
2018-02-27 17:50:14 +01:00
this.setAssetPrefix(assetPrefix)
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
Initial plugins implementation (#9139) * Add initial bit for plugins * Add checks for needed metadata values * Add test * Initial plugins changes * Add handling for _app middleware * Add loading of _document middleware and handling of multiple default export syntaxes * Fix insert order for middleware member expression * Remove early return from middleware plugin from testing * Add tests for current plugin middlewares * Update test plugin package.json * Update handling for class default export * Update to use webpack loader instead of babel plugin, remove redundant middleware naming, and add field for required env for plugins * Add middleware to support material-ui use case and example material-ui plugin * Update tests and remove tests stuff from google analytics plugin * Remove old plugin suite * Add init-server middleware * Exit hard without stack trace when error in collecting plugins * Add on-error-client and on-error-server and update to run init-server with next-start in serverless mode * Update init-client for google analytics plugin * Add example Sentry plugin and update with-sentry-simple * Remove middleware field/folder and use src dir for plugins * Add post-hydration middleware and update material-ui plugin * Put plugins code behind flag * Update chromedriver * Revert "Update chromedriver" This reverts commit 1461e978e677f7da05e29e0415ec614a04bf65f9. * Update lock file * Remove un-needed _app for sentry example * Add auto loading of scoped packages, add plugins config for manually listing plugins, and update to only collect plugins once * Update example plugins * Expose plugins' config * Rename plugin lifecycles and add babel-preset-build * Rename other methods with unstable * Update log when plugin config overrides auto-detecting
2019-11-01 20:13:13 +01:00
// call init-server middleware, this is also handled
// individually in serverless bundles when deployed
if (!dev && this.nextConfig.experimental.plugins) {
const initServer = require(join(this.serverBuildDir, 'init-server.js'))
.default
Initial plugins implementation (#9139) * Add initial bit for plugins * Add checks for needed metadata values * Add test * Initial plugins changes * Add handling for _app middleware * Add loading of _document middleware and handling of multiple default export syntaxes * Fix insert order for middleware member expression * Remove early return from middleware plugin from testing * Add tests for current plugin middlewares * Update test plugin package.json * Update handling for class default export * Update to use webpack loader instead of babel plugin, remove redundant middleware naming, and add field for required env for plugins * Add middleware to support material-ui use case and example material-ui plugin * Update tests and remove tests stuff from google analytics plugin * Remove old plugin suite * Add init-server middleware * Exit hard without stack trace when error in collecting plugins * Add on-error-client and on-error-server and update to run init-server with next-start in serverless mode * Update init-client for google analytics plugin * Add example Sentry plugin and update with-sentry-simple * Remove middleware field/folder and use src dir for plugins * Add post-hydration middleware and update material-ui plugin * Put plugins code behind flag * Update chromedriver * Revert "Update chromedriver" This reverts commit 1461e978e677f7da05e29e0415ec614a04bf65f9. * Update lock file * Remove un-needed _app for sentry example * Add auto loading of scoped packages, add plugins config for manually listing plugins, and update to only collect plugins once * Update example plugins * Expose plugins' config * Rename plugin lifecycles and add babel-preset-build * Rename other methods with unstable * Update log when plugin config overrides auto-detecting
2019-11-01 20:13:13 +01:00
this.onErrorMiddleware = require(join(
this.serverBuildDir,
Initial plugins implementation (#9139) * Add initial bit for plugins * Add checks for needed metadata values * Add test * Initial plugins changes * Add handling for _app middleware * Add loading of _document middleware and handling of multiple default export syntaxes * Fix insert order for middleware member expression * Remove early return from middleware plugin from testing * Add tests for current plugin middlewares * Update test plugin package.json * Update handling for class default export * Update to use webpack loader instead of babel plugin, remove redundant middleware naming, and add field for required env for plugins * Add middleware to support material-ui use case and example material-ui plugin * Update tests and remove tests stuff from google analytics plugin * Remove old plugin suite * Add init-server middleware * Exit hard without stack trace when error in collecting plugins * Add on-error-client and on-error-server and update to run init-server with next-start in serverless mode * Update init-client for google analytics plugin * Add example Sentry plugin and update with-sentry-simple * Remove middleware field/folder and use src dir for plugins * Add post-hydration middleware and update material-ui plugin * Put plugins code behind flag * Update chromedriver * Revert "Update chromedriver" This reverts commit 1461e978e677f7da05e29e0415ec614a04bf65f9. * Update lock file * Remove un-needed _app for sentry example * Add auto loading of scoped packages, add plugins config for manually listing plugins, and update to only collect plugins once * Update example plugins * Expose plugins' config * Rename plugin lifecycles and add babel-preset-build * Rename other methods with unstable * Update log when plugin config overrides auto-detecting
2019-11-01 20:13:13 +01:00
'on-error-server.js'
)).default
initServer()
}
this.incrementalCache = new IncrementalCache({
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
dev,
distDir: this.distDir,
pagesDir: join(
this.distDir,
this._isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY,
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
'pages'
),
flushToDisk: this.nextConfig.experimental.sprFlushToDisk,
})
/**
* This sets environment variable to be used at the time of SSR by head.tsx.
* Using this from process.env allows targetting both serverless and SSR by calling
* `process.env.__NEXT_OPTIMIZE_FONTS`.
* TODO(prateekbh@): Remove this when experimental.optimizeFonts are being clened up.
*/
if (this.renderOpts.optimizeFonts) {
process.env.__NEXT_OPTIMIZE_FONTS = JSON.stringify(true)
}
if (this.renderOpts.optimizeImages) {
process.env.__NEXT_OPTIMIZE_IMAGES = JSON.stringify(true)
}
}
2016-10-06 01:52:50 +02:00
protected currentPhase(): string {
return PHASE_PRODUCTION_SERVER
}
public logError(err: Error): void {
Initial plugins implementation (#9139) * Add initial bit for plugins * Add checks for needed metadata values * Add test * Initial plugins changes * Add handling for _app middleware * Add loading of _document middleware and handling of multiple default export syntaxes * Fix insert order for middleware member expression * Remove early return from middleware plugin from testing * Add tests for current plugin middlewares * Update test plugin package.json * Update handling for class default export * Update to use webpack loader instead of babel plugin, remove redundant middleware naming, and add field for required env for plugins * Add middleware to support material-ui use case and example material-ui plugin * Update tests and remove tests stuff from google analytics plugin * Remove old plugin suite * Add init-server middleware * Exit hard without stack trace when error in collecting plugins * Add on-error-client and on-error-server and update to run init-server with next-start in serverless mode * Update init-client for google analytics plugin * Add example Sentry plugin and update with-sentry-simple * Remove middleware field/folder and use src dir for plugins * Add post-hydration middleware and update material-ui plugin * Put plugins code behind flag * Update chromedriver * Revert "Update chromedriver" This reverts commit 1461e978e677f7da05e29e0415ec614a04bf65f9. * Update lock file * Remove un-needed _app for sentry example * Add auto loading of scoped packages, add plugins config for manually listing plugins, and update to only collect plugins once * Update example plugins * Expose plugins' config * Rename plugin lifecycles and add babel-preset-build * Rename other methods with unstable * Update log when plugin config overrides auto-detecting
2019-11-01 20:13:13 +01:00
if (this.onErrorMiddleware) {
this.onErrorMiddleware({ err })
}
if (this.quiet) return
Initial plugins implementation (#9139) * Add initial bit for plugins * Add checks for needed metadata values * Add test * Initial plugins changes * Add handling for _app middleware * Add loading of _document middleware and handling of multiple default export syntaxes * Fix insert order for middleware member expression * Remove early return from middleware plugin from testing * Add tests for current plugin middlewares * Update test plugin package.json * Update handling for class default export * Update to use webpack loader instead of babel plugin, remove redundant middleware naming, and add field for required env for plugins * Add middleware to support material-ui use case and example material-ui plugin * Update tests and remove tests stuff from google analytics plugin * Remove old plugin suite * Add init-server middleware * Exit hard without stack trace when error in collecting plugins * Add on-error-client and on-error-server and update to run init-server with next-start in serverless mode * Update init-client for google analytics plugin * Add example Sentry plugin and update with-sentry-simple * Remove middleware field/folder and use src dir for plugins * Add post-hydration middleware and update material-ui plugin * Put plugins code behind flag * Update chromedriver * Revert "Update chromedriver" This reverts commit 1461e978e677f7da05e29e0415ec614a04bf65f9. * Update lock file * Remove un-needed _app for sentry example * Add auto loading of scoped packages, add plugins config for manually listing plugins, and update to only collect plugins once * Update example plugins * Expose plugins' config * Rename plugin lifecycles and add babel-preset-build * Rename other methods with unstable * Update log when plugin config overrides auto-detecting
2019-11-01 20:13:13 +01:00
console.error(err)
}
private async handleRequest(
2019-03-01 20:51:13 +01:00
req: IncomingMessage,
res: ServerResponse,
parsedUrl?: UrlWithParsedQuery
2019-03-01 20:51:13 +01:00
): Promise<void> {
setLazyProp({ req: req as any }, 'cookies', getCookieParser(req))
// Parse url if parsedUrl not provided
if (!parsedUrl || typeof parsedUrl !== 'object') {
const url: any = req.url
parsedUrl = parseUrl(url, true)
}
// Parse the querystring ourselves if the user doesn't handle querystring parsing
if (typeof parsedUrl.query === 'string') {
parsedUrl.query = parseQs(parsedUrl.query)
}
const { basePath } = this.nextConfig
const { i18n } = this.nextConfig.experimental
if (basePath && req.url?.startsWith(basePath)) {
// store original URL to allow checking if basePath was
// provided or not
;(req as any)._nextHadBasePath = true
req.url = req.url!.replace(basePath, '') || '/'
}
if (i18n && !parsedUrl.pathname?.startsWith('/_next')) {
// get pathname from URL with basePath stripped for locale detection
const { pathname, ...parsed } = parseUrl(req.url || '/')
let defaultLocale = i18n.defaultLocale
let detectedLocale = detectLocaleCookie(req, i18n.locales)
let acceptPreferredLocale = accept.language(
req.headers['accept-language'],
i18n.locales
)
const { host } = req?.headers || {}
// remove port from host and remove port if present
const hostname = host?.split(':')[0].toLowerCase()
const detectedDomain = detectDomainLocale(i18n.domains, hostname)
if (detectedDomain) {
defaultLocale = detectedDomain.defaultLocale
detectedLocale = defaultLocale
}
// if not domain specific locale use accept-language preferred
detectedLocale = detectedLocale || acceptPreferredLocale
let localeDomainRedirect: string | undefined
const localePathResult = normalizeLocalePath(pathname!, i18n.locales)
if (localePathResult.detectedLocale) {
detectedLocale = localePathResult.detectedLocale
req.url = formatUrl({
...parsed,
pathname: localePathResult.pathname,
})
;(req as any).__nextStrippedLocale = true
parsedUrl.pathname = localePathResult.pathname
// check if the locale prefix matches a domain's defaultLocale
// and we're on a locale specific domain if so redirect to that domain
// if (detectedDomain) {
// const matchedDomain = detectDomainLocale(
// i18n.domains,
// undefined,
// detectedLocale
// )
// if (matchedDomain) {
// localeDomainRedirect = `http${matchedDomain.http ? '' : 's'}://${
// matchedDomain?.domain
// }`
// }
// }
} else if (detectedDomain) {
const matchedDomain = detectDomainLocale(
i18n.domains,
undefined,
acceptPreferredLocale
)
if (matchedDomain && matchedDomain.domain !== detectedDomain.domain) {
localeDomainRedirect = `http${matchedDomain.http ? '' : 's'}://${
matchedDomain.domain
}`
}
}
const denormalizedPagePath = denormalizePagePath(pathname || '/')
const detectedDefaultLocale =
!detectedLocale ||
detectedLocale.toLowerCase() === defaultLocale.toLowerCase()
const shouldStripDefaultLocale = false
// detectedDefaultLocale &&
// denormalizedPagePath.toLowerCase() ===
// `/${i18n.defaultLocale.toLowerCase()}`
const shouldAddLocalePrefix =
!detectedDefaultLocale && denormalizedPagePath === '/'
detectedLocale = detectedLocale || i18n.defaultLocale
if (
i18n.localeDetection !== false &&
(localeDomainRedirect ||
shouldAddLocalePrefix ||
shouldStripDefaultLocale)
) {
// set the NEXT_LOCALE cookie when a user visits the default locale
// with the locale prefix so that they aren't redirected back to
// their accept-language preferred locale
if (
shouldStripDefaultLocale &&
acceptPreferredLocale !== defaultLocale
) {
const previous = res.getHeader('set-cookie')
res.setHeader('set-cookie', [
...(typeof previous === 'string'
? [previous]
: Array.isArray(previous)
? previous
: []),
cookie.serialize('NEXT_LOCALE', defaultLocale, {
httpOnly: true,
path: '/',
}),
])
}
res.setHeader(
'Location',
formatUrl({
// make sure to include any query values when redirecting
...parsed,
pathname: localeDomainRedirect
? localeDomainRedirect
: shouldStripDefaultLocale
? '/'
: `/${detectedLocale}`,
})
)
res.statusCode = 307
res.end()
return
}
parsedUrl.query.__nextLocale = detectedLocale || defaultLocale
}
res.statusCode = 200
try {
return await this.run(req, res, parsedUrl)
} catch (err) {
2019-03-01 20:51:13 +01:00
this.logError(err)
res.statusCode = 500
res.end('Internal Server Error')
}
}
public getRequestHandler() {
return this.handleRequest.bind(this)
2016-10-06 01:52:50 +02:00
}
public setAssetPrefix(prefix?: string): void {
this.renderOpts.assetPrefix = prefix ? prefix.replace(/\/$/, '') : ''
}
// Backwards compatibility
public async prepare(): Promise<void> {}
2016-10-17 09:07:41 +02:00
// Backwards compatibility
protected async close(): Promise<void> {}
protected setImmutableAssetCacheControl(res: ServerResponse): void {
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable')
}
protected getCustomRoutes(): CustomRoutes {
2019-11-09 23:34:53 +01:00
return require(join(this.distDir, ROUTES_MANIFEST))
}
private _cachedPreviewManifest: PrerenderManifest | undefined
protected getPrerenderManifest(): PrerenderManifest {
if (this._cachedPreviewManifest) {
return this._cachedPreviewManifest
}
const manifest = require(join(this.distDir, PRERENDER_MANIFEST))
return (this._cachedPreviewManifest = manifest)
}
protected getPreviewProps(): __ApiPreviewProps {
return this.getPrerenderManifest().preview
}
protected generateRoutes(): {
basePath: string
headers: Route[]
rewrites: Route[]
fsRoutes: Route[]
redirects: Route[]
catchAllRoute: Route
pageChecker: PageChecker
useFileSystemPublicRoutes: boolean
dynamicRoutes: DynamicRoutes | undefined
} {
const server: Server = this
const publicRoutes = fs.existsSync(this.publicDir)
? this.generatePublicRoutes()
: []
2019-11-09 23:34:53 +01:00
const staticFilesRoute = this.hasStaticDir
? [
{
// It's very important to keep this route's param optional.
// (but it should support as many params as needed, separated by '/')
// Otherwise this will lead to a pretty simple DOS attack.
// See more: https://github.com/vercel/next.js/issues/2617
match: route('/static/:path*'),
name: 'static catchall',
fn: async (req, res, params, parsedUrl) => {
const p = join(this.dir, 'static', ...params.path)
await this.serveStatic(req, res, p, parsedUrl)
return {
finished: true,
}
},
} as Route,
]
: []
const fsRoutes: Route[] = [
{
match: route('/_next/static/:path*'),
type: 'route',
name: '_next/static catchall',
fn: async (req, res, params, parsedUrl) => {
// make sure to 404 for /_next/static itself
if (!params.path) {
await this.render404(req, res, parsedUrl)
return {
finished: true,
}
}
2019-03-01 20:51:13 +01:00
if (
params.path[0] === CLIENT_STATIC_FILES_RUNTIME ||
params.path[0] === 'chunks' ||
params.path[0] === 'css' ||
params.path[0] === 'media' ||
params.path[0] === this.buildId ||
params.path[0] === 'pages' ||
params.path[1] === 'pages'
2019-03-01 20:51:13 +01:00
) {
this.setImmutableAssetCacheControl(res)
}
2019-03-01 20:51:13 +01:00
const p = join(
this.distDir,
CLIENT_STATIC_FILES_PATH,
...(params.path || [])
2019-03-01 20:51:13 +01:00
)
await this.serveStatic(req, res, p, parsedUrl)
return {
finished: true,
}
},
},
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
{
match: route('/_next/data/:path*'),
type: 'route',
name: '_next/data catchall',
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
fn: async (req, res, params, _parsedUrl) => {
// Make sure to 404 for /_next/data/ itself and
// we also want to 404 if the buildId isn't correct
if (!params.path || params.path[0] !== this.buildId) {
await this.render404(req, res, _parsedUrl)
return {
finished: true,
}
}
// remove buildId from URL
params.path.shift()
// show 404 if it doesn't end with .json
if (!params.path[params.path.length - 1].endsWith('.json')) {
await this.render404(req, res, _parsedUrl)
return {
finished: true,
}
}
// re-create page's pathname
let pathname = `/${params.path.join('/')}`
const { i18n } = this.nextConfig.experimental
if (i18n) {
const { host } = req?.headers || {}
// remove port from host and remove port if present
const hostname = host?.split(':')[0].toLowerCase()
const localePathResult = normalizeLocalePath(pathname, i18n.locales)
const { defaultLocale } =
detectDomainLocale(i18n.domains, hostname) || {}
let detectedLocale = defaultLocale
if (localePathResult.detectedLocale) {
pathname = localePathResult.pathname
detectedLocale = localePathResult.detectedLocale
}
_parsedUrl.query.__nextLocale = detectedLocale!
}
pathname = getRouteFromAssetPath(pathname, '.json')
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
const parsedUrl = parseUrl(pathname, true)
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
await this.render(
req,
res,
pathname,
{ ..._parsedUrl.query, _nextDataReq: '1' },
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
parsedUrl
)
return {
finished: true,
}
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
},
},
{
match: route('/_next/image'),
type: 'route',
name: '_next/image catchall',
fn: (req, res, _params, parsedUrl) =>
imageOptimizer(server, req, res, parsedUrl),
},
{
match: route('/_next/:path*'),
type: 'route',
name: '_next catchall',
// This path is needed because `render()` does a check for `/_next` and the calls the routing again
fn: async (req, res, _params, parsedUrl) => {
await this.render404(req, res, parsedUrl)
return {
finished: true,
}
},
},
...publicRoutes,
...staticFilesRoute,
]
const getCustomRouteBasePath = (r: { basePath?: false }) => {
return r.basePath !== false && this.renderOpts.dev
? this.nextConfig.basePath
: ''
}
const getCustomRoute = (r: Rewrite | Redirect | Header, type: RouteType) =>
({
...r,
type,
match: getCustomRouteMatcher(`${getCustomRouteBasePath(r)}${r.source}`),
name: type,
fn: async (_req, _res, _params, _parsedUrl) => ({ finished: false }),
} as Route & Rewrite & Header)
const updateHeaderValue = (value: string, params: Params): string => {
if (!value.includes(':')) {
return value
}
for (const key of Object.keys(params)) {
if (value.includes(`:${key}`)) {
value = value
.replace(
new RegExp(`:${key}\\*`, 'g'),
`:${key}--ESCAPED_PARAM_ASTERISKS`
)
.replace(
new RegExp(`:${key}\\?`, 'g'),
`:${key}--ESCAPED_PARAM_QUESTION`
)
.replace(
new RegExp(`:${key}\\+`, 'g'),
`:${key}--ESCAPED_PARAM_PLUS`
)
.replace(
new RegExp(`:${key}(?!\\w)`, 'g'),
`--ESCAPED_PARAM_COLON${key}`
)
}
}
value = value
.replace(/(:|\*|\?|\+|\(|\)|\{|\})/g, '\\$1')
.replace(/--ESCAPED_PARAM_PLUS/g, '+')
.replace(/--ESCAPED_PARAM_COLON/g, ':')
.replace(/--ESCAPED_PARAM_QUESTION/g, '?')
.replace(/--ESCAPED_PARAM_ASTERISKS/g, '*')
// the value needs to start with a forward-slash to be compiled
// correctly
return compilePathToRegex(`/${value}`, { validate: false })(
params
).substr(1)
}
// Headers come very first
const headers = this.customRoutes.headers.map((r) => {
const headerRoute = getCustomRoute(r, 'header')
return {
match: headerRoute.match,
type: headerRoute.type,
name: `${headerRoute.type} ${headerRoute.source} header route`,
fn: async (_req, res, params, _parsedUrl) => {
const hasParams = Object.keys(params).length > 0
for (const header of (headerRoute as Header).headers) {
let { key, value } = header
if (hasParams) {
key = updateHeaderValue(key, params)
value = updateHeaderValue(value, params)
}
res.setHeader(key, value)
}
return { finished: false }
},
} as Route
})
const redirects = this.customRoutes.redirects.map((redirect) => {
const redirectRoute = getCustomRoute(redirect, 'redirect')
return {
type: redirectRoute.type,
match: redirectRoute.match,
statusCode: redirectRoute.statusCode,
name: `Redirect route`,
fn: async (_req, res, params, parsedUrl) => {
const { parsedDestination } = prepareDestination(
redirectRoute.destination,
params,
parsedUrl.query,
false,
getCustomRouteBasePath(redirectRoute)
)
const { query } = parsedDestination
delete (parsedDestination as any).query
parsedDestination.search = stringifyQs(query)
const updatedDestination = formatUrl(parsedDestination)
res.setHeader('Location', updatedDestination)
res.statusCode = getRedirectStatus(redirectRoute as Redirect)
// Since IE11 doesn't support the 308 header add backwards
// compatibility using refresh header
if (res.statusCode === 308) {
res.setHeader('Refresh', `0;url=${updatedDestination}`)
}
res.end()
return {
finished: true,
}
},
} as Route
})
const rewrites = this.customRoutes.rewrites.map((rewrite) => {
const rewriteRoute = getCustomRoute(rewrite, 'rewrite')
return {
...rewriteRoute,
check: true,
type: rewriteRoute.type,
name: `Rewrite route`,
match: rewriteRoute.match,
fn: async (req, res, params, parsedUrl) => {
const { newUrl, parsedDestination } = prepareDestination(
rewriteRoute.destination,
params,
parsedUrl.query,
true,
getCustomRouteBasePath(rewriteRoute)
)
// external rewrite, proxy it
if (parsedDestination.protocol) {
const { query } = parsedDestination
delete (parsedDestination as any).query
parsedDestination.search = stringifyQs(query)
const target = formatUrl(parsedDestination)
const proxy = new Proxy({
target,
changeOrigin: true,
ignorePath: true,
})
proxy.web(req, res)
proxy.on('error', (err: Error) => {
console.error(`Error occurred proxying ${target}`, err)
})
return {
finished: true,
}
}
;(req as any)._nextRewroteUrl = newUrl
;(req as any)._nextDidRewrite =
(req as any)._nextRewroteUrl !== req.url
return {
finished: false,
pathname: newUrl,
query: parsedDestination.query,
}
},
} as Route
})
const catchAllRoute: Route = {
match: route('/:path*'),
type: 'route',
name: 'Catchall render',
fn: async (req, res, params, parsedUrl) => {
let { pathname, query } = parsedUrl
if (!pathname) {
throw new Error('pathname is undefined')
}
// next.js core assumes page path without trailing slash
pathname = removePathTrailingSlash(pathname)
if (params?.path?.[0] === 'api') {
const handled = await this.handleApiRequest(
req as NextApiRequest,
res as NextApiResponse,
pathname,
query
)
if (handled) {
return { finished: true }
}
}
await this.render(req, res, pathname, query, parsedUrl)
return {
finished: true,
}
},
}
const { useFileSystemPublicRoutes } = this.nextConfig
if (useFileSystemPublicRoutes) {
this.dynamicRoutes = this.getDynamicRoutes()
}
2016-10-06 01:52:50 +02:00
return {
headers,
fsRoutes,
rewrites,
redirects,
catchAllRoute,
useFileSystemPublicRoutes,
dynamicRoutes: this.dynamicRoutes,
basePath: this.nextConfig.basePath,
pageChecker: this.hasPage.bind(this),
}
}
private async getPagePath(pathname: string): Promise<string> {
return getPagePath(
pathname,
this.distDir,
this._isLikeServerless,
this.renderOpts.dev
)
}
protected async hasPage(pathname: string): Promise<boolean> {
let found = false
try {
found = !!(await this.getPagePath(pathname))
} catch (_) {}
return found
}
protected async _beforeCatchAllRender(
_req: IncomingMessage,
_res: ServerResponse,
_params: Params,
_parsedUrl: UrlWithParsedQuery
): Promise<boolean> {
return false
}
// Used to build API page in development
protected async ensureApiPage(_pathname: string): Promise<void> {}
/**
* Resolves `API` request, in development builds on demand
* @param req http request
* @param res http response
* @param pathname path of request
*/
private async handleApiRequest(
req: IncomingMessage,
res: ServerResponse,
pathname: string,
query: ParsedUrlQuery
): Promise<boolean> {
let page = pathname
let params: Params | boolean = false
let pageFound = await this.hasPage(page)
if (!pageFound && this.dynamicRoutes) {
for (const dynamicRoute of this.dynamicRoutes) {
params = dynamicRoute.match(pathname)
if (dynamicRoute.page.startsWith('/api') && params) {
page = dynamicRoute.page
pageFound = true
break
}
}
}
if (!pageFound) {
return false
}
// Make sure the page is built before getting the path
// or else it won't be in the manifest yet
await this.ensureApiPage(page)
let builtPagePath
try {
builtPagePath = await this.getPagePath(page)
} catch (err) {
if (err.code === 'ENOENT') {
return false
}
throw err
}
const pageModule = await require(builtPagePath)
query = { ...query, ...params }
if (!this.renderOpts.dev && this._isLikeServerless) {
if (typeof pageModule.default === 'function') {
prepareServerlessUrl(req, query)
await pageModule.default(req, res)
return true
}
}
await apiResolver(
req,
res,
query,
pageModule,
this.renderOpts.previewProps,
false,
this.onErrorMiddleware
)
return true
}
protected generatePublicRoutes(): Route[] {
const publicFiles = new Set(
recursiveReadDirSync(this.publicDir).map((p) =>
encodeURI(p.replace(/\\/g, '/'))
)
)
return [
{
match: route('/:path*'),
name: 'public folder catchall',
fn: async (req, res, params, parsedUrl) => {
const pathParts: string[] = params.path || []
const { basePath } = this.nextConfig
// if basePath is defined require it be present
if (basePath) {
if (pathParts[0] !== basePath.substr(1)) return { finished: false }
pathParts.shift()
}
const path = `/${pathParts.join('/')}`
if (publicFiles.has(path)) {
await this.serveStatic(
req,
res,
join(this.publicDir, ...pathParts),
parsedUrl
)
return {
finished: true,
}
}
return {
finished: false,
}
},
} as Route,
]
2019-05-03 18:57:47 +02:00
}
protected getDynamicRoutes() {
return getSortedRoutes(Object.keys(this.pagesManifest!))
.filter(isDynamicRoute)
2020-05-18 21:24:37 +02:00
.map((page) => ({
page,
match: getRouteMatcher(getRouteRegex(page)),
}))
}
private handleCompression(req: IncomingMessage, res: ServerResponse): void {
if (this.compression) {
this.compression(req, res, () => {})
}
}
protected async run(
2019-03-01 20:51:13 +01:00
req: IncomingMessage,
res: ServerResponse,
parsedUrl: UrlWithParsedQuery
): Promise<void> {
this.handleCompression(req, res)
try {
const matched = await this.router.execute(req, res, parsedUrl)
if (matched) {
return
}
} catch (err) {
if (err.code === 'DECODE_FAILED') {
res.statusCode = 400
return this.renderError(null, req, res, '/_error', {})
}
throw err
}
await this.render404(req, res, parsedUrl)
2016-10-06 01:52:50 +02:00
}
protected async sendHTML(
2019-03-01 20:51:13 +01:00
req: IncomingMessage,
res: ServerResponse,
html: string
): Promise<void> {
const { generateEtags, poweredByHeader } = this.renderOpts
return sendPayload(req, res, html, 'html', {
generateEtags,
poweredByHeader,
})
}
2019-03-01 20:51:13 +01:00
public async render(
req: IncomingMessage,
res: ServerResponse,
pathname: string,
query: ParsedUrlQuery = {},
parsedUrl?: UrlWithParsedQuery
2019-03-01 20:51:13 +01:00
): Promise<void> {
if (!pathname.startsWith('/')) {
console.warn(
`Cannot render page with path "${pathname}", did you mean "/${pathname}"?. See more info here: https://err.sh/next.js/render-no-starting-slash`
)
}
Fix pages/index.js and pages/index/index.js behavior (#13699) Disambiguate between pages/index.js and pages/index/index.js so that they resolve differently. It all started with a bug in pagesmanifest that propagated throughout the codebase. After fixing pagesmanifest I was able to remove a few hacks here and there and more logic is shared now. especially the logic that resolves an entrypoint back into a route path. To sum up what happened: - `getRouteFromEntrypoint` is the inverse operation of `getPageFile` that's under `pages/_document.tsx` - `denormalizePagePath` is the inverse operation of `normalizePagePath`. Everything is refactored in terms of these operations, that makes their behavior uniform and easier to update/patch in a central place. Before there were subtle differences between those that made `index/index.js` hard to handle. Some potential follow up on this PR: - [`hot-reloader`](https://github.com/vercel/next.js/pull/13699/files#diff-6161346d2c5f4b7abc87059d8768c44bR207) still has one place that does very similar behavior to `getRouteFromEntrypoint`. It can probably be rewritten in terms of `getRouteFromEntrypoint`. - There are a few places where `denormalizePagePath(normalizePagePath(...))` is happening. This is a sign that `normalizePagePath` is doing some validation that is independent of its rewriting logic. That should probably be factored out in its own function. after that I should probably investigate whether `normalizePagePath` is even still needed at all. - a lot of code is doing `.replace(/\\/g, '')`. If wanted, that could be replaced with `normalizePathSep`. - It looks to me like some logic that's spread across the project can be centralized in 4 functions - `getRouteFromEntrypoint` (part of this PR) - its inverse `getEntrypointFromRoute` (already exists in `_document.tsx` as `getPageFile`) - `getRouteFromPageFile` - its inverse `getPageFileFromRoute` (already exists as `findPageFile ` in `server/lib/find-page-file.ts`) It could be beneficial to structure the code to keep these fuctionalities close together and name them similarly. - revise `index.amp` handling in pagesmanifest. I left it alone in this PR to keep it scoped, but it may be broken wrt nested index files as well. It might even make sense to reshape the pagesmanifest altogether to handle html/json/amp/... better
2020-06-04 19:32:45 +02:00
if (
this.renderOpts.customServer &&
pathname === '/index' &&
!(await this.hasPage('/index'))
) {
// maintain backwards compatibility for custom server
// (see custom-server integration tests)
pathname = '/'
}
const url: any = req.url
// we allow custom servers to call render for all URLs
// so check if we need to serve a static _next file or not.
// we don't modify the URL for _next/data request but still
// call render so we special case this to prevent an infinite loop
if (
!query._nextDataReq &&
(url.match(/^\/_next\//) ||
(this.hasStaticDir && url.match(/^\/static\//)))
) {
return this.handleRequest(req, res, parsedUrl)
}
if (isBlockedPage(pathname)) {
return this.render404(req, res, parsedUrl)
}
const html = await this.renderToHTML(req, res, pathname, query)
// Request was ended by the user
if (html === null) {
return
}
return this.sendHTML(req, res, html)
}
2016-10-19 14:41:45 +02:00
private async findPageComponents(
2019-03-01 20:51:13 +01:00
pathname: string,
query: ParsedUrlQuery = {},
params: Params | null = null
): Promise<FindComponentsResult | null> {
let paths = [
// try serving a static AMP version first
query.amp ? normalizePagePath(pathname) + '.amp' : null,
pathname,
].filter(Boolean)
if (query.__nextLocale) {
paths = [
...paths.map(
(path) => `/${query.__nextLocale}${path === '/' ? '' : path}`
),
...paths,
]
}
for (const pagePath of paths) {
try {
const components = await loadComponents(
this.distDir,
pagePath!,
!this.renderOpts.dev && this._isLikeServerless
)
// if loading an static HTML file the locale is required
// to be present since all HTML files are output under their locale
if (
query.__nextLocale &&
typeof components.Component === 'string' &&
!pagePath?.startsWith(`/${query.__nextLocale}`)
) {
const err = new Error('NOT_FOUND')
;(err as any).code = 'ENOENT'
throw err
}
return {
components,
query: {
...(components.getStaticProps
? {
amp: query.amp,
_nextDataReq: query._nextDataReq,
__nextLocale: query.__nextLocale,
}
: query),
...(params || {}),
},
}
} catch (err) {
if (err.code !== 'ENOENT') throw err
}
}
return null
}
protected async getStaticPaths(
pathname: string
): Promise<{
staticPaths: string[] | undefined
fallbackMode: 'static' | 'blocking' | false
}> {
// `staticPaths` is intentionally set to `undefined` as it should've
// been caught when checking disk data.
const staticPaths = undefined
// Read whether or not fallback should exist from the manifest.
const fallbackField = this.getPrerenderManifest().dynamicRoutes[pathname]
.fallback
return {
staticPaths,
fallbackMode:
typeof fallbackField === 'string'
? 'static'
: fallbackField === null
? 'blocking'
: false,
}
}
private async renderToHTMLWithComponents(
req: IncomingMessage,
res: ServerResponse,
pathname: string,
{ components, query }: FindComponentsResult,
opts: RenderOptsPartial
): Promise<string | null> {
// we need to ensure the status code if /404 is visited directly
if (pathname === '/404') {
res.statusCode = 404
}
// handle static page
if (typeof components.Component === 'string') {
return components.Component
}
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
// check request state
const isLikeServerless =
typeof components.Component === 'object' &&
typeof (components.Component as any).renderReqToHTML === 'function'
const isSSG = !!components.getStaticProps
const isServerProps = !!components.getServerSideProps
const hasStaticPaths = !!components.getStaticPaths
if (!query.amp) {
delete query.amp
}
// Toggle whether or not this is a Data request
const isDataReq = !!query._nextDataReq && (isSSG || isServerProps)
delete query._nextDataReq
const locale = query.__nextLocale as string
delete query.__nextLocale
const { i18n } = this.nextConfig.experimental
const locales = i18n.locales as string[]
let previewData: string | false | object | undefined
let isPreviewMode = false
if (isServerProps || isSSG) {
previewData = tryGetPreviewData(req, res, this.renderOpts.previewProps)
isPreviewMode = previewData !== false
}
// Compute the iSSG cache key. We use the rewroteUrl since
// pages with fallback: false are allowed to be rewritten to
// and we need to look up the path by the rewritten path
let urlPathname = parseUrl(req.url || '').pathname || '/'
let resolvedUrlPathname = (req as any)._nextRewroteUrl
? (req as any)._nextRewroteUrl
: urlPathname
resolvedUrlPathname = removePathTrailingSlash(resolvedUrlPathname)
urlPathname = removePathTrailingSlash(urlPathname)
const stripNextDataPath = (path: string) => {
if (path.includes(this.buildId)) {
path = denormalizePagePath(
(path.split(this.buildId).pop() || '/').replace(/\.json$/, '')
)
}
if (this.nextConfig.experimental.i18n) {
return normalizeLocalePath(path, locales).pathname
}
return path
}
// remove /_next/data prefix from urlPathname so it matches
// for direct page visit and /_next/data visit
if (isDataReq) {
resolvedUrlPathname = stripNextDataPath(resolvedUrlPathname)
urlPathname = stripNextDataPath(urlPathname)
}
const ssgCacheKey =
isPreviewMode || !isSSG
? undefined // Preview mode bypasses the cache
: `${locale ? `/${locale}` : ''}${resolvedUrlPathname}${
query.amp ? '.amp' : ''
}`
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
// Complete the response with cached data if its present
const cachedData = ssgCacheKey
? await this.incrementalCache.get(ssgCacheKey)
: undefined
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
if (cachedData) {
if (cachedData.isNotFound) {
// we don't currently revalidate when notFound is returned
// so trigger rendering 404 here
throw new NoFallbackError()
}
const data = isDataReq
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
? JSON.stringify(cachedData.pageData)
: cachedData.html
sendPayload(
req,
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
res,
data,
isDataReq ? 'json' : 'html',
{
generateEtags: this.renderOpts.generateEtags,
poweredByHeader: this.renderOpts.poweredByHeader,
},
!this.renderOpts.dev
? {
private: isPreviewMode,
stateful: false, // GSP response
revalidate:
cachedData.curRevalidate !== undefined
? cachedData.curRevalidate
: /* default to minimum revalidate (this should be an invariant) */ 1,
}
: undefined
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
)
// Stop the request chain here if the data we sent was up-to-date
if (!cachedData.isStale) {
return null
}
}
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
// If we're here, that means data is missing or it's stale.
const maybeCoalesceInvoke = ssgCacheKey
? (fn: any) => withCoalescedInvoke(fn).bind(null, ssgCacheKey, [])
: (fn: any) => async () => {
const value = await fn()
return { isOrigin: true, value }
}
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
const doRender = maybeCoalesceInvoke(
async (): Promise<{
html: string | null
pageData: any
sprRevalidate: number | false
isNotFound?: boolean
}> => {
let pageData: any
let html: string | null
let sprRevalidate: number | false
let isNotFound: boolean | undefined
let renderResult
// handle serverless
if (isLikeServerless) {
renderResult = await (components.Component as any).renderReqToHTML(
req,
res,
'passthrough',
{
fontManifest: this.renderOpts.fontManifest,
locale,
locales,
// defaultLocale,
}
)
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
html = renderResult.html
pageData = renderResult.renderOpts.pageData
sprRevalidate = renderResult.renderOpts.revalidate
isNotFound = renderResult.renderOpts.ssgNotFound
} else {
const origQuery = parseUrl(req.url || '', true).query
const resolvedUrl = formatUrl({
pathname: resolvedUrlPathname,
// make sure to only add query values from original URL
query: origQuery,
})
const renderOpts: RenderOpts = {
...components,
...opts,
isDataReq,
resolvedUrl,
locale,
locales,
// defaultLocale,
// For getServerSideProps we need to ensure we use the original URL
// and not the resolved URL to prevent a hydration mismatch on
// asPath
resolvedAsPath: isServerProps
? formatUrl({
// we use the original URL pathname less the _next/data prefix if
// present
pathname: urlPathname,
query: origQuery,
})
: resolvedUrl,
}
renderResult = await renderToHTML(
req,
res,
pathname,
query,
renderOpts
)
html = renderResult
// TODO: change this to a different passing mechanism
pageData = (renderOpts as any).pageData
sprRevalidate = (renderOpts as any).revalidate
isNotFound = (renderOpts as any).ssgNotFound
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
}
return { html, pageData, sprRevalidate, isNotFound }
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
}
)
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
const isProduction = !this.renderOpts.dev
const isDynamicPathname = isDynamicRoute(pathname)
const didRespond = isResSent(res)
const { staticPaths, fallbackMode } = hasStaticPaths
? await this.getStaticPaths(pathname)
: { staticPaths: undefined, fallbackMode: false }
// When we did not respond from cache, we need to choose to block on
// rendering or return a skeleton.
//
// * Data requests always block.
//
// * Blocking mode fallback always blocks.
//
// * Preview mode toggles all pages to be resolved in a blocking manner.
//
// * Non-dynamic pages should block (though this is an impossible
// case in production).
//
// * Dynamic pages should return their skeleton if not defined in
// getStaticPaths, then finish the data request on the client-side.
//
if (
fallbackMode !== 'blocking' &&
ssgCacheKey &&
!didRespond &&
!isPreviewMode &&
isDynamicPathname &&
// Development should trigger fallback when the path is not in
// `getStaticPaths`
(isProduction ||
!staticPaths ||
// static paths always includes locale so make sure it's prefixed
// with it
!staticPaths.includes(
`${locale ? '/' + locale : ''}${resolvedUrlPathname}`
))
) {
if (
// In development, fall through to render to handle missing
// getStaticPaths.
(isProduction || staticPaths) &&
// When fallback isn't present, abort this render so we 404
fallbackMode !== 'static'
) {
throw new NoFallbackError()
}
if (!isDataReq) {
let html: string
// Production already emitted the fallback as static HTML.
if (isProduction) {
html = await this.incrementalCache.getFallback(
locale ? `/${locale}${pathname}` : pathname
)
}
// We need to generate the fallback on-demand for development.
else {
query.__nextFallback = 'true'
if (isLikeServerless) {
prepareServerlessUrl(req, query)
}
const { value: renderResult } = await doRender()
html = renderResult.html
}
sendPayload(req, res, html, 'html', {
generateEtags: this.renderOpts.generateEtags,
poweredByHeader: this.renderOpts.poweredByHeader,
})
return null
}
}
const {
isOrigin,
value: { html, pageData, sprRevalidate, isNotFound },
} = await doRender()
let resHtml = html
if (
!isResSent(res) &&
!isNotFound &&
(isSSG || isDataReq || isServerProps)
) {
sendPayload(
req,
res,
isDataReq ? JSON.stringify(pageData) : html,
isDataReq ? 'json' : 'html',
{
generateEtags: this.renderOpts.generateEtags,
poweredByHeader: this.renderOpts.poweredByHeader,
},
!this.renderOpts.dev || (isServerProps && !isDataReq)
? {
private: isPreviewMode,
stateful: !isSSG,
revalidate: sprRevalidate,
}
: undefined
)
resHtml = null
}
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
// Update the cache if the head request and cacheable
if (isOrigin && ssgCacheKey) {
await this.incrementalCache.set(
ssgCacheKey,
{ html: html!, pageData, isNotFound },
sprRevalidate
)
}
if (isNotFound) {
throw new NoFallbackError()
}
return resHtml
}
public async renderToHTML(
2019-03-01 20:51:13 +01:00
req: IncomingMessage,
res: ServerResponse,
pathname: string,
query: ParsedUrlQuery = {}
2019-03-01 20:51:13 +01:00
): Promise<string | null> {
try {
const result = await this.findPageComponents(pathname, query)
if (result) {
try {
return await this.renderToHTMLWithComponents(
req,
res,
pathname,
result,
{ ...this.renderOpts }
)
} catch (err) {
if (!(err instanceof NoFallbackError)) {
throw err
}
}
}
if (this.dynamicRoutes) {
for (const dynamicRoute of this.dynamicRoutes) {
const params = dynamicRoute.match(pathname)
if (!params) {
continue
}
const dynamicRouteResult = await this.findPageComponents(
dynamicRoute.page,
query,
params
)
if (dynamicRouteResult) {
try {
return await this.renderToHTMLWithComponents(
req,
res,
dynamicRoute.page,
dynamicRouteResult,
{ ...this.renderOpts, params }
)
} catch (err) {
if (!(err instanceof NoFallbackError)) {
throw err
}
}
}
}
}
} catch (err) {
this.logError(err)
if (err && err.code === 'DECODE_FAILED') {
res.statusCode = 400
return await this.renderErrorToHTML(err, req, res, pathname, query)
}
res.statusCode = 500
return await this.renderErrorToHTML(err, req, res, pathname, query)
}
res.statusCode = 404
return await this.renderErrorToHTML(null, req, res, pathname, query)
}
2019-03-01 20:51:13 +01:00
public async renderError(
err: Error | null,
req: IncomingMessage,
res: ServerResponse,
pathname: string,
query: ParsedUrlQuery = {}
2019-03-01 20:51:13 +01:00
): Promise<void> {
res.setHeader(
'Cache-Control',
'no-cache, no-store, max-age=0, must-revalidate'
2019-03-01 20:51:13 +01:00
)
const html = await this.renderErrorToHTML(err, req, res, pathname, query)
if (html === null) {
return
}
return this.sendHTML(req, res, html)
2016-10-06 01:52:50 +02:00
}
private customErrorNo404Warn = execOnce(() => {
console.warn(
chalk.bold.yellow(`Warning: `) +
chalk.yellow(
`You have added a custom /_error page without a custom /404 page. This prevents the 404 page from being auto statically optimized.\nSee here for info: https://err.sh/next.js/custom-error-no-custom-404`
)
)
})
2019-03-01 20:51:13 +01:00
public async renderErrorToHTML(
err: Error | null,
req: IncomingMessage,
res: ServerResponse,
_pathname: string,
query: ParsedUrlQuery = {}
2019-03-01 20:51:13 +01:00
) {
let result: null | FindComponentsResult = null
const is404 = res.statusCode === 404
let using404Page = false
// use static 404 page if available and is 404 response
if (is404) {
result = await this.findPageComponents('/404', query)
using404Page = result !== null
}
if (!result) {
result = await this.findPageComponents('/_error', query)
}
if (
process.env.NODE_ENV !== 'production' &&
!using404Page &&
(await this.hasPage('/_error')) &&
!(await this.hasPage('/404'))
) {
this.customErrorNo404Warn()
}
let html: string | null
try {
try {
html = await this.renderToHTMLWithComponents(
req,
res,
using404Page ? '/404' : '/_error',
result!,
{
...this.renderOpts,
err,
}
)
} catch (maybeFallbackError) {
if (maybeFallbackError instanceof NoFallbackError) {
throw new Error('invariant: failed to render error page')
}
throw maybeFallbackError
}
} catch (renderToHtmlError) {
console.error(renderToHtmlError)
res.statusCode = 500
html = 'Internal Server Error'
}
return html
}
2019-03-01 20:51:13 +01:00
public async render404(
req: IncomingMessage,
res: ServerResponse,
parsedUrl?: UrlWithParsedQuery
2019-03-01 20:51:13 +01:00
): Promise<void> {
const url: any = req.url
const { pathname, query } = parsedUrl ? parsedUrl : parseUrl(url, true)
res.statusCode = 404
return this.renderError(null, req, res, pathname!, query)
}
2019-03-01 20:51:13 +01:00
public async serveStatic(
req: IncomingMessage,
res: ServerResponse,
path: string,
parsedUrl?: UrlWithParsedQuery
2019-03-01 20:51:13 +01:00
): Promise<void> {
2017-06-01 02:16:32 +02:00
if (!this.isServeableUrl(path)) {
return this.render404(req, res, parsedUrl)
2017-06-01 02:16:32 +02:00
}
if (!(req.method === 'GET' || req.method === 'HEAD')) {
res.statusCode = 405
res.setHeader('Allow', ['GET', 'HEAD'])
return this.renderError(null, req, res, path)
}
try {
await serveStatic(req, res, path)
} catch (err) {
2018-11-30 17:09:23 +01:00
if (err.code === 'ENOENT' || err.statusCode === 404) {
this.render404(req, res, parsedUrl)
} else if (err.statusCode === 412) {
res.statusCode = 412
return this.renderError(err, req, res, path)
} else {
throw err
}
}
}
private _validFilesystemPathSet: Set<string> | null = null
private getFilesystemPaths(): Set<string> {
if (this._validFilesystemPathSet) {
return this._validFilesystemPathSet
}
const pathUserFilesStatic = join(this.dir, 'static')
let userFilesStatic: string[] = []
if (this.hasStaticDir && fs.existsSync(pathUserFilesStatic)) {
2020-05-18 21:24:37 +02:00
userFilesStatic = recursiveReadDirSync(pathUserFilesStatic).map((f) =>
join('.', 'static', f)
)
}
let userFilesPublic: string[] = []
if (this.publicDir && fs.existsSync(this.publicDir)) {
2020-05-18 21:24:37 +02:00
userFilesPublic = recursiveReadDirSync(this.publicDir).map((f) =>
join('.', 'public', f)
)
}
let nextFilesStatic: string[] = []
nextFilesStatic = recursiveReadDirSync(
join(this.distDir, 'static')
2020-05-18 21:24:37 +02:00
).map((f) => join('.', relative(this.dir, this.distDir), 'static', f))
return (this._validFilesystemPathSet = new Set<string>([
...nextFilesStatic,
...userFilesPublic,
...userFilesStatic,
]))
}
protected isServeableUrl(untrustedFileUrl: string): boolean {
// This method mimics what the version of `send` we use does:
// 1. decodeURIComponent:
// https://github.com/pillarjs/send/blob/0.17.1/index.js#L989
// https://github.com/pillarjs/send/blob/0.17.1/index.js#L518-L522
// 2. resolve:
// https://github.com/pillarjs/send/blob/de073ed3237ade9ff71c61673a34474b30e5d45b/index.js#L561
let decodedUntrustedFilePath: string
try {
// (1) Decode the URL so we have the proper file name
decodedUntrustedFilePath = decodeURIComponent(untrustedFileUrl)
} catch {
return false
}
// (2) Resolve "up paths" to determine real request
const untrustedFilePath = resolve(decodedUntrustedFilePath)
// don't allow null bytes anywhere in the file path
if (untrustedFilePath.indexOf('\0') !== -1) {
return false
}
// Check if .next/static, static and public are in the path.
// If not the path is not available.
2017-06-01 02:16:32 +02:00
if (
(untrustedFilePath.startsWith(join(this.distDir, 'static') + sep) ||
untrustedFilePath.startsWith(join(this.dir, 'static') + sep) ||
untrustedFilePath.startsWith(join(this.dir, 'public') + sep)) === false
2017-06-01 02:16:32 +02:00
) {
return false
}
// Check against the real filesystem paths
const filesystemUrls = this.getFilesystemPaths()
const resolved = relative(this.dir, untrustedFilePath)
return filesystemUrls.has(resolved)
2017-06-01 02:16:32 +02:00
}
protected readBuildId(): string {
const buildIdFile = join(this.distDir, BUILD_ID_FILE)
try {
return fs.readFileSync(buildIdFile, 'utf8').trim()
} catch (err) {
if (!fs.existsSync(buildIdFile)) {
2019-03-01 20:51:13 +01:00
throw new Error(
`Could not find a valid build in the '${this.distDir}' directory! Try building your app with 'next build' before starting the server.`
2019-03-01 20:51:13 +01:00
)
}
throw err
}
}
protected get _isLikeServerless(): boolean {
return isTargetLikeServerless(this.nextConfig.target)
}
}
function prepareServerlessUrl(
req: IncomingMessage,
query: ParsedUrlQuery
): void {
const curUrl = parseUrl(req.url!, true)
req.url = formatUrl({
...curUrl,
search: undefined,
query: {
...curUrl.query,
...query,
},
})
}
class NoFallbackError extends Error {}