2022-08-09 21:34:25 +02:00
import type { __ApiPreviewProps } from './api-utils'
2022-01-26 07:22:11 +01:00
import type { CustomRoutes } from '../lib/load-custom-routes'
2021-12-05 22:53:11 +01:00
import type { DomainLocale } from './config'
2022-05-19 17:46:21 +02:00
import type { DynamicRoutes , PageChecker , Route } from './router'
2022-09-16 23:13:21 +02:00
import type { FontManifest , FontConfig } from './font-utils'
2021-12-05 22:53:11 +01:00
import type { LoadComponentsReturnType } from './load-components'
2022-05-19 17:46:21 +02:00
import type { RouteMatch } from '../shared/lib/router/utils/route-matcher'
feat(next): Support has match and locale option on middleware config (#39257)
## Feature
As the title, support `has` match, `local` that works the same with the `rewrites` and `redirects` of next.config.js on middleware config. With this PR, you can write the config like the following:
```js
export const config = {
matcher: [
"/foo",
{ source: "/bar" },
{
source: "/baz",
has: [
{
type: 'header',
key: 'x-my-header',
value: 'my-value',
}
]
},
{
source: "/en/asdf",
locale: false,
},
]
}
```
Also, fixes https://github.com/vercel/next.js/issues/39428
related https://github.com/vercel/edge-functions/issues/178, https://github.com/vercel/edge-functions/issues/179
- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-08-31 18:23:30 +02:00
import type { MiddlewareRouteMatch } from '../shared/lib/router/utils/middleware-route-matcher'
2022-05-19 17:46:21 +02:00
import type { Params } from '../shared/lib/router/utils/route-matcher'
2022-08-07 21:16:10 +02:00
import type { NextConfig , NextConfigComplete } from './config-shared'
2021-12-05 22:53:11 +01:00
import type { NextParsedUrlQuery , NextUrlWithParsedQuery } from './request-meta'
import type { ParsedUrlQuery } from 'querystring'
import type { RenderOpts , RenderOptsPartial } from './render'
2022-08-12 17:25:47 +02:00
import type {
ResponseCacheBase ,
ResponseCacheEntry ,
ResponseCacheValue ,
} from './response-cache'
2021-12-05 22:53:11 +01:00
import type { UrlWithParsedQuery } from 'url'
2022-05-01 11:23:06 +02:00
import {
NormalizeError ,
DecodeError ,
normalizeRepeatedSlashes ,
2022-06-27 21:15:09 +02:00
MissingStaticPage ,
2022-05-01 11:23:06 +02:00
} from '../shared/lib/utils'
2022-08-07 21:16:10 +02:00
import type { PreviewData , ServerRuntime } from 'next/types'
2022-01-19 13:36:06 +01:00
import type { PagesManifest } from '../build/webpack/plugins/pages-manifest-plugin'
import type { BaseNextRequest , BaseNextResponse } from './base-http'
2022-02-24 20:53:17 +01:00
import type { PayloadOptions } from './send-payload'
2022-08-16 16:47:27 +02:00
import type { PrerenderManifest } from '../build'
2022-09-22 07:12:59 +02:00
import type { FontLoaderManifest } from '../build/webpack/plugins/font-loader-manifest-plugin'
2021-12-05 22:53:11 +01:00
2022-01-26 07:22:11 +01:00
import { parse as parseQs } from 'querystring'
2021-12-05 22:53:11 +01:00
import { format as formatUrl , parse as parseUrl } from 'url'
2022-08-09 21:34:25 +02:00
import { getRedirectStatus } from '../lib/redirect-status'
2021-12-05 22:53:11 +01:00
import {
2022-05-02 22:52:46 +02:00
NEXT_BUILTIN_DOCUMENT ,
2021-12-05 22:53:11 +01:00
STATIC_STATUS_PAGES ,
TEMPORARY_REDIRECT_STATUS ,
} from '../shared/lib/constants'
2022-05-19 17:46:21 +02:00
import { getSortedRoutes , isDynamicRoute } from '../shared/lib/router/utils'
2022-02-11 20:56:25 +01:00
import {
setLazyProp ,
getCookieParser ,
checkIsManualRevalidate ,
} from './api-utils'
2022-09-29 10:56:28 +02:00
import { setConfig } from '../shared/lib/runtime-config'
2022-04-27 11:50:29 +02:00
import Router from './router'
2022-08-16 16:47:27 +02:00
2022-02-24 20:53:17 +01:00
import { setRevalidateHeaders } from './send-payload/revalidate-headers'
2021-12-05 22:53:11 +01:00
import { execOnce } from '../shared/lib/utils'
2022-09-13 02:27:43 +02:00
import { isBlockedPage } from './utils'
import { isBot } from '../shared/lib/router/utils/is-bot'
2021-12-05 22:53:11 +01:00
import RenderResult from './render-result'
2022-05-27 20:29:04 +02:00
import { removeTrailingSlash } from '../shared/lib/router/utils/remove-trailing-slash'
2022-04-30 13:19:27 +02:00
import { denormalizePagePath } from '../shared/lib/page-path/denormalize-page-path'
2021-12-05 22:53:11 +01:00
import { normalizeLocalePath } from '../shared/lib/i18n/normalize-locale-path'
import * as Log from '../build/output/log'
import { detectDomainLocale } from '../shared/lib/i18n/detect-domain-locale'
import escapePathDelimiters from '../shared/lib/router/utils/escape-path-delimiters'
import { getUtils } from '../build/webpack/loaders/next-serverless-loader/utils'
2022-01-11 21:40:03 +01:00
import isError , { getProperError } from '../lib/is-error'
2021-12-05 22:53:11 +01:00
import { addRequestMeta , getRequestMeta } from './request-meta'
2022-08-16 16:47:27 +02:00
2022-02-22 15:27:18 +01:00
import { ImageConfigComplete } from '../shared/lib/image-config'
2022-05-27 20:29:04 +02:00
import { removePathPrefix } from '../shared/lib/router/utils/remove-path-prefix'
2022-10-12 09:17:17 +02:00
import {
normalizeAppPath ,
normalizeRscPath ,
} from '../shared/lib/router/utils/app-paths'
2022-05-19 17:46:21 +02:00
import { getRouteMatcher } from '../shared/lib/router/utils/route-matcher'
import { getRouteRegex } from '../shared/lib/router/utils/route-regex'
2022-05-27 20:29:04 +02:00
import { getHostname } from '../shared/lib/get-hostname'
import { parseUrl as parseUrlUtil } from '../shared/lib/router/utils/parse-url'
import { getNextPathnameInfo } from '../shared/lib/router/utils/get-next-pathname-info'
feat(next): Support has match and locale option on middleware config (#39257)
## Feature
As the title, support `has` match, `local` that works the same with the `rewrites` and `redirects` of next.config.js on middleware config. With this PR, you can write the config like the following:
```js
export const config = {
matcher: [
"/foo",
{ source: "/bar" },
{
source: "/baz",
has: [
{
type: 'header',
key: 'x-my-header',
value: 'my-value',
}
]
},
{
source: "/en/asdf",
locale: false,
},
]
}
```
Also, fixes https://github.com/vercel/next.js/issues/39428
related https://github.com/vercel/edge-functions/issues/178, https://github.com/vercel/edge-functions/issues/179
- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-08-31 18:23:30 +02:00
import { MiddlewareMatcher } from '../build/analysis/get-page-static-info'
2022-11-08 01:35:32 +01:00
import { RSC , RSC_VARY_HEADER } from '../client/components/app-router-headers'
import { FLIGHT_PARAMETERS } from './app-render'
2021-12-05 22:53:11 +01:00
export type FindComponentsResult = {
components : LoadComponentsReturnType
query : NextParsedUrlQuery
}
2022-05-19 17:46:21 +02:00
export interface RoutingItem {
2022-04-06 16:35:52 +02:00
page : string
2022-05-19 17:46:21 +02:00
match : RouteMatch
2022-06-21 21:04:48 +02:00
re? : RegExp
2022-04-06 16:35:52 +02:00
}
feat(next): Support has match and locale option on middleware config (#39257)
## Feature
As the title, support `has` match, `local` that works the same with the `rewrites` and `redirects` of next.config.js on middleware config. With this PR, you can write the config like the following:
```js
export const config = {
matcher: [
"/foo",
{ source: "/bar" },
{
source: "/baz",
has: [
{
type: 'header',
key: 'x-my-header',
value: 'my-value',
}
]
},
{
source: "/en/asdf",
locale: false,
},
]
}
```
Also, fixes https://github.com/vercel/next.js/issues/39428
related https://github.com/vercel/edge-functions/issues/178, https://github.com/vercel/edge-functions/issues/179
- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-08-31 18:23:30 +02:00
export interface MiddlewareRoutingItem {
page : string
match : MiddlewareRouteMatch
matchers? : MiddlewareMatcher [ ]
}
2021-12-05 22:53:11 +01:00
export interface Options {
/ * *
* Object containing the configuration next . config . js
* /
conf : NextConfig
/ * *
* Set to false when the server was created by Next . js
* /
customServer? : boolean
/ * *
* Tells if Next . js is running in dev mode
* /
dev? : boolean
/ * *
* Where the Next project is located
* /
dir? : string
/ * *
* Tells if Next . js is running in a Serverless platform
* /
minimalMode? : boolean
/ * *
* Hide error messages containing server information
* /
quiet? : boolean
/ * *
* The hostname the server is running behind
* /
hostname? : string
/ * *
* The port the server is running behind
* /
port? : number
2022-08-10 19:00:30 +02:00
/ * *
* The HTTP Server that Next . js is running behind
* /
httpServer? : import ( 'http' ) . Server
2021-12-05 22:53:11 +01:00
}
2022-01-14 22:01:35 +01:00
export interface BaseRequestHandler {
2021-12-05 22:53:11 +01:00
(
2022-01-14 22:01:35 +01:00
req : BaseNextRequest ,
res : BaseNextResponse ,
2021-12-05 22:53:11 +01:00
parsedUrl? : NextUrlWithParsedQuery | undefined
) : Promise < void >
}
2022-08-16 20:05:03 +02:00
export type RequestContext = {
2022-01-14 22:01:35 +01:00
req : BaseNextRequest
res : BaseNextResponse
2021-12-05 22:53:11 +01:00
pathname : string
query : NextParsedUrlQuery
renderOpts : RenderOptsPartial
}
2022-08-16 16:47:27 +02:00
export class NoFallbackError extends Error { }
2022-08-11 23:32:52 +02:00
// Internal wrapper around build errors at development
// time, to prevent us from propagating or logging them
export class WrappedBuildError extends Error {
innerError : Error
constructor ( innerError : Error ) {
super ( )
this . innerError = innerError
}
}
type ResponsePayload = {
2022-10-06 21:43:23 +02:00
type : 'html' | 'json' | 'rsc'
2022-08-11 23:32:52 +02:00
body : RenderResult
revalidateOptions? : any
}
2022-04-28 18:16:51 +02:00
export default abstract class Server < ServerOptions extends Options = Options > {
2021-12-05 22:53:11 +01:00
protected dir : string
protected quiet : boolean
protected nextConfig : NextConfigComplete
protected distDir : string
protected publicDir : string
protected hasStaticDir : boolean
2022-10-05 00:16:44 +02:00
protected hasAppDir : boolean
2021-12-05 22:53:11 +01:00
protected pagesManifest? : PagesManifest
2022-05-25 11:46:26 +02:00
protected appPathsManifest? : PagesManifest
2021-12-05 22:53:11 +01:00
protected buildId : string
protected minimalMode : boolean
protected renderOpts : {
poweredByHeader : boolean
buildId : string
generateEtags : boolean
runtimeConfig ? : { [ key : string ] : any }
assetPrefix? : string
canonicalBase : string
dev? : boolean
previewProps : __ApiPreviewProps
customServer? : boolean
ampOptimizerConfig ? : { [ key : string ] : any }
basePath : string
2022-09-16 23:13:21 +02:00
optimizeFonts : FontConfig
2022-02-09 01:55:53 +01:00
images : ImageConfigComplete
2022-01-12 19:12:36 +01:00
fontManifest? : FontManifest
2021-12-05 22:53:11 +01:00
disableOptimizedLoading? : boolean
optimizeCss : any
2022-03-11 23:26:46 +01:00
nextScriptWorkers : any
2021-12-05 22:53:11 +01:00
locale? : string
locales? : string [ ]
defaultLocale? : string
domainLocales? : DomainLocale [ ]
distDir : string
2022-06-27 03:02:24 +02:00
runtime? : ServerRuntime
2022-01-14 14:01:00 +01:00
serverComponents? : boolean
2021-12-05 22:53:11 +01:00
crossOrigin? : string
2022-02-01 23:36:47 +01:00
supportsDynamicHTML? : boolean
2022-10-24 22:38:30 +02:00
isBot? : boolean
2022-02-01 23:36:47 +01:00
serverComponentManifest? : any
2022-08-24 21:49:47 +02:00
serverCSSManifest? : any
2022-09-22 07:12:59 +02:00
fontLoaderManifest? : FontLoaderManifest
2022-02-01 23:36:47 +01:00
renderServerComponentData? : boolean
serverComponentProps? : any
2022-05-29 03:39:48 +02:00
largePageDataBytes? : number
2021-12-05 22:53:11 +01:00
}
2022-04-28 18:16:51 +02:00
protected serverOptions : ServerOptions
2022-08-12 17:25:47 +02:00
private responseCache : ResponseCacheBase
2021-12-05 22:53:11 +01:00
protected router : Router
protected dynamicRoutes? : DynamicRoutes
2022-09-06 19:03:21 +02:00
protected appPathRoutes? : Record < string , string [ ] >
2021-12-05 22:53:11 +01:00
protected customRoutes : CustomRoutes
2022-02-08 14:16:46 +01:00
protected serverComponentManifest? : any
2022-08-12 15:01:19 +02:00
protected serverCSSManifest? : any
2022-09-22 07:12:59 +02:00
protected fontLoaderManifest? : FontLoaderManifest
2021-12-05 22:53:11 +01:00
public readonly hostname? : string
public readonly port? : number
2022-01-21 17:24:57 +01:00
protected abstract getPublicDir ( ) : string
2021-12-07 02:14:55 +01:00
protected abstract getHasStaticDir ( ) : boolean
2022-10-06 21:43:23 +02:00
protected abstract getHasAppDir ( dev : boolean ) : boolean
2021-12-07 02:14:55 +01:00
protected abstract getPagesManifest ( ) : PagesManifest | undefined
2022-05-25 11:46:26 +02:00
protected abstract getAppPathsManifest ( ) : PagesManifest | undefined
2021-12-07 02:14:55 +01:00
protected abstract getBuildId ( ) : string
2022-08-16 16:47:27 +02:00
2021-12-07 02:14:55 +01:00
protected abstract getFilesystemPaths ( ) : Set < string >
2022-09-06 19:03:21 +02:00
protected abstract findPageComponents ( params : {
pathname : string
query : NextParsedUrlQuery
params : Params
2022-09-03 02:13:47 +02:00
isAppPath : boolean
2022-09-06 19:03:21 +02:00
appPaths? : string [ ] | null
2022-09-09 00:17:15 +02:00
sriEnabled? : boolean
2022-09-06 19:03:21 +02:00
} ) : Promise < FindComponentsResult | null >
2022-01-12 19:12:36 +01:00
protected abstract getFontManifest ( ) : FontManifest | undefined
2022-01-26 07:22:11 +01:00
protected abstract getPrerenderManifest ( ) : PrerenderManifest
2022-02-08 14:16:46 +01:00
protected abstract getServerComponentManifest ( ) : any
2022-08-12 15:01:19 +02:00
protected abstract getServerCSSManifest ( ) : any
2022-09-22 07:12:59 +02:00
protected abstract getFontLoaderManifest ( ) : FontLoaderManifest | undefined
2022-07-22 20:42:35 +02:00
protected abstract attachRequestMeta (
req : BaseNextRequest ,
parsedUrl : NextUrlWithParsedQuery
) : void
2022-08-12 17:25:47 +02:00
protected abstract getFallback ( page : string ) : Promise < string >
2022-08-16 16:47:27 +02:00
protected abstract getCustomRoutes ( ) : CustomRoutes
protected abstract hasPage ( pathname : string ) : Promise < boolean >
protected abstract generateRoutes ( ) : {
headers : Route [ ]
rewrites : {
beforeFiles : Route [ ]
afterFiles : Route [ ]
fallback : Route [ ]
}
fsRoutes : Route [ ]
redirects : Route [ ]
catchAllRoute : Route
catchAllMiddleware : Route [ ]
pageChecker : PageChecker
useFileSystemPublicRoutes : boolean
dynamicRoutes : DynamicRoutes | undefined
nextConfig : NextConfig
}
2021-12-07 02:14:55 +01:00
2022-01-14 22:01:35 +01:00
protected abstract sendRenderResult (
req : BaseNextRequest ,
res : BaseNextResponse ,
options : {
result : RenderResult
2022-10-06 21:43:23 +02:00
type : 'html' | 'json' | 'rsc'
2022-01-14 22:01:35 +01:00
generateEtags : boolean
poweredByHeader : boolean
options? : PayloadOptions
}
) : Promise < void >
protected abstract runApi (
req : BaseNextRequest ,
res : BaseNextResponse ,
query : ParsedUrlQuery ,
2022-06-21 21:04:48 +02:00
params : Params | undefined ,
2022-01-14 22:01:35 +01:00
page : string ,
builtPagePath : string
) : Promise < boolean >
protected abstract renderHTML (
req : BaseNextRequest ,
res : BaseNextResponse ,
pathname : string ,
query : NextParsedUrlQuery ,
renderOpts : RenderOpts
) : Promise < RenderResult | null >
protected abstract handleCompression (
req : BaseNextRequest ,
res : BaseNextResponse
) : void
2022-08-12 17:25:47 +02:00
protected abstract getResponseCache ( options : {
dev : boolean
} ) : ResponseCacheBase
2022-08-11 05:27:48 +02:00
protected abstract loadEnvConfig ( params : {
dev : boolean
forceReload? : boolean
} ) : void
2022-01-20 22:25:44 +01:00
2022-04-28 18:16:51 +02:00
public constructor ( options : ServerOptions ) {
const {
dir = '.' ,
quiet = false ,
conf ,
dev = false ,
minimalMode = false ,
customServer = true ,
hostname ,
port ,
} = options
this . serverOptions = options
2022-08-12 17:25:47 +02:00
this . dir =
process . env . NEXT_RUNTIME === 'edge' ? dir : require ( 'path' ) . resolve ( dir )
2021-12-05 22:53:11 +01:00
this . quiet = quiet
2022-01-20 22:25:44 +01:00
this . loadEnvConfig ( { dev } )
2021-12-05 22:53:11 +01:00
// TODO: should conf be normalized to prevent missing
// values from causing issues as this can be user provided
this . nextConfig = conf as NextConfigComplete
this . hostname = hostname
this . port = port
2022-08-12 17:25:47 +02:00
this . distDir =
process . env . NEXT_RUNTIME === 'edge'
? this . nextConfig . distDir
: require ( 'path' ) . join ( this . dir , this . nextConfig . distDir )
2022-01-21 17:24:57 +01:00
this . publicDir = this . getPublicDir ( )
2021-12-07 02:14:55 +01:00
this . hasStaticDir = ! minimalMode && this . getHasStaticDir ( )
2021-12-05 22:53:11 +01:00
// Only serverRuntimeConfig needs the default
// publicRuntimeConfig gets it's default in client/index.js
const {
serverRuntimeConfig = { } ,
publicRuntimeConfig ,
assetPrefix ,
generateEtags ,
} = this . nextConfig
2021-12-07 02:14:55 +01:00
this . buildId = this . getBuildId ( )
2022-02-11 18:43:39 +01:00
this . minimalMode = minimalMode || ! ! process . env . NEXT_PRIVATE_MINIMAL_MODE
2021-12-05 22:53:11 +01:00
2022-10-07 00:16:42 +02:00
this . hasAppDir =
! ! this . nextConfig . experimental . appDir && this . getHasAppDir ( dev )
2022-10-05 00:16:44 +02:00
const serverComponents = this . hasAppDir
2022-02-08 14:16:46 +01:00
this . serverComponentManifest = serverComponents
? this . getServerComponentManifest ( )
: undefined
2022-08-12 15:01:19 +02:00
this . serverCSSManifest = serverComponents
? this . getServerCSSManifest ( )
: undefined
2022-09-22 07:12:59 +02:00
this . fontLoaderManifest = this . nextConfig . experimental . fontLoaders
? this . getFontLoaderManifest ( )
: undefined
2022-02-08 14:16:46 +01:00
2021-12-05 22:53:11 +01:00
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 ,
2022-02-09 01:55:53 +01:00
images : this.nextConfig.images ,
2022-09-16 23:13:21 +02:00
optimizeFonts : this.nextConfig.optimizeFonts as FontConfig ,
2021-12-05 22:53:11 +01:00
fontManifest :
2022-09-16 23:13:21 +02:00
( this . nextConfig . optimizeFonts as FontConfig ) && ! dev
2022-01-12 19:12:36 +01:00
? this . getFontManifest ( )
: undefined ,
2021-12-05 22:53:11 +01:00
optimizeCss : this.nextConfig.experimental.optimizeCss ,
2022-03-11 23:26:46 +01:00
nextScriptWorkers : this.nextConfig.experimental.nextScriptWorkers ,
2022-02-08 14:16:46 +01:00
disableOptimizedLoading : this.nextConfig.experimental.runtime
? true
: this . nextConfig . experimental . disableOptimizedLoading ,
2021-12-05 22:53:11 +01:00
domainLocales : this.nextConfig.i18n?.domains ,
distDir : this.distDir ,
2022-02-08 14:16:46 +01:00
runtime : this.nextConfig.experimental.runtime ,
serverComponents ,
2021-12-05 22:53:11 +01:00
crossOrigin : this.nextConfig.crossOrigin
? this . nextConfig . crossOrigin
: undefined ,
2022-05-29 03:39:48 +02:00
largePageDataBytes : this.nextConfig.experimental.largePageDataBytes ,
2021-12-05 22:53:11 +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 ) {
this . renderOpts . runtimeConfig = publicRuntimeConfig
}
// Initialize next/config with the environment configuration
2022-09-29 10:56:28 +02:00
setConfig ( {
2021-12-05 22:53:11 +01:00
serverRuntimeConfig ,
publicRuntimeConfig ,
} )
2021-12-07 02:14:55 +01:00
this . pagesManifest = this . getPagesManifest ( )
2022-05-25 11:46:26 +02:00
this . appPathsManifest = this . getAppPathsManifest ( )
2021-12-05 22:53:11 +01:00
this . customRoutes = this . getCustomRoutes ( )
this . router = new Router ( this . generateRoutes ( ) )
this . setAssetPrefix ( assetPrefix )
2022-08-12 17:25:47 +02:00
this . responseCache = this . getResponseCache ( { dev } )
2021-12-05 22:53:11 +01:00
}
public logError ( err : Error ) : void {
if ( this . quiet ) return
console . error ( err )
}
private async handleRequest (
2022-01-14 22:01:35 +01:00
req : BaseNextRequest ,
res : BaseNextResponse ,
2021-12-05 22:53:11 +01:00
parsedUrl? : NextUrlWithParsedQuery
) : Promise < void > {
try {
2022-10-04 19:08:17 +02:00
// ensure cookies set in middleware are merged and
// not overridden by API routes/getServerSideProps
const _res = ( res as any ) . originalResponse || res
const origSetHeader = _res . setHeader . bind ( _res )
_res . setHeader = ( name : string , val : string | string [ ] ) = > {
if ( name . toLowerCase ( ) === 'set-cookie' ) {
const middlewareValue = getRequestMeta ( req , '_nextMiddlewareCookie' )
if (
! middlewareValue ||
! Array . isArray ( val ) ||
! val . every ( ( item , idx ) = > item === middlewareValue [ idx ] )
) {
val = [
. . . ( middlewareValue || [ ] ) ,
. . . ( typeof val === 'string'
? [ val ]
: Array . isArray ( val )
? val
: [ ] ) ,
]
}
}
return origSetHeader ( name , val )
}
2021-12-05 22:53:11 +01:00
const urlParts = ( req . url || '' ) . split ( '?' )
const urlNoQuery = urlParts [ 0 ]
2022-08-16 14:41:22 +02:00
// this normalizes repeated slashes in the path e.g. hello//world ->
// hello/world or backslashes to forward slashes, this does not
// handle trailing slash as that is handled the same as a next.config.js
// redirect
2021-12-05 22:53:11 +01:00
if ( urlNoQuery ? . match ( /(\\|\/\/)/ ) ) {
const cleanUrl = normalizeRepeatedSlashes ( req . url ! )
2022-01-14 22:01:35 +01:00
res . redirect ( cleanUrl , 308 ) . body ( cleanUrl ) . send ( )
2021-12-05 22:53:11 +01:00
return
}
setLazyProp ( { req : req as any } , 'cookies' , getCookieParser ( req . headers ) )
// Parse url if parsedUrl not provided
if ( ! parsedUrl || typeof parsedUrl !== 'object' ) {
parsedUrl = parseUrl ( req . url ! , true )
}
// Parse the querystring ourselves if the user doesn't handle querystring parsing
if ( typeof parsedUrl . query === 'string' ) {
parsedUrl . query = parseQs ( parsedUrl . query )
}
2022-11-05 00:37:00 +01:00
// in minimal mode we detect RSC revalidate if the .rsc
// path is requested
if ( this . minimalMode && req . url . endsWith ( '.rsc' ) ) {
2022-10-19 23:38:54 +02:00
parsedUrl . query . __nextDataReq = '1'
}
2022-10-12 09:17:17 +02:00
req . url = normalizeRscPath ( req . url , this . hasAppDir )
parsedUrl . pathname = normalizeRscPath (
parsedUrl . pathname || '' ,
this . hasAppDir
)
2021-12-05 22:53:11 +01:00
2022-07-22 20:42:35 +02:00
this . attachRequestMeta ( req , parsedUrl )
2021-12-05 22:53:11 +01:00
2022-05-27 20:29:04 +02:00
const domainLocale = detectDomainLocale (
this . nextConfig . i18n ? . domains ,
getHostname ( parsedUrl , req . headers )
)
const defaultLocale =
domainLocale ? . defaultLocale || this . nextConfig . i18n ? . defaultLocale
const url = parseUrlUtil ( req . url . replace ( /^\/+/ , '/' ) )
const pathnameInfo = getNextPathnameInfo ( url . pathname , {
2021-12-05 22:53:11 +01:00
nextConfig : this.nextConfig ,
} )
2022-05-27 20:29:04 +02:00
url . pathname = pathnameInfo . pathname
if ( pathnameInfo . basePath ) {
req . url = removePathPrefix ( req . url ! , this . nextConfig . basePath )
2021-12-05 22:53:11 +01:00
addRequestMeta ( req , '_nextHadBasePath' , true )
}
if (
this . minimalMode &&
typeof req . headers [ 'x-matched-path' ] === 'string'
) {
2022-06-10 19:35:12 +02:00
try {
2022-10-23 18:06:03 +02:00
if ( this . hasAppDir ) {
// ensure /index path is normalized for prerender
// in minimal mode
if ( req . url . match ( /^\/index($|\?)/ ) ) {
req . url = req . url . replace ( /^\/index/ , '/' )
}
parsedUrl . pathname =
parsedUrl . pathname === '/index' ? '/' : parsedUrl . pathname
}
2022-06-10 19:35:12 +02:00
// x-matched-path is the source of truth, it tells what page
// should be rendered because we don't process rewrites in minimalMode
2022-10-12 09:17:17 +02:00
let matchedPath = normalizeRscPath (
new URL ( req . headers [ 'x-matched-path' ] , 'http://localhost' ) . pathname ,
this . hasAppDir
)
2022-06-10 19:35:12 +02:00
let urlPathname = new URL ( req . url , 'http://localhost' ) . pathname
// For ISR the URL is normalized to the prerenderPath so if
// it's a data request the URL path will be the data URL,
// basePath is already stripped by this point
if ( urlPathname . startsWith ( ` /_next/data/ ` ) ) {
parsedUrl . query . __nextDataReq = '1'
2021-12-05 22:53:11 +01:00
}
2022-06-13 15:34:08 +02:00
const normalizedUrlPath = this . stripNextDataPath ( urlPathname )
2022-06-10 19:35:12 +02:00
matchedPath = this . stripNextDataPath ( matchedPath , false )
2021-12-05 22:53:11 +01:00
2022-06-10 19:35:12 +02:00
if ( this . nextConfig . i18n ) {
const localeResult = normalizeLocalePath (
matchedPath ,
this . nextConfig . i18n . locales
)
matchedPath = localeResult . pathname
2021-12-05 22:53:11 +01:00
2022-06-10 19:35:12 +02:00
if ( localeResult . detectedLocale ) {
parsedUrl . query . __nextLocale = localeResult . detectedLocale
2022-04-26 21:12:29 +02:00
}
}
2022-06-10 19:35:12 +02:00
matchedPath = denormalizePagePath ( matchedPath )
let srcPathname = matchedPath
2021-12-05 22:53:11 +01:00
2022-06-10 19:35:12 +02:00
if (
! isDynamicRoute ( srcPathname ) &&
2022-07-04 16:31:07 +02:00
! ( await this . hasPage ( removeTrailingSlash ( srcPathname ) ) )
2022-06-10 19:35:12 +02:00
) {
for ( const dynamicRoute of this . dynamicRoutes || [ ] ) {
if ( dynamicRoute . match ( srcPathname ) ) {
srcPathname = dynamicRoute . page
break
}
}
}
2021-12-05 22:53:11 +01:00
2022-06-10 19:35:12 +02:00
const pageIsDynamic = isDynamicRoute ( srcPathname )
const utils = getUtils ( {
pageIsDynamic ,
page : srcPathname ,
i18n : this.nextConfig.i18n ,
basePath : this.nextConfig.basePath ,
rewrites : this.customRoutes.rewrites ,
} )
2021-12-05 22:53:11 +01:00
// ensure parsedUrl.pathname includes URL before processing
// rewrites or they won't match correctly
2022-05-27 20:29:04 +02:00
if ( defaultLocale && ! pathnameInfo . locale ) {
parsedUrl . pathname = ` / ${ defaultLocale } ${ parsedUrl . pathname } `
2021-12-05 22:53:11 +01:00
}
2022-04-22 10:00:33 +02:00
const pathnameBeforeRewrite = parsedUrl . pathname
const rewriteParams = utils . handleRewrites ( req , parsedUrl )
const rewriteParamKeys = Object . keys ( rewriteParams )
const didRewrite = pathnameBeforeRewrite !== parsedUrl . pathname
if ( didRewrite ) {
addRequestMeta ( req , '_nextRewroteUrl' , parsedUrl . pathname ! )
addRequestMeta ( req , '_nextDidRewrite' , true )
}
2021-12-05 22:53:11 +01:00
// interpolate dynamic params and normalize URL if needed
if ( pageIsDynamic ) {
let params : ParsedUrlQuery | false = { }
2022-06-13 15:34:08 +02:00
let paramsResult = utils . normalizeDynamicRouteParams (
2021-12-05 22:53:11 +01:00
parsedUrl . query
)
2022-06-13 22:46:19 +02:00
// for prerendered ISR paths we attempt parsing the route
// params from the URL directly as route-matches may not
// contain the correct values due to the filesystem path
// matching before the dynamic route has been matched
2022-06-13 15:34:08 +02:00
if (
2022-06-13 22:46:19 +02:00
! paramsResult . hasValidParams &&
pageIsDynamic &&
! isDynamicRoute ( normalizedUrlPath )
2022-06-13 15:34:08 +02:00
) {
2022-06-13 22:46:19 +02:00
let matcherParams = utils . dynamicRouteMatcher ? . ( normalizedUrlPath )
2022-06-13 15:34:08 +02:00
2022-06-13 22:46:19 +02:00
if ( matcherParams ) {
2022-06-14 05:07:40 +02:00
utils . normalizeDynamicRouteParams ( matcherParams )
2022-06-13 22:46:19 +02:00
Object . assign ( paramsResult . params , matcherParams )
paramsResult . hasValidParams = true
2022-06-13 15:34:08 +02:00
}
}
2021-12-05 22:53:11 +01:00
if ( paramsResult . hasValidParams ) {
params = paramsResult . params
2022-06-13 15:34:08 +02:00
}
if (
req . headers [ 'x-now-route-matches' ] &&
2022-06-13 22:46:19 +02:00
isDynamicRoute ( matchedPath ) &&
! paramsResult . hasValidParams
2022-06-13 15:34:08 +02:00
) {
2021-12-05 22:53:11 +01:00
const opts : Record < string , string > = { }
2022-06-13 15:34:08 +02:00
const routeParams = utils . getParamsFromRouteMatches (
2021-12-05 22:53:11 +01:00
req ,
opts ,
parsedUrl . query . __nextLocale || ''
)
if ( opts . locale ) {
parsedUrl . query . __nextLocale = opts . locale
}
2022-06-13 22:46:19 +02:00
paramsResult = utils . normalizeDynamicRouteParams (
routeParams ,
true
)
2022-06-13 15:34:08 +02:00
if ( paramsResult . hasValidParams ) {
params = paramsResult . params
}
2021-12-05 22:53:11 +01:00
}
2022-06-13 22:46:19 +02:00
// handle the actual dynamic route name being requested
if (
pageIsDynamic &&
utils . defaultRouteMatches &&
normalizedUrlPath === srcPathname &&
! paramsResult . hasValidParams &&
! utils . normalizeDynamicRouteParams ( { . . . params } , true )
. hasValidParams
) {
params = utils . defaultRouteMatches
}
2021-12-05 22:53:11 +01:00
if ( params ) {
2022-06-10 19:35:12 +02:00
matchedPath = utils . interpolateDynamicPath ( srcPathname , params )
2021-12-05 22:53:11 +01:00
req . url = utils . interpolateDynamicPath ( req . url ! , params )
}
Object . assign ( parsedUrl . query , params )
2022-04-22 10:00:33 +02:00
}
if ( pageIsDynamic || didRewrite ) {
utils . normalizeVercelUrl ( req , true , [
. . . rewriteParamKeys ,
. . . Object . keys ( utils . defaultRouteRegex ? . groups || { } ) ,
] )
2021-12-05 22:53:11 +01:00
}
2022-06-10 19:35:12 +02:00
parsedUrl . pathname = ` ${ this . nextConfig . basePath || '' } ${
matchedPath === '/' && this . nextConfig . basePath ? '' : matchedPath
} `
url . pathname = parsedUrl . pathname
2021-12-05 22:53:11 +01:00
} catch ( err ) {
2022-05-01 11:23:06 +02:00
if ( err instanceof DecodeError || err instanceof NormalizeError ) {
2021-12-05 22:53:11 +01:00
res . statusCode = 400
return this . renderError ( null , req , res , '/_error' , { } )
}
throw err
}
}
2022-05-27 20:29:04 +02:00
addRequestMeta ( req , '__nextHadTrailingSlash' , pathnameInfo . trailingSlash )
addRequestMeta ( req , '__nextIsLocaleDomain' , Boolean ( domainLocale ) )
parsedUrl . query . __nextDefaultLocale = defaultLocale
2021-12-05 22:53:11 +01:00
2022-05-27 20:29:04 +02:00
if ( pathnameInfo . locale ) {
2021-12-05 22:53:11 +01:00
req . url = formatUrl ( url )
addRequestMeta ( req , '__nextStrippedLocale' , true )
}
if ( ! this . minimalMode || ! parsedUrl . query . __nextLocale ) {
2022-05-27 20:29:04 +02:00
if ( pathnameInfo . locale || defaultLocale ) {
parsedUrl . query . __nextLocale = pathnameInfo . locale || defaultLocale
2021-12-05 22:53:11 +01:00
}
}
2022-09-29 10:56:28 +02:00
if (
// Edge runtime always has minimal mode enabled.
process . env . NEXT_RUNTIME !== 'edge' &&
! this . minimalMode &&
defaultLocale
) {
const { getLocaleRedirect } =
require ( '../shared/lib/i18n/get-locale-redirect' ) as typeof import ( '../shared/lib/i18n/get-locale-redirect' )
2022-05-27 20:29:04 +02:00
const redirect = getLocaleRedirect ( {
defaultLocale ,
domainLocale ,
headers : req.headers ,
nextConfig : this.nextConfig ,
pathLocale : pathnameInfo.locale ,
urlParsed : {
. . . url ,
pathname : pathnameInfo.locale
? ` / ${ pathnameInfo . locale } ${ url . pathname } `
: url . pathname ,
} ,
} )
2021-12-05 22:53:11 +01:00
2022-05-27 20:29:04 +02:00
if ( redirect ) {
return res
. redirect ( redirect , TEMPORARY_REDIRECT_STATUS )
. body ( redirect )
. send ( )
}
2021-12-05 22:53:11 +01:00
}
res . statusCode = 200
return await this . run ( req , res , parsedUrl )
} catch ( err : any ) {
if (
( err && typeof err === 'object' && err . code === 'ERR_INVALID_URL' ) ||
2022-05-01 11:23:06 +02:00
err instanceof DecodeError ||
err instanceof NormalizeError
2021-12-05 22:53:11 +01:00
) {
res . statusCode = 400
return this . renderError ( null , req , res , '/_error' , { } )
}
if ( this . minimalMode || this . renderOpts . dev ) {
throw err
}
2022-01-11 21:40:03 +01:00
this . logError ( getProperError ( err ) )
2021-12-05 22:53:11 +01:00
res . statusCode = 500
2022-01-14 22:01:35 +01:00
res . body ( 'Internal Server Error' ) . send ( )
2021-12-05 22:53:11 +01:00
}
}
2022-01-14 22:01:35 +01:00
public getRequestHandler ( ) : BaseRequestHandler {
2021-12-05 22:53:11 +01:00
return this . handleRequest . bind ( this )
}
2022-08-10 19:00:30 +02:00
protected async handleUpgrade (
_req : BaseNextRequest ,
_socket : any ,
_head? : any
) : Promise < void > { }
2021-12-05 22:53:11 +01:00
public setAssetPrefix ( prefix? : string ) : void {
this . renderOpts . assetPrefix = prefix ? prefix . replace ( /\/$/ , '' ) : ''
}
// Backwards compatibility
public async prepare ( ) : Promise < void > { }
// Backwards compatibility
protected async close ( ) : Promise < void > { }
protected getPreviewProps ( ) : __ApiPreviewProps {
return this . getPrerenderManifest ( ) . preview
}
protected async _beforeCatchAllRender (
2022-01-14 22:01:35 +01:00
_req : BaseNextRequest ,
_res : BaseNextResponse ,
2021-12-05 22:53:11 +01:00
_params : Params ,
_parsedUrl : UrlWithParsedQuery
) : Promise < boolean > {
return false
}
protected getDynamicRoutes ( ) : Array < RoutingItem > {
const addedPages = new Set < string > ( )
2022-04-06 16:35:52 +02:00
return getSortedRoutes (
2022-05-03 12:37:23 +02:00
[
2022-05-25 11:46:26 +02:00
. . . Object . keys ( this . appPathRoutes || { } ) ,
2022-05-03 12:37:23 +02:00
. . . Object . keys ( this . pagesManifest ! ) ,
] . map (
2022-04-06 16:35:52 +02:00
( page ) = >
normalizeLocalePath ( page , this . nextConfig . i18n ? . locales ) . pathname
2021-12-05 22:53:11 +01:00
)
2022-04-06 16:35:52 +02:00
)
. map ( ( page ) = > {
if ( addedPages . has ( page ) || ! isDynamicRoute ( page ) ) return null
addedPages . add ( page )
return {
page ,
match : getRouteMatcher ( getRouteRegex ( page ) ) ,
}
} )
. filter ( ( item ) : item is RoutingItem = > Boolean ( item ) )
2021-12-05 22:53:11 +01:00
}
2022-09-06 19:03:21 +02:00
protected getAppPathRoutes ( ) : Record < string , string [ ] > {
const appPathRoutes : Record < string , string [ ] > = { }
2022-05-03 12:37:23 +02:00
2022-05-25 11:46:26 +02:00
Object . keys ( this . appPathsManifest || { } ) . forEach ( ( entry ) = > {
2022-09-06 19:03:21 +02:00
const normalizedPath = normalizeAppPath ( entry ) || '/'
if ( ! appPathRoutes [ normalizedPath ] ) {
appPathRoutes [ normalizedPath ] = [ ]
}
appPathRoutes [ normalizedPath ] . push ( entry )
2022-05-03 12:37:23 +02:00
} )
2022-05-25 11:46:26 +02:00
return appPathRoutes
2022-05-03 12:37:23 +02:00
}
2021-12-05 22:53:11 +01:00
protected async run (
2022-01-14 22:01:35 +01:00
req : BaseNextRequest ,
res : BaseNextResponse ,
2021-12-05 22:53:11 +01:00
parsedUrl : UrlWithParsedQuery
) : Promise < void > {
this . handleCompression ( req , res )
try {
const matched = await this . router . execute ( req , res , parsedUrl )
if ( matched ) {
return
}
} catch ( err ) {
2022-05-01 11:23:06 +02:00
if ( err instanceof DecodeError || err instanceof NormalizeError ) {
2021-12-05 22:53:11 +01:00
res . statusCode = 400
return this . renderError ( null , req , res , '/_error' , { } )
}
throw err
}
await this . render404 ( req , res , parsedUrl )
}
private async pipe (
fn : ( ctx : RequestContext ) = > Promise < ResponsePayload | null > ,
partialContext : {
2022-01-14 22:01:35 +01:00
req : BaseNextRequest
res : BaseNextResponse
2021-12-05 22:53:11 +01:00
pathname : string
query : NextParsedUrlQuery
}
) : Promise < void > {
2022-03-04 08:39:26 +01:00
const isBotRequest = isBot ( partialContext . req . headers [ 'user-agent' ] || '' )
2021-12-05 22:53:11 +01:00
const ctx = {
. . . partialContext ,
renderOpts : {
. . . this . renderOpts ,
2022-03-04 08:39:26 +01:00
supportsDynamicHTML : ! isBotRequest ,
2022-10-24 22:38:30 +02:00
isBot : ! ! isBotRequest ,
2021-12-05 22:53:11 +01:00
} ,
} as const
const payload = await fn ( ctx )
if ( payload === null ) {
return
}
const { req , res } = ctx
const { body , type , revalidateOptions } = payload
2022-01-14 22:01:35 +01:00
if ( ! res . sent ) {
2021-12-05 22:53:11 +01:00
const { generateEtags , poweredByHeader , dev } = this . renderOpts
if ( dev ) {
// In dev, we should not cache pages for any reason.
res . setHeader ( 'Cache-Control' , 'no-store, must-revalidate' )
}
2022-01-14 22:01:35 +01:00
return this . sendRenderResult ( req , res , {
2021-12-05 22:53:11 +01:00
result : body ,
type ,
generateEtags ,
poweredByHeader ,
options : revalidateOptions ,
} )
}
}
private async getStaticHTML (
fn : ( ctx : RequestContext ) = > Promise < ResponsePayload | null > ,
partialContext : {
2022-01-14 22:01:35 +01:00
req : BaseNextRequest
res : BaseNextResponse
2021-12-05 22:53:11 +01:00
pathname : string
query : ParsedUrlQuery
}
) : Promise < string | null > {
const payload = await fn ( {
. . . partialContext ,
renderOpts : {
. . . this . renderOpts ,
supportsDynamicHTML : false ,
} ,
} )
if ( payload === null ) {
return null
}
return payload . body . toUnchunkedString ( )
}
public async render (
2022-01-14 22:01:35 +01:00
req : BaseNextRequest ,
res : BaseNextResponse ,
2021-12-05 22:53:11 +01:00
pathname : string ,
query : NextParsedUrlQuery = { } ,
2022-01-21 21:38:59 +01:00
parsedUrl? : NextUrlWithParsedQuery ,
internalRender = false
2021-12-05 22:53:11 +01:00
) : Promise < void > {
if ( ! pathname . startsWith ( '/' ) ) {
console . warn (
` Cannot render page with path " ${ pathname } ", did you mean "/ ${ pathname } "?. See more info here: https://nextjs.org/docs/messages/render-no-starting-slash `
)
}
if (
this . renderOpts . customServer &&
pathname === '/index' &&
! ( await this . hasPage ( '/index' ) )
) {
// maintain backwards compatibility for custom server
// (see custom-server integration tests)
pathname = '/'
}
// 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 (
2022-01-21 21:38:59 +01:00
! internalRender &&
2021-12-05 22:53:11 +01:00
! this . minimalMode &&
2022-06-10 19:35:12 +02:00
! query . __nextDataReq &&
2021-12-05 22:53:11 +01:00
( req . url ? . match ( /^\/_next\// ) ||
( this . hasStaticDir && req . url ! . match ( /^\/static\// ) ) )
) {
return this . handleRequest ( req , res , parsedUrl )
}
// Custom server users can run `app.render()` which needs compression.
if ( this . renderOpts . customServer ) {
this . handleCompression ( req , res )
}
if ( isBlockedPage ( pathname ) ) {
return this . render404 ( req , res , parsedUrl )
}
return this . pipe ( ( ctx ) = > this . renderToResponse ( ctx ) , {
req ,
res ,
pathname ,
query ,
} )
}
2022-09-19 20:05:28 +02:00
protected async getStaticPaths ( {
pathname ,
} : {
pathname : string
originalAppPath? : string
} ) : Promise < {
staticPaths? : string [ ]
fallbackMode ? : 'static' | 'blocking' | false
2021-12-05 22:53:11 +01:00
} > {
// `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 =
2022-09-19 20:05:28 +02:00
this . getPrerenderManifest ( ) . dynamicRoutes [ pathname ] ? . fallback
2021-12-05 22:53:11 +01:00
return {
staticPaths ,
fallbackMode :
typeof fallbackField === 'string'
? 'static'
: fallbackField === null
? 'blocking'
2022-09-19 20:05:28 +02:00
: fallbackField ,
2021-12-05 22:53:11 +01:00
}
}
private async renderToResponseWithComponents (
{ req , res , pathname , renderOpts : opts } : RequestContext ,
{ components , query } : FindComponentsResult
) : Promise < ResponsePayload | null > {
const is404Page = pathname === '/404'
const is500Page = pathname === '/500'
2022-09-19 20:05:28 +02:00
const isAppPath = components . isAppPath
2021-12-05 22:53:11 +01:00
const hasServerProps = ! ! components . getServerSideProps
2022-09-19 20:05:28 +02:00
let hasStaticPaths = ! ! components . getStaticPaths
2022-02-18 16:25:10 +01:00
const hasGetInitialProps = ! ! components . Component ? . getInitialProps
2022-09-28 00:03:49 +02:00
let isSSG = ! ! components . getStaticProps
2021-12-05 22:53:11 +01:00
2022-09-19 20:05:28 +02:00
// 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 =
getRequestMeta ( req , '_nextRewroteUrl' ) || urlPathname
let staticPaths : string [ ] | undefined
let fallbackMode : false | undefined | 'blocking' | 'static'
if ( isAppPath ) {
const pathsResult = await this . getStaticPaths ( {
pathname ,
originalAppPath : components.pathname ,
} )
staticPaths = pathsResult . staticPaths
fallbackMode = pathsResult . fallbackMode
const hasFallback = typeof fallbackMode !== 'undefined'
if ( hasFallback ) {
hasStaticPaths = true
}
if ( hasFallback || staticPaths ? . includes ( resolvedUrlPathname ) ) {
isSSG = true
2022-10-06 21:43:23 +02:00
} else if ( ! this . renderOpts . dev ) {
const manifest = this . getPrerenderManifest ( )
isSSG =
isSSG || ! ! manifest . routes [ pathname === '/index' ? '/' : pathname ]
}
}
// Toggle whether or not this is a Data request
let isDataReq =
! ! (
query . __nextDataReq ||
( req . headers [ 'x-nextjs-data' ] &&
( this . serverOptions as any ) . webServerConfig )
) &&
( isSSG || hasServerProps )
2022-11-17 22:22:36 +01:00
// when we are handling a middleware prefetch and it doesn't
// resolve to a static data route we bail early to avoid
// unexpected SSR invocations
if ( ! isSSG && req . headers [ 'x-middleware-prefetch' ] ) {
res . setHeader ( 'x-middleware-skip' , '1' )
res . body ( '{}' ) . send ( )
return null
}
2022-10-18 07:34:29 +02:00
if ( isAppPath ) {
2022-11-08 01:35:32 +01:00
res . setHeader ( 'vary' , RSC_VARY_HEADER )
2022-10-18 07:34:29 +02:00
2022-11-08 01:35:32 +01:00
if ( isSSG && req . headers [ RSC . toLowerCase ( ) ] ) {
2022-10-19 23:38:54 +02:00
if ( ! this . minimalMode ) {
isDataReq = true
}
2022-10-06 21:43:23 +02:00
// strip header so we generate HTML still
if (
opts . runtime !== 'experimental-edge' ||
( this . serverOptions as any ) . webServerConfig
) {
2022-11-08 01:35:32 +01:00
for ( const param of FLIGHT_PARAMETERS ) {
delete req . headers [ param . toString ( ) . toLowerCase ( ) ]
}
2022-10-06 21:43:23 +02:00
}
2022-09-19 20:05:28 +02:00
}
}
2022-10-06 21:43:23 +02:00
delete query . __nextDataReq
2022-09-19 20:05:28 +02:00
2022-06-10 19:35:12 +02:00
// normalize req.url for SSG paths as it is not exposed
// to getStaticProps and the asPath should not expose /_next/data
if (
isSSG &&
this . minimalMode &&
req . headers [ 'x-matched-path' ] &&
req . url . startsWith ( '/_next/data' )
) {
req . url = this . stripNextDataPath ( req . url )
}
2022-06-15 00:31:33 +02:00
if (
! ! req . headers [ 'x-nextjs-data' ] &&
( ! res . statusCode || res . statusCode === 200 )
) {
2022-06-10 19:35:12 +02:00
res . setHeader (
'x-nextjs-matched-path' ,
` ${ query . __nextLocale ? ` / ${ query . __nextLocale } ` : '' } ${ pathname } `
)
}
2022-04-01 18:13:38 +02:00
2022-11-08 01:35:32 +01:00
// Don't delete headers[RSC] yet, it still needs to be used in renderToHTML later
2022-02-10 22:13:52 +01:00
const isFlightRequest = Boolean (
2022-11-08 01:35:32 +01:00
this . serverComponentManifest && req . headers [ RSC . toLowerCase ( ) ]
2022-02-10 22:13:52 +01:00
)
2021-12-05 22:53:11 +01:00
// we need to ensure the status code if /404 is visited directly
2022-02-10 22:13:52 +01:00
if ( is404Page && ! isDataReq && ! isFlightRequest ) {
2021-12-05 22:53:11 +01:00
res . statusCode = 404
}
// ensure correct status is set when visiting a status page
// directly e.g. /500
if ( STATIC_STATUS_PAGES . includes ( pathname ) ) {
2022-03-24 22:49:38 +01:00
res . statusCode = parseInt ( pathname . slice ( 1 ) , 10 )
2021-12-05 22:53:11 +01:00
}
2022-02-15 18:28:18 +01:00
// static pages can only respond to GET/HEAD
// requests so ensure we respond with 405 for
// invalid requests
if (
! is404Page &&
! is500Page &&
pathname !== '/_error' &&
req . method !== 'HEAD' &&
req . method !== 'GET' &&
( typeof components . Component === 'string' || isSSG )
) {
res . statusCode = 405
res . setHeader ( 'Allow' , [ 'GET' , 'HEAD' ] )
await this . renderError ( null , req , res , pathname )
return null
}
2021-12-05 22:53:11 +01:00
// handle static page
if ( typeof components . Component === 'string' ) {
return {
type : 'html' ,
// TODO: Static pages should be serialized as RenderResult
body : RenderResult.fromStatic ( components . Component ) ,
}
}
if ( ! query . amp ) {
delete query . amp
}
if ( opts . supportsDynamicHTML === true ) {
2022-03-04 08:39:26 +01:00
const isBotRequest = isBot ( req . headers [ 'user-agent' ] || '' )
2022-03-11 21:38:09 +01:00
const isSupportedDocument =
typeof components . Document ? . getInitialProps !== 'function' ||
2022-10-29 22:34:03 +02:00
// The built-in `Document` component also supports dynamic HTML for concurrent mode.
NEXT_BUILTIN_DOCUMENT in components . Document
2022-03-11 21:38:09 +01:00
2021-12-05 22:53:11 +01:00
// Disable dynamic HTML in cases that we know it won't be generated,
// so that we can continue generating a cache key when possible.
2022-09-19 20:05:28 +02:00
// TODO-APP: should the first render for a dynamic app path
// be static so we can collect revalidate and populate the
// cache if there are no dynamic data requirements
2021-12-05 22:53:11 +01:00
opts . supportsDynamicHTML =
2022-10-18 18:47:13 +02:00
! isSSG && ! isBotRequest && ! query . amp && isSupportedDocument
2022-10-24 22:38:30 +02:00
opts . isBot = isBotRequest
2021-12-05 22:53:11 +01:00
}
const defaultLocale = isSSG
? this . nextConfig . i18n ? . defaultLocale
: query . __nextDefaultLocale
const locale = query . __nextLocale
const locales = this . nextConfig . i18n ? . locales
let previewData : PreviewData
let isPreviewMode = false
if ( hasServerProps || isSSG ) {
2022-02-11 20:56:25 +01:00
// For the edge runtime, we don't support preview mode in SSG.
2022-04-26 19:54:28 +02:00
if ( process . env . NEXT_RUNTIME !== 'edge' ) {
2022-02-11 20:56:25 +01:00
const { tryGetPreviewData } =
require ( './api-utils/node' ) as typeof import ( './api-utils/node' )
previewData = tryGetPreviewData ( req , res , this . renderOpts . previewProps )
isPreviewMode = previewData !== false
}
2021-12-05 22:53:11 +01:00
}
2022-02-08 04:50:23 +01:00
let isManualRevalidate = false
2022-04-13 18:56:58 +02:00
let revalidateOnlyGenerated = false
2022-02-08 04:50:23 +01:00
if ( isSSG ) {
2022-04-13 18:56:58 +02:00
; ( { isManualRevalidate , revalidateOnlyGenerated } =
checkIsManualRevalidate ( req , this . renderOpts . previewProps ) )
2022-02-08 04:50:23 +01:00
}
2022-06-23 00:02:46 +02:00
if ( isSSG && this . minimalMode && req . headers [ 'x-matched-path' ] ) {
// the url value is already correct when the matched-path header is set
resolvedUrlPathname = urlPathname
}
2022-05-27 20:29:04 +02:00
urlPathname = removeTrailingSlash ( urlPathname )
2021-12-05 22:53:11 +01:00
resolvedUrlPathname = normalizeLocalePath (
2022-05-27 20:29:04 +02:00
removeTrailingSlash ( resolvedUrlPathname ) ,
2021-12-05 22:53:11 +01:00
this . nextConfig . i18n ? . locales
) . pathname
const handleRedirect = ( pageData : any ) = > {
const redirect = {
destination : pageData.pageProps.__N_REDIRECT ,
statusCode : pageData.pageProps.__N_REDIRECT_STATUS ,
basePath : pageData.pageProps.__N_REDIRECT_BASE_PATH ,
}
const statusCode = getRedirectStatus ( redirect )
const { basePath } = this . nextConfig
if (
basePath &&
redirect . basePath !== false &&
redirect . destination . startsWith ( '/' )
) {
redirect . destination = ` ${ basePath } ${ redirect . destination } `
}
if ( redirect . destination . startsWith ( '/' ) ) {
redirect . destination = normalizeRepeatedSlashes ( redirect . destination )
}
2022-01-14 22:01:35 +01:00
res
. redirect ( redirect . destination , statusCode )
. body ( redirect . destination )
. send ( )
2021-12-05 22:53:11 +01:00
}
// remove /_next/data prefix from urlPathname so it matches
// for direct page visit and /_next/data visit
if ( isDataReq ) {
2022-05-03 22:40:36 +02:00
resolvedUrlPathname = this . stripNextDataPath ( resolvedUrlPathname )
urlPathname = this . stripNextDataPath ( urlPathname )
2021-12-05 22:53:11 +01:00
}
let ssgCacheKey =
2022-10-06 21:43:23 +02:00
isPreviewMode || ! isSSG || opts . supportsDynamicHTML
2022-04-01 18:13:38 +02:00
? null // Preview mode, manual revalidate, flight request can bypass the cache
2021-12-05 22:53:11 +01:00
: ` ${ locale ? ` / ${ locale } ` : '' } ${
( pathname === '/' || resolvedUrlPathname === '/' ) && locale
? ''
: resolvedUrlPathname
} $ { query . amp ? '.amp' : '' } `
if ( ( is404Page || is500Page ) && isSSG ) {
ssgCacheKey = ` ${ locale ? ` / ${ locale } ` : '' } ${ pathname } ${
query . amp ? '.amp' : ''
} `
}
if ( ssgCacheKey ) {
// we only encode path delimiters for path segments from
// getStaticPaths so we need to attempt decoding the URL
// to match against and only escape the path delimiters
// this allows non-ascii values to be handled e.g. Japanese characters
// TODO: investigate adding this handling for non-SSG pages so
// non-ascii names work there also
ssgCacheKey = ssgCacheKey
. split ( '/' )
. map ( ( seg ) = > {
try {
seg = escapePathDelimiters ( decodeURIComponent ( seg ) , true )
} catch ( _ ) {
// An improperly encoded URL was provided
throw new DecodeError ( 'failed to decode param' )
}
return seg
} )
. join ( '/' )
2022-03-23 15:28:04 +01:00
// ensure /index and / is normalized to one key
ssgCacheKey =
ssgCacheKey === '/index' && pathname === '/' ? '/' : ssgCacheKey
2021-12-05 22:53:11 +01:00
}
const doRender : ( ) = > Promise < ResponseCacheEntry | null > = async ( ) = > {
let pageData : any
let body : RenderResult | null
let sprRevalidate : number | false
let isNotFound : boolean | undefined
let isRedirect : boolean | undefined
2022-10-18 18:47:13 +02:00
const origQuery = parseUrl ( req . url || '' , true ) . query
2021-12-05 22:53:11 +01:00
2022-10-18 18:47:13 +02:00
// clear any dynamic route params so they aren't in
// the resolvedUrl
if ( opts . params ) {
Object . keys ( opts . params ) . forEach ( ( key ) = > {
delete origQuery [ key ]
2021-12-05 22:53:11 +01:00
} )
2022-10-18 18:47:13 +02:00
}
const hadTrailingSlash =
urlPathname !== '/' && this . nextConfig . trailingSlash
2021-12-05 22:53:11 +01:00
2022-10-18 18:47:13 +02:00
const resolvedUrl = formatUrl ( {
pathname : ` ${ resolvedUrlPathname } ${ hadTrailingSlash ? '/' : '' } ` ,
// make sure to only add query values from original URL
query : origQuery ,
} )
2021-12-05 22:53:11 +01:00
2022-10-18 18:47:13 +02:00
const renderOpts : RenderOpts = {
. . . components ,
. . . opts ,
isDataReq ,
resolvedUrl ,
locale ,
locales ,
defaultLocale ,
// For getServerSideProps and getInitialProps we need to ensure we use the original URL
// and not the resolved URL to prevent a hydration mismatch on
// asPath
resolvedAsPath :
hasServerProps || hasGetInitialProps
? formatUrl ( {
// we use the original URL pathname less the _next/data prefix if
// present
pathname : ` ${ urlPathname } ${ hadTrailingSlash ? '/' : '' } ` ,
query : origQuery ,
} )
: resolvedUrl ,
}
2022-09-19 20:05:28 +02:00
2022-10-18 18:47:13 +02:00
if ( isSSG || hasStaticPaths ) {
renderOpts . supportsDynamicHTML = false
2021-12-05 22:53:11 +01:00
}
2022-10-18 18:47:13 +02:00
const renderResult = await this . renderHTML (
req ,
res ,
pathname ,
query ,
renderOpts
)
body = renderResult
// TODO: change this to a different passing mechanism
pageData = ( renderOpts as any ) . pageData
sprRevalidate = ( renderOpts as any ) . revalidate
isNotFound = ( renderOpts as any ) . isNotFound
isRedirect = ( renderOpts as any ) . isRedirect
2021-12-05 22:53:11 +01:00
let value : ResponseCacheValue | null
if ( isNotFound ) {
value = null
} else if ( isRedirect ) {
value = { kind : 'REDIRECT' , props : pageData }
} else {
if ( ! body ) {
return null
}
value = { kind : 'PAGE' , html : body , pageData }
}
return { revalidate : sprRevalidate , value }
}
const cacheEntry = await this . responseCache . get (
ssgCacheKey ,
2022-02-08 04:50:23 +01:00
async ( hasResolved , hadCache ) = > {
2021-12-05 22:53:11 +01:00
const isProduction = ! this . renderOpts . dev
const isDynamicPathname = isDynamicRoute ( pathname )
2022-01-14 22:01:35 +01:00
const didRespond = hasResolved || res . sent
2021-12-05 22:53:11 +01:00
2022-09-19 20:05:28 +02:00
if ( ! staticPaths ) {
; ( { staticPaths , fallbackMode } = hasStaticPaths
? await this . getStaticPaths ( { pathname } )
: { staticPaths : undefined , fallbackMode : false } )
}
2021-12-05 22:53:11 +01:00
if (
fallbackMode === 'static' &&
isBot ( req . headers [ 'user-agent' ] || '' )
) {
fallbackMode = 'blocking'
}
2022-04-13 18:56:58 +02:00
// skip manual revalidate if cache is not present and
// revalidate-if-generated is set
2022-04-13 22:04:00 +02:00
if (
isManualRevalidate &&
revalidateOnlyGenerated &&
! hadCache &&
! this . minimalMode
) {
2022-04-13 18:56:58 +02:00
await this . render404 ( req , res )
return null
}
2022-02-08 04:50:23 +01:00
// only allow manual revalidate for fallback: true/blocking
// or for prerendered fallback: false paths
if ( isManualRevalidate && ( fallbackMode !== false || hadCache ) ) {
fallbackMode = 'blocking'
}
2021-12-05 22:53:11 +01:00
// 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 (
2022-09-29 10:56:28 +02:00
process . env . NEXT_RUNTIME !== 'edge' &&
2021-12-05 22:53:11 +01:00
this . minimalMode !== true &&
fallbackMode !== 'blocking' &&
ssgCacheKey &&
! didRespond &&
! isPreviewMode &&
isDynamicPathname &&
// Development should trigger fallback when the path is not in
// `getStaticPaths`
( isProduction ||
! staticPaths ||
! staticPaths . includes (
// we use ssgCacheKey here as it is normalized to match the
// encoding from getStaticPaths along with including the locale
query . amp ? ssgCacheKey . replace ( /\.amp$/ , '' ) : ssgCacheKey
) )
) {
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 ) {
// Production already emitted the fallback as static HTML.
if ( isProduction ) {
2022-05-28 02:50:21 +02:00
const html = await this . getFallback (
2021-12-05 22:53:11 +01:00
locale ? ` / ${ locale } ${ pathname } ` : pathname
)
return {
value : {
kind : 'PAGE' ,
html : RenderResult.fromStatic ( html ) ,
pageData : { } ,
} ,
}
}
// We need to generate the fallback on-demand for development.
else {
query . __nextFallback = 'true'
const result = await doRender ( )
if ( ! result ) {
return null
}
// Prevent caching this result
delete result . revalidate
return result
}
}
}
const result = await doRender ( )
if ( ! result ) {
return null
}
return {
. . . result ,
revalidate :
result . revalidate !== undefined
? result . revalidate
: /* default to minimum revalidate (this should be an invariant) */ 1 ,
}
2022-02-08 04:50:23 +01:00
} ,
{
isManualRevalidate ,
2022-05-30 01:05:23 +02:00
isPrefetch : req.headers.purpose === 'prefetch' ,
2021-12-05 22:53:11 +01:00
}
)
if ( ! cacheEntry ) {
2022-04-13 18:56:58 +02:00
if ( ssgCacheKey && ! ( isManualRevalidate && revalidateOnlyGenerated ) ) {
2021-12-05 22:53:11 +01:00
// A cache entry might not be generated if a response is written
// in `getInitialProps` or `getServerSideProps`, but those shouldn't
// have a cache key. If we do have a cache key but we don't end up
// with a cache entry, then either Next.js or the application has a
// bug that needs fixing.
throw new Error ( 'invariant: cache entry required but not generated' )
}
return null
}
2022-05-10 00:08:29 +02:00
if ( isSSG && ! this . minimalMode ) {
2022-02-25 23:17:07 +01:00
// set x-nextjs-cache header to match the header
// we set for the image-optimizer
res . setHeader (
'x-nextjs-cache' ,
isManualRevalidate
? 'REVALIDATED'
: cacheEntry . isMiss
? 'MISS'
: cacheEntry . isStale
? 'STALE'
: 'HIT'
)
}
2021-12-05 22:53:11 +01:00
const { revalidate , value : cachedData } = cacheEntry
const revalidateOptions : any =
typeof revalidate !== 'undefined' &&
( ! this . renderOpts . dev || ( hasServerProps && ! isDataReq ) )
? {
// When the page is 404 cache-control should not be added unless
// we are rendering the 404 page for notFound: true which should
// cache according to revalidate correctly
private : isPreviewMode || ( is404Page && cachedData ) ,
stateful : ! isSSG ,
revalidate ,
}
: undefined
if ( ! cachedData ) {
if ( revalidateOptions ) {
setRevalidateHeaders ( res , revalidateOptions )
}
if ( isDataReq ) {
res . statusCode = 404
2022-01-14 22:01:35 +01:00
res . body ( '{"notFound":true}' ) . send ( )
2021-12-05 22:53:11 +01:00
return null
} else {
2022-02-16 19:53:48 +01:00
if ( this . renderOpts . dev ) {
query . __nextNotFoundSrcPage = pathname
}
2021-12-05 22:53:11 +01:00
await this . render404 (
req ,
res ,
{
pathname ,
query ,
} as UrlWithParsedQuery ,
false
)
return null
}
} else if ( cachedData . kind === 'REDIRECT' ) {
2022-06-23 20:23:30 +02:00
if ( revalidateOptions ) {
setRevalidateHeaders ( res , revalidateOptions )
}
2021-12-05 22:53:11 +01:00
if ( isDataReq ) {
return {
type : 'json' ,
2022-04-01 18:13:38 +02:00
body : RenderResult.fromStatic (
// @TODO: Handle flight data.
JSON . stringify ( cachedData . props )
) ,
2021-12-05 22:53:11 +01:00
revalidateOptions ,
}
} else {
await handleRedirect ( cachedData . props )
return null
}
2022-02-09 00:46:59 +01:00
} else if ( cachedData . kind === 'IMAGE' ) {
throw new Error ( 'invariant SSG should not return an image cache value' )
2021-12-05 22:53:11 +01:00
} else {
return {
2022-10-06 21:43:23 +02:00
type : isDataReq ? ( isAppPath ? 'rsc' : 'json' ) : 'html' ,
2021-12-05 22:53:11 +01:00
body : isDataReq
2022-10-06 21:43:23 +02:00
? RenderResult . fromStatic (
isAppPath
? ( cachedData . pageData as string )
: JSON . stringify ( cachedData . pageData )
)
2021-12-05 22:53:11 +01:00
: cachedData . html ,
revalidateOptions ,
}
}
}
2022-06-10 19:35:12 +02:00
private stripNextDataPath ( path : string , stripLocale = true ) {
2022-05-03 22:40:36 +02:00
if ( path . includes ( this . buildId ) ) {
const splitPath = path . substring (
path . indexOf ( this . buildId ) + this . buildId . length
)
path = denormalizePagePath ( splitPath . replace ( /\.json$/ , '' ) )
}
2022-06-10 19:35:12 +02:00
if ( this . nextConfig . i18n && stripLocale ) {
2022-05-03 22:40:36 +02:00
const { locales } = this . nextConfig . i18n
return normalizeLocalePath ( path , locales ) . pathname
}
return path
}
2022-08-24 21:49:47 +02:00
// map the route to the actual bundle name
2022-09-06 19:03:21 +02:00
protected getOriginalAppPaths ( route : string ) {
2022-10-05 00:16:44 +02:00
if ( this . hasAppDir ) {
2022-08-24 21:49:47 +02:00
const originalAppPath = this . appPathRoutes ? . [ route ]
2022-05-03 12:37:23 +02:00
2022-08-24 21:49:47 +02:00
if ( ! originalAppPath ) {
return null
2022-05-03 12:37:23 +02:00
}
2022-08-24 21:49:47 +02:00
return originalAppPath
2022-05-03 12:37:23 +02:00
}
2022-08-24 21:49:47 +02:00
return null
}
2022-05-03 12:37:23 +02:00
2022-08-24 21:49:47 +02:00
protected async renderPageComponent (
ctx : RequestContext ,
bubbleNoFallback : boolean
) {
2022-06-22 01:56:53 +02:00
const { query , pathname } = ctx
2022-05-03 12:37:23 +02:00
2022-09-06 19:03:21 +02:00
const appPaths = this . getOriginalAppPaths ( pathname )
2022-09-19 20:05:28 +02:00
const isAppPath = Array . isArray ( appPaths )
2022-09-06 19:03:21 +02:00
2022-06-22 01:56:53 +02:00
let page = pathname
2022-09-19 20:05:28 +02:00
if ( isAppPath ) {
2022-09-06 19:03:21 +02:00
// When it's an array, we need to pass all parallel routes to the loader.
page = appPaths [ 0 ]
2022-06-22 01:56:53 +02:00
}
2022-07-09 14:33:51 +02:00
2022-09-06 19:03:21 +02:00
const result = await this . findPageComponents ( {
pathname : page ,
2022-06-22 01:56:53 +02:00
query ,
2022-09-06 19:03:21 +02:00
params : ctx.renderOpts.params || { } ,
2022-09-19 20:05:28 +02:00
isAppPath ,
2022-09-06 19:03:21 +02:00
appPaths ,
2022-09-09 00:17:15 +02:00
sriEnabled : ! ! this . nextConfig . experimental . sri ? . algorithm ,
2022-09-06 19:03:21 +02:00
} )
2022-06-22 01:56:53 +02:00
if ( result ) {
try {
return await this . renderToResponseWithComponents ( ctx , result )
} catch ( err ) {
const isNoFallbackError = err instanceof NoFallbackError
2021-12-05 22:53:11 +01:00
2022-06-22 01:56:53 +02:00
if ( ! isNoFallbackError || ( isNoFallbackError && bubbleNoFallback ) ) {
throw err
2021-12-05 22:53:11 +01:00
}
}
2022-06-22 01:56:53 +02:00
}
2022-06-28 01:47:32 +02:00
return false
2022-06-22 01:56:53 +02:00
}
private async renderToResponse (
ctx : RequestContext
) : Promise < ResponsePayload | null > {
const { res , query , pathname } = ctx
let page = pathname
const bubbleNoFallback = ! ! query . _nextBubbleNoFallback
delete query . _nextBubbleNoFallback
try {
// Ensure a request to the URL /accounts/[id] will be treated as a dynamic
// route correctly and not loaded immediately without parsing params.
if ( ! isDynamicRoute ( page ) ) {
const result = await this . renderPageComponent ( ctx , bubbleNoFallback )
2022-06-28 01:47:32 +02:00
if ( result !== false ) return result
2022-06-22 01:56:53 +02:00
}
2021-12-05 22:53:11 +01:00
if ( this . dynamicRoutes ) {
for ( const dynamicRoute of this . dynamicRoutes ) {
const params = dynamicRoute . match ( pathname )
if ( ! params ) {
continue
}
2022-05-03 12:37:23 +02:00
page = dynamicRoute . page
2022-06-22 01:56:53 +02:00
const result = await this . renderPageComponent (
{
. . . ctx ,
pathname : page ,
renderOpts : {
. . . ctx . renderOpts ,
params ,
} ,
} ,
bubbleNoFallback
2021-12-05 22:53:11 +01:00
)
2022-06-28 01:47:32 +02:00
if ( result !== false ) return result
2021-12-05 22:53:11 +01:00
}
}
2022-08-31 01:30:49 +02:00
// currently edge functions aren't receiving the x-matched-path
// header so we need to fallback to matching the current page
// when we weren't able to match via dynamic route to handle
// the rewrite case
// @ts-expect-error extended in child class web-server
if ( this . serverOptions . webServerConfig ) {
// @ts-expect-error extended in child class web-server
ctx . pathname = this . serverOptions . webServerConfig . page
const result = await this . renderPageComponent ( ctx , bubbleNoFallback )
if ( result !== false ) return result
}
2021-12-05 22:53:11 +01:00
} catch ( error ) {
2022-01-11 21:40:03 +01:00
const err = getProperError ( error )
2022-06-27 21:15:09 +02:00
if ( error instanceof MissingStaticPage ) {
console . error (
'Invariant: failed to load static page' ,
JSON . stringify (
{
page ,
url : ctx.req.url ,
matchedPath : ctx.req.headers [ 'x-matched-path' ] ,
initUrl : getRequestMeta ( ctx . req , '__NEXT_INIT_URL' ) ,
didRewrite : getRequestMeta ( ctx . req , '_nextDidRewrite' ) ,
rewroteUrl : getRequestMeta ( ctx . req , '_nextRewroteUrl' ) ,
} ,
null ,
2
)
)
throw err
}
2021-12-05 22:53:11 +01:00
if ( err instanceof NoFallbackError && bubbleNoFallback ) {
throw err
}
2022-05-01 11:23:06 +02:00
if ( err instanceof DecodeError || err instanceof NormalizeError ) {
2021-12-05 22:53:11 +01:00
res . statusCode = 400
return await this . renderErrorToResponse ( ctx , err )
}
res . statusCode = 500
2022-08-31 01:14:12 +02:00
// if pages/500 is present we still need to trigger
// /_error `getInitialProps` to allow reporting error
if ( await this . hasPage ( '/500' ) ) {
ctx . query . __nextCustomErrorRender = '1'
await this . renderErrorToResponse ( ctx , err )
delete ctx . query . __nextCustomErrorRender
}
2021-12-05 22:53:11 +01:00
const isWrappedError = err instanceof WrappedBuildError
if ( ! isWrappedError ) {
2022-04-26 19:54:28 +02:00
if (
( this . minimalMode && process . env . NEXT_RUNTIME !== 'edge' ) ||
this . renderOpts . dev
) {
2021-12-05 22:53:11 +01:00
if ( isError ( err ) ) err . page = page
throw err
}
2022-01-11 21:40:03 +01:00
this . logError ( getProperError ( err ) )
2021-12-05 22:53:11 +01:00
}
2022-08-31 01:14:12 +02:00
const response = await this . renderErrorToResponse (
ctx ,
isWrappedError ? ( err as WrappedBuildError ) . innerError : err
)
2021-12-05 22:53:11 +01:00
return response
}
2022-06-15 21:32:44 +02:00
if (
2022-06-21 21:04:48 +02:00
this . router . catchAllMiddleware [ 0 ] &&
2022-06-15 21:32:44 +02:00
! ! ctx . req . headers [ 'x-nextjs-data' ] &&
( ! res . statusCode || res . statusCode === 200 || res . statusCode === 404 )
) {
res . setHeader (
'x-nextjs-matched-path' ,
` ${ query . __nextLocale ? ` / ${ query . __nextLocale } ` : '' } ${ pathname } `
)
res . statusCode = 200
res . setHeader ( 'content-type' , 'application/json' )
res . body ( '{}' )
res . send ( )
return null
}
2021-12-05 22:53:11 +01:00
res . statusCode = 404
return this . renderErrorToResponse ( ctx , null )
}
public async renderToHTML (
2022-01-14 22:01:35 +01:00
req : BaseNextRequest ,
res : BaseNextResponse ,
2021-12-05 22:53:11 +01:00
pathname : string ,
query : ParsedUrlQuery = { }
) : Promise < string | null > {
return this . getStaticHTML ( ( ctx ) = > this . renderToResponse ( ctx ) , {
req ,
res ,
pathname ,
query ,
} )
}
public async renderError (
err : Error | null ,
2022-01-14 22:01:35 +01:00
req : BaseNextRequest ,
res : BaseNextResponse ,
2021-12-05 22:53:11 +01:00
pathname : string ,
query : NextParsedUrlQuery = { } ,
setHeaders = true
) : Promise < void > {
if ( setHeaders ) {
res . setHeader (
'Cache-Control' ,
'no-cache, no-store, max-age=0, must-revalidate'
)
}
return this . pipe (
async ( ctx ) = > {
const response = await this . renderErrorToResponse ( ctx , err )
if ( this . minimalMode && res . statusCode === 500 ) {
throw err
}
return response
} ,
{ req , res , pathname , query }
)
}
private customErrorNo404Warn = execOnce ( ( ) = > {
Log . warn (
` 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://nextjs.org/docs/messages/custom-error-no-custom-404 `
)
} )
private async renderErrorToResponse (
ctx : RequestContext ,
2022-01-11 21:40:03 +01:00
err : Error | null
2021-12-05 22:53:11 +01:00
) : Promise < ResponsePayload | null > {
const { res , query } = ctx
try {
let result : null | FindComponentsResult = null
const is404 = res . statusCode === 404
let using404Page = false
// use static 404 page if available and is 404 response
2022-08-22 18:59:23 +02:00
if ( is404 && ( await this . hasPage ( '/404' ) ) ) {
2022-09-06 19:03:21 +02:00
result = await this . findPageComponents ( {
pathname : '/404' ,
query ,
params : { } ,
isAppPath : false ,
} )
2021-12-05 22:53:11 +01:00
using404Page = result !== null
}
let statusPage = ` / ${ res . statusCode } `
2022-08-31 01:14:12 +02:00
if (
! ctx . query . __nextCustomErrorRender &&
! result &&
STATIC_STATUS_PAGES . includes ( statusPage )
) {
2022-08-22 18:59:23 +02:00
// skip ensuring /500 in dev mode as it isn't used and the
// dev overlay is used instead
if ( statusPage !== '/500' || ! this . renderOpts . dev ) {
2022-09-06 19:03:21 +02:00
result = await this . findPageComponents ( {
pathname : statusPage ,
query ,
params : { } ,
isAppPath : false ,
} )
2022-08-22 18:59:23 +02:00
}
2021-12-05 22:53:11 +01:00
}
if ( ! result ) {
2022-09-06 19:03:21 +02:00
result = await this . findPageComponents ( {
pathname : '/_error' ,
query ,
params : { } ,
isAppPath : false ,
} )
2021-12-05 22:53:11 +01:00
statusPage = '/_error'
}
if (
process . env . NODE_ENV !== 'production' &&
! using404Page &&
( await this . hasPage ( '/_error' ) ) &&
! ( await this . hasPage ( '/404' ) )
) {
this . customErrorNo404Warn ( )
}
try {
return await this . renderToResponseWithComponents (
{
. . . ctx ,
pathname : statusPage ,
renderOpts : {
. . . ctx . renderOpts ,
err ,
} ,
} ,
result !
)
} catch ( maybeFallbackError ) {
if ( maybeFallbackError instanceof NoFallbackError ) {
throw new Error ( 'invariant: failed to render error page' )
}
throw maybeFallbackError
}
} catch ( error ) {
2022-01-11 21:40:03 +01:00
const renderToHtmlError = getProperError ( error )
2021-12-05 22:53:11 +01:00
const isWrappedError = renderToHtmlError instanceof WrappedBuildError
if ( ! isWrappedError ) {
2022-01-11 21:40:03 +01:00
this . logError ( renderToHtmlError )
2021-12-05 22:53:11 +01:00
}
res . statusCode = 500
const fallbackComponents = await this . getFallbackErrorComponents ( )
if ( fallbackComponents ) {
return this . renderToResponseWithComponents (
{
. . . ctx ,
pathname : '/_error' ,
renderOpts : {
. . . ctx . renderOpts ,
// We render `renderToHtmlError` here because `err` is
// already captured in the stacktrace.
err : isWrappedError
? renderToHtmlError . innerError
: renderToHtmlError ,
} ,
} ,
{
query ,
components : fallbackComponents ,
}
)
}
return {
type : 'html' ,
body : RenderResult.fromStatic ( 'Internal Server Error' ) ,
}
}
}
public async renderErrorToHTML (
err : Error | null ,
2022-01-14 22:01:35 +01:00
req : BaseNextRequest ,
res : BaseNextResponse ,
2021-12-05 22:53:11 +01:00
pathname : string ,
query : ParsedUrlQuery = { }
) : Promise < string | null > {
return this . getStaticHTML ( ( ctx ) = > this . renderErrorToResponse ( ctx , err ) , {
req ,
res ,
pathname ,
query ,
} )
}
protected async getFallbackErrorComponents ( ) : Promise < LoadComponentsReturnType | null > {
// The development server will provide an implementation for this
return null
}
public async render404 (
2022-01-14 22:01:35 +01:00
req : BaseNextRequest ,
res : BaseNextResponse ,
2021-12-05 22:53:11 +01:00
parsedUrl? : NextUrlWithParsedQuery ,
setHeaders = true
) : Promise < void > {
const { pathname , query } : NextUrlWithParsedQuery = parsedUrl
? parsedUrl
: parseUrl ( req . url ! , true )
if ( this . nextConfig . i18n ) {
query . __nextLocale =
query . __nextLocale || this . nextConfig . i18n . defaultLocale
query . __nextDefaultLocale =
query . __nextDefaultLocale || this . nextConfig . i18n . defaultLocale
}
res . statusCode = 404
return this . renderError ( null , req , res , pathname ! , query , setHeaders )
}
}