2020-03-29 00:56:38 +01:00
import compression from 'next/dist/compiled/compression'
2019-05-27 20:20:33 +02:00
import fs from 'fs'
2020-11-06 03:33:14 +01:00
import chalk from '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'
2020-03-26 17:58:15 +01:00
import { join , relative , resolve , sep } from 'path'
2020-09-24 08:05:40 +02:00
import {
parse as parseQs ,
stringify as stringifyQs ,
ParsedUrlQuery ,
} from 'querystring'
2019-11-11 04:24:53 +01:00
import { format as formatUrl , parse as parseUrl , UrlWithParsedQuery } from 'url'
2020-02-27 13:23:28 +01:00
import { PrerenderManifest } from '../../build'
2020-02-12 02:16:42 +01:00
import {
getRedirectStatus ,
Header ,
Redirect ,
Rewrite ,
RouteType ,
2020-06-09 22:16:23 +02:00
CustomRoutes ,
} from '../../lib/load-custom-routes'
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 ,
2020-11-04 23:18:44 +01:00
PERMANENT_REDIRECT_STATUS ,
2020-02-12 02:16:42 +01:00
PRERENDER_MANIFEST ,
2019-11-11 04:24:53 +01:00
ROUTES_MANIFEST ,
2019-06-10 02:18:38 +02:00
SERVERLESS_DIRECTORY ,
2020-02-12 02:16:42 +01:00
SERVER_DIRECTORY ,
2021-02-22 17:29:50 +01:00
STATIC_STATUS_PAGES ,
2020-11-20 16:07:00 +01:00
TEMPORARY_REDIRECT_STATUS ,
2019-04-24 16:47:50 +02:00
} 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'
2018-02-26 12:03:27 +01:00
import * as envConfig from '../lib/runtime-config'
2019-10-23 02:40:33 +02:00
import { isResSent , NextApiRequest , NextApiResponse } from '../lib/utils'
2020-10-07 23:11:01 +02:00
import {
apiResolver ,
setLazyProp ,
getCookieParser ,
tryGetPreviewData ,
__ApiPreviewProps ,
} from './api-utils'
2021-02-27 01:29:32 +01:00
import { DomainLocales , isTargetLikeServerless , NextConfig } from './config'
2020-08-21 21:11:25 +02:00
import pathMatch from '../lib/router/utils/path-match'
2019-05-27 20:20:33 +02:00
import { recursiveReadDirSync } from './lib/recursive-readdir-sync'
2019-07-07 23:45:40 +02:00
import { loadComponents , LoadComponentsReturnType } from './load-components'
2020-02-12 02:16:42 +01:00
import { normalizePagePath } from './normalize-page-path'
2020-03-02 11:58:47 +01:00
import { RenderOpts , RenderOptsPartial , renderToHTML } from './render'
2020-07-28 12:19:28 +02:00
import { getPagePath , requireFontManifest } from './require'
2019-12-23 22:20:17 +01:00
import Router , {
DynamicRoutes ,
PageChecker ,
2020-02-12 02:16:42 +01:00
Params ,
route ,
Route ,
2019-12-23 22:20:17 +01:00
} from './router'
2020-11-07 05:30:14 +01:00
import prepareDestination , {
compileNonPath ,
} from '../lib/router/utils/prepare-destination'
2020-11-14 08:12:47 +01:00
import { sendPayload , setRevalidateHeaders } from './send-payload'
2019-05-27 20:20:33 +02:00
import { serveStatic } from './serve-static'
2020-06-28 22:58:43 +02:00
import { IncrementalCache } from './incremental-cache'
2020-04-05 13:19:14 +02:00
import { execOnce } from '../lib/utils'
2019-11-13 05:07:19 +01:00
import { isBlockedPage } from './utils'
2020-09-25 20:14:28 +02:00
import { loadEnvConfig } from '@next/env'
2020-05-12 21:58:21 +02:00
import './node-polyfill-fetch'
2020-05-25 23:15:56 +02:00
import { PagesManifest } from '../../build/webpack/plugins/pages-manifest-plugin'
2020-06-30 06:06:39 +02:00
import { removePathTrailingSlash } from '../../client/normalize-trailing-slash'
2020-06-23 07:49:48 +02:00
import getRouteFromAssetPath from '../lib/router/utils/get-route-from-asset-path'
2020-07-28 12:19:28 +02:00
import { FontManifest } from './font-utils'
2020-09-14 18:48:04 +02:00
import { denormalizePagePath } from './denormalize-page-path'
2020-10-07 23:11:01 +02:00
import accept from '@hapi/accept'
import { normalizeLocalePath } from '../lib/i18n/normalize-locale-path'
import { detectLocaleCookie } from '../lib/i18n/detect-locale-cookie'
2020-09-25 20:14:28 +02:00
import * as Log from '../../build/output/log'
2020-10-16 13:10:01 +02:00
import { imageOptimizer } from './image-optimizer'
2020-10-14 11:56:58 +02:00
import { detectDomainLocale } from '../lib/i18n/detect-domain-locale'
2020-10-14 23:02:38 +02:00
import cookie from 'next/dist/compiled/cookie'
2020-12-28 21:08:58 +01:00
import escapePathDelimiters from '../lib/router/utils/escape-path-delimiters'
2020-12-16 21:46:55 +01:00
import { getUtils } from '../../build/webpack/loaders/next-serverless-loader/utils'
2019-11-09 23:34:53 +01:00
const getCustomRouteMatcher = pathMatch ( true )
2018-12-09 22:46:45 +01:00
2019-07-29 20:35:09 +02:00
type Middleware = (
req : IncomingMessage ,
res : ServerResponse ,
next : ( err? : Error ) = > void
) = > void
2020-02-13 05:28:31 +01:00
type FindComponentsResult = {
components : LoadComponentsReturnType
query : ParsedUrlQuery
}
2020-11-08 03:18:13 +01:00
type DynamicRouteItem = {
page : string
match : ReturnType < typeof getRouteMatcher >
}
2019-04-24 16:47:50 +02:00
export type ServerConstructor = {
2019-06-10 16:20:39 +02:00
/ * *
* Where the Next project is located - @default '.'
* /
2019-03-01 20:51:13 +01:00
dir? : string
2019-06-10 16:20:39 +02:00
/ * *
* Hide error messages containing server information - @default false
* /
2019-03-01 20:51:13 +01:00
quiet? : boolean
2019-06-10 16:20:39 +02:00
/ * *
* Object what you would use in next . config . js - @default { }
* /
2020-12-04 11:14:55 +01:00
conf? : NextConfig | null
2019-09-24 10:50:04 +02:00
dev? : boolean
2020-03-06 17:14:39 +01:00
customServer? : boolean
2018-12-09 22:46:45 +01:00
}
2017-04-07 19:58:35 +02:00
2016-10-06 01:52:50 +02:00
export default class Server {
2018-12-09 22:46:45 +01:00
dir : string
quiet : boolean
nextConfig : NextConfig
distDir : string
2019-09-30 11:08:15 +02:00
pagesDir? : string
2019-05-03 18:57:47 +02:00
publicDir : string
2019-11-13 05:07:19 +01:00
hasStaticDir : boolean
2019-12-10 16:08:42 +01:00
serverBuildDir : string
2020-05-25 23:15:56 +02:00
pagesManifest? : PagesManifest
2018-12-09 22:46:45 +01:00
buildId : string
2020-12-16 21:46:55 +01:00
minimalMode : boolean
2018-12-09 22:46:45 +01:00
renderOpts : {
2019-04-15 09:48:14 +02:00
poweredByHeader : boolean
2019-03-01 20:51:13 +01:00
buildId : string
generateEtags : boolean
runtimeConfig ? : { [ key : string ] : any }
2019-05-29 13:57:26 +02:00
assetPrefix? : string
canonicalBase : string
dev? : boolean
2020-03-02 11:58:47 +01:00
previewProps : __ApiPreviewProps
2020-03-06 17:14:39 +01:00
customServer? : boolean
2020-03-24 09:31:04 +01:00
ampOptimizerConfig ? : { [ key : string ] : any }
2020-04-14 09:50:39 +02:00
basePath : string
2020-12-21 20:26:00 +01:00
optimizeFonts : boolean
2020-10-14 11:57:10 +02:00
images : string
2020-07-28 12:19:28 +02:00
fontManifest : FontManifest
2020-08-05 19:49:44 +02:00
optimizeImages : boolean
2020-12-01 19:02:07 +01:00
optimizeCss : any
2020-10-07 23:11:01 +02:00
locale? : string
locales? : string [ ]
2020-10-08 13:12:17 +02:00
defaultLocale? : string
2020-12-30 07:44:07 +01:00
domainLocales? : DomainLocales
2021-02-25 10:56:11 +01:00
distDir : string
2018-12-09 22:46:45 +01:00
}
2019-07-29 20:35:09 +02:00
private compression? : Middleware
2019-11-16 03:00:24 +01:00
private onErrorMiddleware ? : ( { err } : { err : Error } ) = > Promise < void >
2020-06-28 22:58:43 +02:00
private incrementalCache : IncrementalCache
2018-12-09 22:46:45 +01:00
router : Router
2019-12-23 22:20:17 +01:00
protected dynamicRoutes? : DynamicRoutes
2020-06-09 22:16:23 +02:00
protected customRoutes : CustomRoutes
2018-12-09 22:46:45 +01:00
2019-03-01 20:51:13 +01:00
public constructor ( {
dir = '.' ,
quiet = false ,
2021-02-27 01:29:32 +01:00
conf ,
2019-09-24 10:50:04 +02:00
dev = false ,
2020-12-16 21:46:55 +01:00
minimalMode = false ,
2020-03-06 17:14:39 +01:00
customServer = true ,
2021-02-27 01:29:32 +01:00
} : ServerConstructor & { conf : NextConfig ; minimalMode? : boolean } ) {
2016-10-06 13:05:52 +02:00
this . dir = resolve ( dir )
2016-12-16 21:33:08 +01:00
this . quiet = quiet
2020-09-25 20:14:28 +02:00
loadEnvConfig ( this . dir , dev , Log )
2020-03-26 13:32:41 +01:00
2021-02-27 01:29:32 +01:00
this . nextConfig = 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 )
2021-02-11 10:31:49 +01:00
this . hasStaticDir = ! minimalMode && fs . existsSync ( join ( this . dir , 'static' ) )
2018-02-05 13:39:32 +01:00
2018-03-13 14:18:59 +01:00
// 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 ,
2019-07-29 20:35:09 +02:00
compress ,
2019-03-01 20:51:13 +01:00
} = this . nextConfig
2019-01-29 13:42:07 +01:00
2018-09-28 14:05:23 +02:00
this . buildId = this . readBuildId ( )
2020-12-16 21:46:55 +01:00
this . minimalMode = minimalMode
2019-05-29 02:32:18 +02:00
2017-03-07 19:43:56 +01:00
this . renderOpts = {
2019-04-15 09:48:14 +02:00
poweredByHeader : this.nextConfig.poweredByHeader ,
2019-05-29 02:32:18 +02:00
canonicalBase : this.nextConfig.amp.canonicalBase ,
2017-04-18 06:18:43 +02:00
buildId : this.buildId ,
2018-12-31 14:44:27 +01:00
generateEtags ,
2020-03-02 11:58:47 +01:00
previewProps : this.getPreviewProps ( ) ,
2020-03-06 17:14:39 +01:00
customServer : customServer === true ? true : undefined ,
2020-03-24 09:31:04 +01:00
ampOptimizerConfig : this.nextConfig.experimental.amp?.optimizer ,
2020-06-18 12:10:20 +02:00
basePath : this.nextConfig.basePath ,
2020-10-14 11:57:10 +02:00
images : JSON.stringify ( this . nextConfig . images ) ,
2021-03-26 16:19:48 +01:00
optimizeFonts : ! ! this . nextConfig . experimental . optimizeFonts && ! dev ,
2020-12-21 20:26:00 +01:00
fontManifest :
this . nextConfig . experimental . optimizeFonts && ! dev
? requireFontManifest ( this . distDir , this . _isLikeServerless )
: null ,
2021-03-26 16:19:48 +01:00
optimizeImages : ! ! this . nextConfig . experimental . optimizeImages ,
2020-12-01 19:02:07 +01:00
optimizeCss : this.nextConfig.experimental.optimizeCss ,
2020-12-30 07:44:07 +01:00
domainLocales : this.nextConfig.i18n?.domains ,
2021-02-25 10:56:11 +01:00
distDir : this.distDir ,
2017-03-07 19:43:56 +01:00
}
2016-12-16 21:33:08 +01:00
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
2019-07-10 16:43:04 +02:00
if ( Object . keys ( publicRuntimeConfig ) . length > 0 ) {
2018-02-27 17:50:14 +01:00
this . renderOpts . runtimeConfig = publicRuntimeConfig
2018-02-26 12:03:27 +01:00
}
2019-08-06 00:26:20 +02:00
if ( compress && this . nextConfig . target === 'server' ) {
2019-07-29 20:35:09 +02:00
this . compression = compression ( ) as Middleware
}
2018-02-27 17:50:14 +01:00
// Initialize next/config with the environment configuration
2020-02-03 23:34:23 +01:00
envConfig . setConfig ( {
serverRuntimeConfig ,
publicRuntimeConfig ,
} )
2018-02-27 17:50:14 +01:00
2019-12-10 16:08:42 +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 )
}
2020-06-09 22:16:23 +02:00
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 )
2019-09-24 10:50:04 +02:00
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 ) {
2019-12-10 16:08:42 +01:00
const initServer = require ( join ( this . serverBuildDir , 'init-server.js' ) )
. default
2019-11-01 20:13:13 +01:00
this . onErrorMiddleware = require ( join (
2019-12-10 16:08:42 +01:00
this . serverBuildDir ,
2019-11-01 20:13:13 +01:00
'on-error-server.js'
) ) . default
initServer ( )
}
2020-06-28 22:58:43 +02:00
this . incrementalCache = new IncrementalCache ( {
2019-09-24 10:50:04 +02:00
dev ,
distDir : this.distDir ,
pagesDir : join (
this . distDir ,
2020-06-22 23:12:36 +02:00
this . _isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY ,
2019-09-24 10:50:04 +02:00
'pages'
) ,
2020-11-11 03:09:45 +01:00
locales : this.nextConfig.i18n?.locales ,
2020-12-16 21:46:55 +01:00
flushToDisk : ! minimalMode && this . nextConfig . experimental . sprFlushToDisk ,
2019-09-24 10:50:04 +02:00
} )
2020-07-28 12:19:28 +02:00
/ * *
* 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
2020-12-04 10:52:54 +01:00
* ` process.env.__NEXT_OPTIMIZE_IMAGES ` .
* TODO ( atcastle @ ) : Remove this when experimental . optimizeImages are being clened up .
2020-07-28 12:19:28 +02:00
* /
2020-12-21 20:26:00 +01:00
if ( this . renderOpts . optimizeFonts ) {
process . env . __NEXT_OPTIMIZE_FONTS = JSON . stringify ( true )
}
2020-08-05 19:49:44 +02:00
if ( this . renderOpts . optimizeImages ) {
process . env . __NEXT_OPTIMIZE_IMAGES = JSON . stringify ( true )
}
2020-12-01 19:02:07 +01:00
if ( this . renderOpts . optimizeCss ) {
process . env . __NEXT_OPTIMIZE_CSS = JSON . stringify ( true )
}
2016-12-16 21:33:08 +01:00
}
2016-10-06 01:52:50 +02:00
2020-10-16 13:10:01 +02:00
public logError ( err : Error ) : void {
2019-11-01 20:13:13 +01:00
if ( this . onErrorMiddleware ) {
this . onErrorMiddleware ( { err } )
}
2018-12-31 14:44:27 +01:00
if ( this . quiet ) return
2019-11-01 20:13:13 +01:00
console . error ( err )
2018-12-18 17:12:49 +01:00
}
2020-02-13 05:28:31 +01:00
private async handleRequest (
2019-03-01 20:51:13 +01:00
req : IncomingMessage ,
res : ServerResponse ,
2019-05-29 13:57:26 +02:00
parsedUrl? : UrlWithParsedQuery
2019-03-01 20:51:13 +01:00
) : Promise < void > {
2020-10-07 23:11:01 +02:00
setLazyProp ( { req : req as any } , 'cookies' , getCookieParser ( req ) )
2017-04-07 19:58:35 +02:00
// Parse url if parsedUrl not provided
2017-07-02 07:52:39 +02:00
if ( ! parsedUrl || typeof parsedUrl !== 'object' ) {
2018-12-09 22:46:45 +01:00
const url : any = req . url
parsedUrl = parseUrl ( url , true )
2017-04-07 19:58:35 +02:00
}
2021-01-16 16:46:21 +01:00
const { basePath , i18n } = this . nextConfig
2017-02-09 04:22:48 +01:00
2017-04-07 19:58:35 +02:00
// Parse the querystring ourselves if the user doesn't handle querystring parsing
if ( typeof parsedUrl . query === 'string' ) {
parsedUrl . query = parseQs ( parsedUrl . query )
2016-12-16 21:33:08 +01:00
}
2020-11-07 05:30:14 +01:00
; ( req as any ) . __NEXT_INIT_QUERY = Object . assign ( { } , parsedUrl . query )
2017-04-07 19:58:35 +02:00
2020-07-12 21:03:49 +02:00
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 , '' ) || '/'
2019-12-29 20:03:12 +01:00
}
2021-01-16 16:46:21 +01:00
if (
this . minimalMode &&
req . headers [ 'x-matched-path' ] &&
typeof req . headers [ 'x-matched-path' ] === 'string'
) {
const reqUrlIsDataUrl = req . url ? . includes ( '/_next/data' )
const matchedPathIsDataUrl = req . headers [ 'x-matched-path' ] ? . includes (
'/_next/data'
)
const isDataUrl = reqUrlIsDataUrl || matchedPathIsDataUrl
let parsedPath = parseUrl (
isDataUrl ? req . url ! : ( req . headers [ 'x-matched-path' ] as string ) ,
true
)
const { pathname , query } = parsedPath
let matchedPathname = pathname as string
2021-03-04 23:09:45 +01:00
let matchedPathnameNoExt = isDataUrl
2021-01-16 16:46:21 +01:00
? matchedPathname . replace ( /\.json$/ , '' )
: matchedPathname
if ( i18n ) {
const localePathResult = normalizeLocalePath (
matchedPathname || '/' ,
i18n . locales
)
if ( localePathResult . detectedLocale ) {
parsedUrl . query . __nextLocale = localePathResult . detectedLocale
}
}
2021-03-04 23:09:45 +01:00
if ( isDataUrl ) {
matchedPathname = denormalizePagePath ( matchedPathname )
matchedPathnameNoExt = denormalizePagePath ( matchedPathnameNoExt )
}
2021-01-16 16:46:21 +01:00
const pageIsDynamic = isDynamicRoute ( matchedPathnameNoExt )
2021-03-26 16:19:48 +01:00
const combinedRewrites : Rewrite [ ] = [ ]
combinedRewrites . push ( . . . this . customRoutes . rewrites . beforeFiles )
combinedRewrites . push ( . . . this . customRoutes . rewrites . afterFiles )
combinedRewrites . push ( . . . this . customRoutes . rewrites . fallback )
2021-01-16 16:46:21 +01:00
const utils = getUtils ( {
pageIsDynamic ,
page : matchedPathnameNoExt ,
i18n : this.nextConfig.i18n ,
basePath : this.nextConfig.basePath ,
2021-03-26 16:19:48 +01:00
rewrites : combinedRewrites ,
2021-01-16 16:46:21 +01:00
} )
2021-03-24 17:50:16 +01:00
utils . handleRewrites ( req , parsedUrl )
2021-01-16 16:46:21 +01:00
// interpolate dynamic params and normalize URL if needed
if ( pageIsDynamic ) {
let params : ParsedUrlQuery | false = { }
2021-03-02 20:51:53 +01:00
Object . assign ( parsedUrl . query , query )
const paramsResult = utils . normalizeDynamicRouteParams ( parsedUrl . query )
2021-01-16 16:46:21 +01:00
if ( paramsResult . hasValidParams ) {
params = paramsResult . params
} else if ( req . headers [ 'x-now-route-matches' ] ) {
const opts : Record < string , string > = { }
params = utils . getParamsFromRouteMatches (
req ,
opts ,
( parsedUrl . query . __nextLocale as string | undefined ) || ''
)
if ( opts . locale ) {
parsedUrl . query . __nextLocale = opts . locale
}
} else {
params = utils . dynamicRouteMatcher ! ( matchedPathnameNoExt )
}
if ( params ) {
params = utils . normalizeDynamicRouteParams ( params ) . params
matchedPathname = utils . interpolateDynamicPath (
matchedPathname ,
params
)
req . url = utils . interpolateDynamicPath ( req . url ! , params )
}
if ( reqUrlIsDataUrl && matchedPathIsDataUrl ) {
req . url = formatUrl ( {
. . . parsedPath ,
pathname : matchedPathname ,
} )
}
2021-03-02 20:51:53 +01:00
2021-01-16 16:46:21 +01:00
Object . assign ( parsedUrl . query , params )
utils . normalizeVercelUrl ( req , true )
}
parsedUrl . pathname = ` ${ basePath || '' } ${
matchedPathname === '/' && basePath ? '' : matchedPathname
} `
}
if ( i18n ) {
2020-10-07 23:11:01 +02:00
// get pathname from URL with basePath stripped for locale detection
2020-11-12 06:26:48 +01:00
let { pathname , . . . parsed } = parseUrl ( req . url || '/' )
pathname = pathname || '/'
2020-10-14 11:56:58 +02:00
let defaultLocale = i18n . defaultLocale
2020-10-07 23:11:01 +02:00
let detectedLocale = detectLocaleCookie ( req , i18n . locales )
2020-11-02 22:02:15 +01:00
let acceptPreferredLocale =
i18n . localeDetection !== false
? accept . language ( req . headers [ 'accept-language' ] , i18n . locales )
: detectedLocale
2020-10-07 23:11:01 +02:00
2020-10-20 12:23:11 +02:00
const { host } = req ? . headers || { }
2021-03-24 17:50:16 +01:00
// remove port from host if present
2020-10-20 12:23:11 +02:00
const hostname = host ? . split ( ':' ) [ 0 ] . toLowerCase ( )
const detectedDomain = detectDomainLocale ( i18n . domains , hostname )
2020-10-14 11:56:58 +02:00
if ( detectedDomain ) {
defaultLocale = detectedDomain . defaultLocale
detectedLocale = defaultLocale
2021-02-11 11:18:24 +01:00
; ( req as any ) . __nextIsLocaleDomain = true
2020-10-14 11:56:58 +02:00
}
2020-10-10 12:22:45 +02:00
2020-10-14 23:02:38 +02:00
// if not domain specific locale use accept-language preferred
detectedLocale = detectedLocale || acceptPreferredLocale
2020-10-07 23:11:01 +02:00
2020-10-14 11:56:58 +02:00
let localeDomainRedirect : string | undefined
2020-11-17 22:46:46 +01:00
; ( req as any ) . __nextHadTrailingSlash = pathname ! . endsWith ( '/' )
if ( pathname === '/' ) {
; ( req as any ) . __nextHadTrailingSlash = this . nextConfig . trailingSlash
}
2020-10-14 11:56:58 +02:00
const localePathResult = normalizeLocalePath ( pathname ! , i18n . locales )
if ( localePathResult . detectedLocale ) {
detectedLocale = localePathResult . detectedLocale
req . url = formatUrl ( {
. . . parsed ,
pathname : localePathResult.pathname ,
} )
2020-10-16 01:00:08 +02:00
; ( req as any ) . __nextStrippedLocale = true
2020-10-27 09:45:39 +01:00
}
// If a detected locale is a domain specific locale and we aren't already
// on that domain and path prefix redirect to it to prevent duplicate
// content from multiple domains
2020-11-12 06:26:48 +01:00
if ( detectedDomain && pathname === '/' ) {
2020-10-27 09:45:39 +01:00
const localeToCheck = acceptPreferredLocale
// const localeToCheck = localePathResult.detectedLocale
// ? detectedLocale
// : acceptPreferredLocale
2020-10-14 11:56:58 +02:00
2020-10-21 11:35:01 +02:00
const matchedDomain = detectDomainLocale (
i18n . domains ,
undefined ,
2020-10-27 09:45:39 +01:00
localeToCheck
2020-10-21 11:35:01 +02:00
)
2020-10-27 09:45:39 +01:00
if (
matchedDomain &&
( matchedDomain . domain !== detectedDomain . domain ||
localeToCheck !== matchedDomain . defaultLocale )
) {
2020-10-21 11:35:01 +02:00
localeDomainRedirect = ` http ${ matchedDomain . http ? '' : 's' } :// ${
matchedDomain . domain
2020-10-27 09:45:39 +01:00
} / $ {
localeToCheck === matchedDomain . defaultLocale ? '' : localeToCheck
2020-10-21 11:35:01 +02:00
} `
2020-10-14 11:56:58 +02:00
}
}
2020-10-08 13:12:17 +02:00
const denormalizedPagePath = denormalizePagePath ( pathname || '/' )
2020-10-10 12:22:45 +02:00
const detectedDefaultLocale =
2020-10-11 22:40:47 +02:00
! detectedLocale ||
detectedLocale . toLowerCase ( ) === defaultLocale . toLowerCase ( )
2020-10-21 11:35:01 +02:00
const shouldStripDefaultLocale = false
// detectedDefaultLocale &&
// denormalizedPagePath.toLowerCase() ===
// `/${i18n.defaultLocale.toLowerCase()}`
2020-10-14 23:02:38 +02:00
2020-10-08 13:12:17 +02:00
const shouldAddLocalePrefix =
! detectedDefaultLocale && denormalizedPagePath === '/'
2020-10-10 12:22:45 +02:00
2020-10-14 11:56:58 +02:00
detectedLocale = detectedLocale || i18n . defaultLocale
2020-10-08 13:12:17 +02:00
2020-10-07 23:11:01 +02:00
if (
i18n . localeDetection !== false &&
2020-10-14 11:56:58 +02:00
( localeDomainRedirect ||
shouldAddLocalePrefix ||
shouldStripDefaultLocale )
2020-10-07 23:11:01 +02:00
) {
2020-10-14 23:02:38 +02:00
// 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 : '/' ,
} ) ,
] )
}
2020-10-07 23:11:01 +02:00
res . setHeader (
'Location' ,
2020-11-17 22:46:46 +01:00
localeDomainRedirect
? localeDomainRedirect
: formatUrl ( {
// make sure to include any query values when redirecting
. . . parsed ,
pathname : shouldStripDefaultLocale
? basePath || ` / `
: ` ${ basePath || '' } / ${ detectedLocale } ` ,
} )
2020-10-07 23:11:01 +02:00
)
2020-11-20 16:07:00 +01:00
res . statusCode = TEMPORARY_REDIRECT_STATUS
2020-10-07 23:11:01 +02:00
res . end ( )
2020-10-08 13:12:17 +02:00
return
2020-10-07 23:11:01 +02:00
}
2020-11-03 00:00:41 +01:00
2020-11-11 03:09:45 +01:00
parsedUrl . query . __nextDefaultLocale =
detectedDomain ? . defaultLocale || i18n . defaultLocale
2021-01-16 16:46:21 +01:00
if ( ! this . minimalMode || ! parsedUrl . query . __nextLocale ) {
parsedUrl . query . __nextLocale =
localePathResult . detectedLocale ||
detectedDomain ? . defaultLocale ||
defaultLocale
2020-12-16 21:46:55 +01:00
}
}
2017-06-19 09:27:35 +02:00
res . statusCode = 200
2020-02-13 05:28:31 +01:00
try {
return await this . run ( req , res , parsedUrl )
} catch ( err ) {
2021-02-11 10:31:49 +01:00
if ( this . minimalMode ) {
throw err
}
2019-03-01 20:51:13 +01:00
this . logError ( err )
res . statusCode = 500
res . end ( 'Internal Server Error' )
2020-02-13 05:28:31 +01:00
}
2017-04-07 19:58:35 +02:00
}
2018-12-31 14:44:27 +01:00
public getRequestHandler() {
2017-04-07 19:58:35 +02:00
return this . handleRequest . bind ( this )
2016-10-06 01:52:50 +02:00
}
2020-05-27 17:43:10 +02:00
public setAssetPrefix ( prefix? : string ) : void {
2018-02-03 17:12:01 +01:00
this . renderOpts . assetPrefix = prefix ? prefix . replace ( /\/$/ , '' ) : ''
2018-02-02 15:43:36 +01:00
}
2018-10-01 16:31:47 +02:00
// Backwards compatibility
2018-12-31 14:44:27 +01:00
public async prepare ( ) : Promise < void > { }
2016-10-17 09:07:41 +02:00
2018-09-28 14:05:23 +02:00
// Backwards compatibility
2019-10-04 18:11:39 +02:00
protected async close ( ) : Promise < void > { }
2018-09-28 14:05:23 +02:00
2020-05-27 17:43:10 +02:00
protected setImmutableAssetCacheControl ( res : ServerResponse ) : void {
2018-09-28 14:05:23 +02:00
res . setHeader ( 'Cache-Control' , 'public, max-age=31536000, immutable' )
2016-12-17 05:04:40 +01:00
}
2020-06-09 22:16:23 +02:00
protected getCustomRoutes ( ) : CustomRoutes {
2021-03-26 16:19:48 +01:00
const customRoutes = require ( join ( this . distDir , ROUTES_MANIFEST ) )
let rewrites : CustomRoutes [ 'rewrites' ]
// rewrites can be stored as an array when an array is
// returned in next.config.js so massage them into
// the expected object format
if ( Array . isArray ( customRoutes . rewrites ) ) {
rewrites = {
beforeFiles : [ ] ,
afterFiles : customRoutes.rewrites ,
fallback : [ ] ,
}
} else {
rewrites = customRoutes . rewrites
}
return Object . assign ( customRoutes , { rewrites } )
2019-11-09 23:34:53 +01:00
}
2020-02-27 13:23:28 +01:00
private _cachedPreviewManifest : PrerenderManifest | undefined
protected getPrerenderManifest ( ) : PrerenderManifest {
if ( this . _cachedPreviewManifest ) {
return this . _cachedPreviewManifest
2020-02-12 02:16:42 +01:00
}
2020-02-27 13:23:28 +01:00
const manifest = require ( join ( this . distDir , PRERENDER_MANIFEST ) )
return ( this . _cachedPreviewManifest = manifest )
}
protected getPreviewProps ( ) : __ApiPreviewProps {
return this . getPrerenderManifest ( ) . preview
2020-02-12 02:16:42 +01:00
}
2019-12-23 22:20:17 +01:00
protected generateRoutes ( ) : {
2020-07-12 21:03:49 +02:00
basePath : string
2020-02-11 00:06:38 +01:00
headers : Route [ ]
2021-03-26 16:19:48 +01:00
rewrites : {
beforeFiles : Route [ ]
afterFiles : Route [ ]
fallback : Route [ ]
}
2019-12-23 22:20:17 +01:00
fsRoutes : Route [ ]
2020-02-11 00:06:38 +01:00
redirects : Route [ ]
2019-12-23 22:20:17 +01:00
catchAllRoute : Route
pageChecker : PageChecker
2020-02-11 00:06:38 +01:00
useFileSystemPublicRoutes : boolean
2019-12-23 22:20:17 +01:00
dynamicRoutes : DynamicRoutes | undefined
2020-11-14 04:35:42 +01:00
locales : string [ ]
2019-12-23 22:20:17 +01:00
} {
2020-10-16 13:10:01 +02:00
const server : Server = this
2019-10-08 05:12:41 +02:00
const publicRoutes = fs . existsSync ( this . publicDir )
? this . generatePublicRoutes ( )
: [ ]
2019-11-09 23:34:53 +01:00
2019-11-13 05:07:19 +01:00
const staticFilesRoute = this . hasStaticDir
2019-11-05 06:55:35 +01:00
? [
{
// 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.
2020-05-27 23:51:11 +02:00
// See more: https://github.com/vercel/next.js/issues/2617
2019-11-05 06:55:35 +01:00
match : route ( '/static/:path*' ) ,
2019-11-18 01:12:48 +01:00
name : 'static catchall' ,
2019-11-05 06:55:35 +01:00
fn : async ( req , res , params , parsedUrl ) = > {
2020-09-24 08:05:40 +02:00
const p = join ( this . dir , 'static' , . . . params . path )
2019-11-05 06:55:35 +01:00
await this . serveStatic ( req , res , p , parsedUrl )
2019-11-18 01:12:48 +01:00
return {
finished : true ,
}
2019-11-05 06:55:35 +01:00
} ,
} as Route ,
]
: [ ]
2019-10-08 05:12:41 +02:00
2019-12-23 22:20:17 +01:00
const fsRoutes : Route [ ] = [
2018-09-28 14:05:23 +02:00
{
2018-10-01 16:31:47 +02:00
match : route ( '/_next/static/:path*' ) ,
2019-11-18 01:12:48 +01:00
type : 'route' ,
name : '_next/static catchall' ,
2018-12-09 22:46:45 +01:00
fn : async ( req , res , params , parsedUrl ) = > {
2019-08-29 22:00:54 +02:00
// make sure to 404 for /_next/static itself
2019-11-18 01:12:48 +01:00
if ( ! params . path ) {
await this . render404 ( req , res , parsedUrl )
return {
finished : true ,
}
}
2019-08-29 22:00:54 +02:00
2019-03-01 20:51:13 +01:00
if (
params . path [ 0 ] === CLIENT_STATIC_FILES_RUNTIME ||
params . path [ 0 ] === 'chunks' ||
2020-01-21 14:19:53 +01:00
params . path [ 0 ] === 'css' ||
params . path [ 0 ] === 'media' ||
2020-06-11 10:57:24 +02:00
params . path [ 0 ] === this . buildId ||
2020-06-20 21:59:47 +02:00
params . path [ 0 ] === 'pages' ||
2020-06-11 10:57:24 +02:00
params . path [ 1 ] === 'pages'
2019-03-01 20:51:13 +01:00
) {
2018-09-28 14:05:23 +02:00
this . setImmutableAssetCacheControl ( res )
2018-05-19 21:43:18 +02:00
}
2019-03-01 20:51:13 +01:00
const p = join (
this . distDir ,
CLIENT_STATIC_FILES_PATH ,
2019-05-29 13:57:26 +02:00
. . . ( params . path || [ ] )
2019-03-01 20:51:13 +01:00
)
2018-12-09 22:46:45 +01:00
await this . serveStatic ( req , res , p , parsedUrl )
2019-11-18 01:12:48 +01:00
return {
finished : true ,
}
2018-12-31 14:44:27 +01:00
} ,
2017-04-03 20:10:24 +02:00
} ,
2019-09-24 10:50:04 +02:00
{
match : route ( '/_next/data/:path*' ) ,
2019-11-18 01:12:48 +01:00
type : 'route' ,
name : '_next/data catchall' ,
2019-09-24 10:50:04 +02:00
fn : async ( req , res , params , _parsedUrl ) = > {
2019-10-10 19:07:51 +02:00
// 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 ) {
2019-11-18 01:12:48 +01:00
await this . render404 ( req , res , _parsedUrl )
return {
finished : true ,
}
2019-10-10 19:07:51 +02:00
}
// 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' ) ) {
2019-11-18 01:12:48 +01:00
await this . render404 ( req , res , _parsedUrl )
return {
finished : true ,
}
2019-10-10 19:07:51 +02:00
}
// re-create page's pathname
2020-10-07 23:11:01 +02:00
let pathname = ` / ${ params . path . join ( '/' ) } `
2020-11-11 21:33:44 +01:00
pathname = getRouteFromAssetPath ( pathname , '.json' )
2020-10-07 23:11:01 +02:00
2020-10-27 16:30:34 +01:00
const { i18n } = this . nextConfig
2020-10-08 13:12:17 +02:00
if ( i18n ) {
2020-10-20 12:23:11 +02:00
const { host } = req ? . headers || { }
// remove port from host and remove port if present
const hostname = host ? . split ( ':' ) [ 0 ] . toLowerCase ( )
2020-10-08 13:12:17 +02:00
const localePathResult = normalizeLocalePath ( pathname , i18n . locales )
2020-10-14 11:56:58 +02:00
const { defaultLocale } =
2020-10-20 12:23:11 +02:00
detectDomainLocale ( i18n . domains , hostname ) || { }
2020-11-20 16:07:00 +01:00
let detectedLocale = ''
2020-10-07 23:11:01 +02:00
if ( localePathResult . detectedLocale ) {
pathname = localePathResult . pathname
detectedLocale = localePathResult . detectedLocale
}
2020-11-11 03:09:45 +01:00
2020-10-14 11:56:58 +02:00
_parsedUrl . query . __nextLocale = detectedLocale !
2020-11-11 03:09:45 +01:00
_parsedUrl . query . __nextDefaultLocale =
defaultLocale || i18n . defaultLocale
2020-11-20 16:07:00 +01:00
if ( ! detectedLocale ) {
_parsedUrl . query . __nextLocale =
_parsedUrl . query . __nextDefaultLocale
await this . render404 ( req , res , _parsedUrl )
return { finished : true }
}
2020-10-07 23:11:01 +02:00
}
2019-10-10 19:07:51 +02:00
2019-09-24 10:50:04 +02:00
const parsedUrl = parseUrl ( pathname , true )
2020-06-01 15:25:00 +02:00
2019-09-24 10:50:04 +02:00
await this . render (
req ,
res ,
pathname ,
2020-01-27 23:50:59 +01:00
{ . . . _parsedUrl . query , _nextDataReq : '1' } ,
2019-09-24 10:50:04 +02:00
parsedUrl
)
2019-11-18 01:12:48 +01:00
return {
finished : true ,
}
2019-09-24 10:50:04 +02:00
} ,
} ,
2020-10-16 13:10:01 +02:00
{
match : route ( '/_next/image' ) ,
type : 'route' ,
name : '_next/image catchall' ,
fn : ( req , res , _params , parsedUrl ) = >
imageOptimizer ( server , req , res , parsedUrl ) ,
} ,
2018-09-28 14:05:23 +02:00
{
2018-10-01 16:31:47 +02:00
match : route ( '/_next/:path*' ) ,
2019-11-18 01:12:48 +01:00
type : 'route' ,
name : '_next catchall' ,
2018-09-28 14:05:23 +02:00
// This path is needed because `render()` does a check for `/_next` and the calls the routing again
2018-12-09 22:46:45 +01:00
fn : async ( req , res , _params , parsedUrl ) = > {
2018-09-28 14:05:23 +02:00
await this . render404 ( req , res , parsedUrl )
2019-11-18 01:12:48 +01:00
return {
finished : true ,
}
2019-05-11 13:18:56 +02:00
} ,
} ,
2019-11-20 16:53:31 +01:00
. . . publicRoutes ,
. . . staticFilesRoute ,
2018-09-28 14:05:23 +02:00
]
2018-09-07 14:38:01 +02:00
2020-11-14 04:35:42 +01:00
const getCustomRoute = (
r : Rewrite | Redirect | Header ,
type : RouteType
) = > {
2020-12-04 11:14:55 +01:00
const match = getCustomRouteMatcher ( r . source )
2020-11-14 04:35:42 +01:00
return {
2020-06-09 22:16:23 +02:00
. . . r ,
type ,
2020-11-14 04:35:42 +01:00
match ,
2020-06-09 22:16:23 +02:00
name : type ,
fn : async ( _req , _res , _params , _parsedUrl ) = > ( { finished : false } ) ,
2020-11-14 04:35:42 +01:00
} as Route & Rewrite & Header
}
2020-06-09 22:16:23 +02:00
// Headers come very first
const headers = this . customRoutes . headers . map ( ( r ) = > {
const headerRoute = getCustomRoute ( r , 'header' )
return {
match : headerRoute.match ,
2021-03-24 17:50:16 +01:00
has : headerRoute.has ,
2020-06-09 22:16:23 +02:00
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 ) {
2020-11-07 05:30:14 +01:00
key = compileNonPath ( key , params )
value = compileNonPath ( value , params )
2020-02-11 00:06:38 +01:00
}
2020-06-09 22:16:23 +02:00
res . setHeader ( key , value )
}
return { finished : false }
} ,
} as Route
} )
2020-11-07 05:30:14 +01:00
// since initial query values are decoded by querystring.parse
// we need to re-encode them here but still allow passing through
// values from rewrites/redirects
const stringifyQuery = ( req : IncomingMessage , query : ParsedUrlQuery ) = > {
const initialQueryValues = Object . values ( ( req as any ) . __NEXT_INIT_QUERY )
return stringifyQs ( query , undefined , undefined , {
encodeURIComponent ( value ) {
if ( initialQueryValues . some ( ( val ) = > val === value ) ) {
return encodeURIComponent ( value )
}
return value
} ,
} )
}
2020-12-16 21:46:55 +01:00
const redirects = this . minimalMode
? [ ]
: this . customRoutes . redirects . map ( ( redirect ) = > {
const redirectRoute = getCustomRoute ( redirect , 'redirect' )
return {
internal : redirectRoute.internal ,
type : redirectRoute . type ,
match : redirectRoute.match ,
2021-03-24 17:50:16 +01:00
has : redirectRoute.has ,
2020-12-16 21:46:55 +01:00
statusCode : redirectRoute.statusCode ,
name : ` Redirect route ${ redirectRoute . source } ` ,
fn : async ( req , res , params , parsedUrl ) = > {
const { parsedDestination } = prepareDestination (
redirectRoute . destination ,
params ,
parsedUrl . query ,
false
)
2020-09-24 08:05:40 +02:00
2020-12-16 21:46:55 +01:00
const { query } = parsedDestination
delete ( parsedDestination as any ) . query
2020-09-24 08:05:40 +02:00
2020-12-16 21:46:55 +01:00
parsedDestination . search = stringifyQuery ( req , query )
2020-09-24 08:05:40 +02:00
2020-12-16 21:46:55 +01:00
const updatedDestination = formatUrl ( parsedDestination )
2020-06-09 22:16:23 +02:00
2020-12-16 21:46:55 +01:00
res . setHeader ( 'Location' , updatedDestination )
res . statusCode = getRedirectStatus ( redirectRoute as Redirect )
2020-06-09 22:16:23 +02:00
2020-12-16 21:46:55 +01:00
// Since IE11 doesn't support the 308 header add backwards
// compatibility using refresh header
if ( res . statusCode === 308 ) {
res . setHeader ( 'Refresh' , ` 0;url= ${ updatedDestination } ` )
}
2020-06-09 22:16:23 +02:00
2020-12-16 21:46:55 +01:00
res . end ( )
return {
finished : true ,
}
} ,
} as Route
} )
2020-06-09 22:16:23 +02:00
2021-03-26 16:19:48 +01:00
const buildRewrite = ( rewrite : Rewrite ) = > {
2020-06-09 22:16:23 +02:00
const rewriteRoute = getCustomRoute ( rewrite , 'rewrite' )
return {
2020-07-12 21:03:49 +02:00
. . . rewriteRoute ,
2020-06-09 22:16:23 +02:00
check : true ,
type : rewriteRoute . type ,
2020-11-12 06:26:48 +01:00
name : ` Rewrite route ${ rewriteRoute . source } ` ,
2020-06-09 22:16:23 +02:00
match : rewriteRoute.match ,
fn : async ( req , res , params , parsedUrl ) = > {
const { newUrl , parsedDestination } = prepareDestination (
rewriteRoute . destination ,
params ,
parsedUrl . query ,
2020-12-04 11:14:55 +01:00
true
2020-06-09 22:16:23 +02:00
)
2019-12-31 21:13:55 +01:00
2020-06-09 22:16:23 +02:00
// external rewrite, proxy it
if ( parsedDestination . protocol ) {
2020-09-24 08:05:40 +02:00
const { query } = parsedDestination
2020-10-15 23:55:38 +02:00
delete ( parsedDestination as any ) . query
2020-11-07 05:30:14 +01:00
parsedDestination . search = stringifyQuery ( req , query )
2020-09-24 08:05:40 +02:00
2020-06-09 22:16:23 +02:00
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 )
} )
2020-02-11 00:06:38 +01:00
return {
finished : true ,
}
2020-06-09 22:16:23 +02:00
}
; ( req as any ) . _nextRewroteUrl = newUrl
2020-07-12 21:03:49 +02:00
; ( req as any ) . _nextDidRewrite =
( req as any ) . _nextRewroteUrl !== req . url
2019-12-23 22:20:17 +01:00
2020-06-09 22:16:23 +02:00
return {
finished : false ,
pathname : newUrl ,
query : parsedDestination.query ,
}
} ,
} as Route
2021-03-26 16:19:48 +01:00
}
let beforeFiles : Route [ ] = [ ]
let afterFiles : Route [ ] = [ ]
let fallback : Route [ ] = [ ]
if ( Array . isArray ( this . customRoutes . rewrites ) ) {
afterFiles = this . customRoutes . rewrites . map ( buildRewrite )
} else {
beforeFiles = this . customRoutes . rewrites . beforeFiles . map ( buildRewrite )
afterFiles = this . customRoutes . rewrites . afterFiles . map ( buildRewrite )
fallback = this . customRoutes . rewrites . fallback . map ( buildRewrite )
}
2020-01-20 22:13:47 +01:00
const catchAllRoute : Route = {
match : route ( '/:path*' ) ,
type : 'route' ,
name : 'Catchall render' ,
2021-01-06 10:54:45 +01:00
fn : async ( req , res , _params , parsedUrl ) = > {
2020-06-23 13:38:49 +02:00
let { pathname , query } = parsedUrl
2020-01-20 22:13:47 +01:00
if ( ! pathname ) {
throw new Error ( 'pathname is undefined' )
}
2020-06-23 13:38:49 +02:00
// next.js core assumes page path without trailing slash
2020-06-30 06:06:39 +02:00
pathname = removePathTrailingSlash ( pathname )
2020-06-23 13:38:49 +02:00
2020-11-14 04:35:42 +01:00
if ( this . nextConfig . i18n ) {
const localePathResult = normalizeLocalePath (
pathname ,
this . nextConfig . i18n ? . locales
)
if ( localePathResult . detectedLocale ) {
pathname = localePathResult . pathname
parsedUrl . query . __nextLocale = localePathResult . detectedLocale
}
}
2021-01-06 10:54:45 +01:00
if ( pathname === '/api' || pathname . startsWith ( '/api/' ) ) {
2019-12-23 22:20:17 +01:00
const handled = await this . handleApiRequest (
req as NextApiRequest ,
res as NextApiResponse ,
2020-05-19 20:03:14 +02:00
pathname ,
2020-01-23 10:23:34 +01:00
query
2019-12-23 22:20:17 +01:00
)
if ( handled ) {
return { finished : true }
}
}
await this . render ( req , res , pathname , query , parsedUrl )
2019-11-20 16:53:31 +01:00
return {
finished : true ,
}
} ,
2019-12-23 22:20:17 +01:00
}
2019-11-18 01:12:48 +01:00
2020-02-11 00:06:38 +01:00
const { useFileSystemPublicRoutes } = this . nextConfig
2019-05-27 20:20:33 +02:00
2020-02-11 00:06:38 +01:00
if ( useFileSystemPublicRoutes ) {
this . dynamicRoutes = this . getDynamicRoutes ( )
2017-01-12 16:38:43 +01:00
}
2016-10-06 01:52:50 +02:00
2019-12-23 22:20:17 +01:00
return {
2020-02-11 00:06:38 +01:00
headers ,
2019-12-23 22:20:17 +01:00
fsRoutes ,
2021-03-26 16:19:48 +01:00
rewrites : {
beforeFiles ,
afterFiles ,
fallback ,
} ,
2020-02-11 00:06:38 +01:00
redirects ,
2019-12-23 22:20:17 +01:00
catchAllRoute ,
2020-02-11 00:06:38 +01:00
useFileSystemPublicRoutes ,
2019-12-23 22:20:17 +01:00
dynamicRoutes : this.dynamicRoutes ,
2020-07-12 21:03:49 +02:00
basePath : this.nextConfig.basePath ,
2019-12-23 22:20:17 +01:00
pageChecker : this.hasPage.bind ( this ) ,
2020-12-04 11:14:55 +01:00
locales : this.nextConfig.i18n?.locales || [ ] ,
2019-12-23 22:20:17 +01:00
}
2018-09-28 14:05:23 +02:00
}
2020-05-27 17:43:10 +02:00
private async getPagePath ( pathname : string ) : Promise < string > {
2019-12-10 16:08:42 +01:00
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
}
2019-11-18 01:12:48 +01:00
protected async _beforeCatchAllRender (
_req : IncomingMessage ,
_res : ServerResponse ,
_params : Params ,
_parsedUrl : UrlWithParsedQuery
2020-05-27 17:43:10 +02:00
) : Promise < boolean > {
2019-11-18 01:12:48 +01:00
return false
}
2019-12-10 16:08:42 +01:00
// Used to build API page in development
2020-06-03 17:58:58 +02:00
protected async ensureApiPage ( _pathname : string ) : Promise < void > { }
2019-12-10 16:08:42 +01:00
2019-05-11 13:18:56 +02:00
/ * *
* Resolves ` API ` request , in development builds on demand
* @param req http request
* @param res http response
* @param pathname path of request
* /
2019-05-26 23:22:43 +02:00
private async handleApiRequest (
2019-12-10 16:08:42 +01:00
req : IncomingMessage ,
res : ServerResponse ,
2020-01-23 10:23:34 +01:00
pathname : string ,
query : ParsedUrlQuery
2020-05-27 17:43:10 +02:00
) : Promise < boolean > {
2019-12-10 16:08:42 +01:00
let page = pathname
2019-06-27 18:01:36 +02:00
let params : Params | boolean = false
2019-12-10 16:08:42 +01:00
let pageFound = await this . hasPage ( page )
2019-07-09 00:50:01 +02:00
2019-12-10 16:08:42 +01:00
if ( ! pageFound && this . dynamicRoutes ) {
2019-06-27 18:01:36 +02:00
for ( const dynamicRoute of this . dynamicRoutes ) {
params = dynamicRoute . match ( pathname )
2020-02-01 15:14:50 +01:00
if ( dynamicRoute . page . startsWith ( '/api' ) && params ) {
2019-12-10 16:08:42 +01:00
page = dynamicRoute . page
pageFound = true
2019-06-27 18:01:36 +02:00
break
}
}
}
2019-12-10 16:08:42 +01:00
if ( ! pageFound ) {
2019-12-23 22:20:17 +01:00
return false
2019-07-09 00:50:01 +02:00
}
2019-12-10 16:08:42 +01:00
// Make sure the page is built before getting the path
// or else it won't be in the manifest yet
await this . ensureApiPage ( page )
2020-05-19 20:03:14 +02:00
let builtPagePath
try {
builtPagePath = await this . getPagePath ( page )
} catch ( err ) {
if ( err . code === 'ENOENT' ) {
return false
}
throw err
}
2020-10-14 11:55:42 +02:00
const pageModule = await require ( builtPagePath )
2020-01-23 10:23:34 +01:00
query = { . . . query , . . . params }
2019-07-09 00:50:01 +02:00
2021-01-06 10:54:45 +01:00
delete query . __nextLocale
delete query . __nextDefaultLocale
2019-08-06 00:26:20 +02:00
if ( ! this . renderOpts . dev && this . _isLikeServerless ) {
2019-12-10 16:08:42 +01:00
if ( typeof pageModule . default === 'function' ) {
2020-02-28 17:36:19 +01:00
prepareServerlessUrl ( req , query )
2019-12-23 22:20:17 +01:00
await pageModule . default ( req , res )
return true
2019-07-09 00:50:01 +02:00
}
}
2020-02-12 02:16:42 +01:00
await apiResolver (
req ,
res ,
query ,
pageModule ,
2020-03-02 11:58:47 +01:00
this . renderOpts . previewProps ,
2020-06-02 01:12:45 +02:00
false ,
2020-02-12 02:16:42 +01:00
this . onErrorMiddleware
)
2019-12-23 22:20:17 +01:00
return true
2019-05-11 13:18:56 +02:00
}
2019-10-04 18:11:39 +02:00
protected generatePublicRoutes ( ) : Route [ ] {
2020-01-10 03:56:05 +01:00
const publicFiles = new Set (
2020-09-24 08:05:40 +02:00
recursiveReadDirSync ( this . publicDir ) . map ( ( p ) = >
encodeURI ( p . replace ( /\\/g , '/' ) )
)
2020-01-10 03:56:05 +01:00
)
return [
{
match : route ( '/:path*' ) ,
name : 'public folder catchall' ,
fn : async ( req , res , params , parsedUrl ) = > {
2020-03-26 17:58:15 +01:00
const pathParts : string [ ] = params . path || [ ]
2020-08-19 19:30:33 +02:00
const { basePath } = this . nextConfig
// if basePath is defined require it be present
if ( basePath ) {
2020-11-03 03:44:50 +01:00
const basePathParts = basePath . split ( '/' )
// remove first empty value
basePathParts . shift ( )
if (
! basePathParts . every ( ( part : string , idx : number ) = > {
return part === pathParts [ idx ]
} )
) {
return { finished : false }
}
pathParts . splice ( 0 , basePathParts . length )
2020-08-19 19:30:33 +02:00
}
2020-03-26 17:58:15 +01:00
const path = ` / ${ pathParts . join ( '/' ) } `
2020-01-10 03:56:05 +01:00
if ( publicFiles . has ( path ) ) {
await this . serveStatic (
req ,
res ,
2020-09-24 08:05:40 +02:00
join ( this . publicDir , . . . pathParts ) ,
2020-01-10 03:56:05 +01:00
parsedUrl
)
2019-11-18 01:12:48 +01:00
return {
finished : true ,
}
2020-01-10 03:56:05 +01:00
}
return {
finished : false ,
}
} ,
} as Route ,
]
2019-05-03 18:57:47 +02:00
}
2020-11-08 03:18:13 +01:00
protected getDynamicRoutes ( ) : Array < DynamicRouteItem > {
const addedPages = new Set < string > ( )
2020-12-12 15:06:46 +01:00
return getSortedRoutes (
Object . keys ( this . pagesManifest ! ) . map (
( page ) = >
normalizeLocalePath ( page , this . nextConfig . i18n ? . locales ) . pathname
)
)
2020-11-08 03:18:13 +01:00
. map ( ( page ) = > {
2020-12-12 15:06:46 +01:00
if ( addedPages . has ( page ) || ! isDynamicRoute ( page ) ) return null
2020-11-08 03:18:13 +01:00
addedPages . add ( page )
return {
page ,
match : getRouteMatcher ( getRouteRegex ( page ) ) ,
}
} )
. filter ( ( item ) : item is DynamicRouteItem = > Boolean ( item ) )
2019-05-27 20:20:33 +02:00
}
2020-05-27 17:43:10 +02:00
private handleCompression ( req : IncomingMessage , res : ServerResponse ) : void {
2019-07-29 20:35:09 +02:00
if ( this . compression ) {
this . compression ( req , res , ( ) = > { } )
}
}
2019-10-04 18:11:39 +02:00
protected async run (
2019-03-01 20:51:13 +01:00
req : IncomingMessage ,
res : ServerResponse ,
2019-05-29 13:57:26 +02:00
parsedUrl : UrlWithParsedQuery
2020-05-27 17:43:10 +02:00
) : Promise < void > {
2019-07-29 20:35:09 +02:00
this . handleCompression ( req , res )
2018-11-04 01:22:33 +01:00
try {
2019-11-18 01:12:48 +01:00
const matched = await this . router . execute ( req , res , parsedUrl )
if ( matched ) {
2018-11-04 01:22:33 +01:00
return
}
} catch ( err ) {
if ( err . code === 'DECODE_FAILED' ) {
res . statusCode = 400
return this . renderError ( null , req , res , '/_error' , { } )
}
throw err
2017-01-12 16:38:43 +01:00
}
2019-07-04 10:22:25 +02:00
await this . render404 ( req , res , parsedUrl )
2016-10-06 01:52:50 +02:00
}
2019-10-04 18:11:39 +02:00
protected async sendHTML (
2019-03-01 20:51:13 +01:00
req : IncomingMessage ,
res : ServerResponse ,
2019-05-29 13:57:26 +02:00
html : string
2020-05-27 17:43:10 +02:00
) : Promise < void > {
2019-04-15 09:48:14 +02:00
const { generateEtags , poweredByHeader } = this . renderOpts
2020-07-27 22:19:30 +02:00
return sendPayload ( req , res , html , 'html' , {
generateEtags ,
poweredByHeader ,
} )
2018-12-09 22:46:45 +01:00
}
2019-03-01 20:51:13 +01:00
public async render (
req : IncomingMessage ,
res : ServerResponse ,
pathname : string ,
query : ParsedUrlQuery = { } ,
2019-05-29 13:57:26 +02:00
parsedUrl? : UrlWithParsedQuery
2019-03-01 20:51:13 +01:00
) : Promise < void > {
2020-04-06 18:54:42 +02:00
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 `
)
}
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 = '/'
}
2018-12-09 22:46:45 +01:00
const url : any = req . url
2019-11-13 05:07:19 +01:00
2020-04-06 20:55:22 +02:00
// 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
2019-11-13 05:07:19 +01:00
if (
2020-04-06 20:55:22 +02:00
! query . _nextDataReq &&
( url . match ( /^\/_next\// ) ||
( this . hasStaticDir && url . match ( /^\/static\// ) ) )
2019-11-13 05:07:19 +01:00
) {
2017-04-07 19:58:35 +02:00
return this . handleRequest ( req , res , parsedUrl )
}
2018-11-18 20:44:50 +01:00
if ( isBlockedPage ( pathname ) ) {
2018-12-09 22:46:45 +01:00
return this . render404 ( req , res , parsedUrl )
2017-07-06 14:29:25 +02:00
}
2020-03-01 18:26:31 +01:00
const html = await this . renderToHTML ( req , res , pathname , query )
2018-12-09 22:46:45 +01:00
// Request was ended by the user
if ( html === null ) {
2018-02-01 23:25:30 +01:00
return
}
2018-12-09 22:46:45 +01:00
return this . sendHTML ( req , res , html )
2016-12-16 21:33:08 +01:00
}
2016-10-19 14:41:45 +02:00
2019-05-26 23:22:43 +02:00
private async findPageComponents (
2019-03-01 20:51:13 +01:00
pathname : string ,
2020-02-13 05:28:31 +01:00
query : ParsedUrlQuery = { } ,
params : Params | null = null
) : Promise < FindComponentsResult | null > {
2020-10-09 11:13:05 +02:00
let paths = [
2020-02-13 05:28:31 +01:00
// try serving a static AMP version first
query . amp ? normalizePagePath ( pathname ) + '.amp' : null ,
pathname ,
] . filter ( Boolean )
2020-10-09 11:13:05 +02:00
if ( query . __nextLocale ) {
paths = [
. . . paths . map (
( path ) = > ` / ${ query . __nextLocale } ${ path === '/' ? '' : path } `
) ,
. . . paths ,
]
}
2020-02-13 05:28:31 +01:00
for ( const pagePath of paths ) {
2019-05-22 18:36:53 +02:00
try {
2020-02-13 05:28:31 +01:00
const components = await loadComponents (
2019-05-26 23:22:43 +02:00
this . distDir ,
2020-02-13 05:28:31 +01:00
pagePath ! ,
! this . renderOpts . dev && this . _isLikeServerless
2019-05-26 23:22:43 +02:00
)
2020-10-10 12:22:45 +02:00
// 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
}
2020-02-13 05:28:31 +01:00
return {
components ,
query : {
2020-02-27 18:57:39 +01:00
. . . ( components . getStaticProps
2020-10-09 11:13:05 +02:00
? {
amp : query.amp ,
_nextDataReq : query._nextDataReq ,
__nextLocale : query.__nextLocale ,
2020-11-11 03:09:45 +01:00
__nextDefaultLocale : query.__nextDefaultLocale ,
2020-10-09 11:13:05 +02:00
}
2020-02-13 05:28:31 +01:00
: query ) ,
. . . ( params || { } ) ,
} ,
}
2019-05-22 18:36:53 +02:00
} catch ( err ) {
if ( err . code !== 'ENOENT' ) throw err
}
}
2020-02-13 05:28:31 +01:00
return null
2019-05-26 23:22:43 +02:00
}
2020-07-01 16:59:18 +02:00
protected async getStaticPaths (
2020-02-28 17:56:50 +01:00
pathname : string
) : Promise < {
staticPaths : string [ ] | undefined
2020-08-04 17:10:31 +02:00
fallbackMode : 'static' | 'blocking' | false
2020-02-28 17:56:50 +01:00
} > {
2020-07-01 16:59:18 +02: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.
2020-08-04 17:10:31 +02:00
const fallbackField = this . getPrerenderManifest ( ) . dynamicRoutes [ pathname ]
. fallback
2020-02-28 17:56:50 +01:00
2020-08-04 17:10:31 +02:00
return {
staticPaths ,
fallbackMode :
typeof fallbackField === 'string'
? 'static'
: fallbackField === null
? 'blocking'
: false ,
}
2020-02-28 17:56:50 +01:00
}
2019-05-26 23:22:43 +02:00
private async renderToHTMLWithComponents (
req : IncomingMessage ,
res : ServerResponse ,
pathname : string ,
2020-02-13 05:28:31 +01:00
{ components , query } : FindComponentsResult ,
2020-03-02 11:58:47 +01:00
opts : RenderOptsPartial
2020-03-03 19:25:45 +01:00
) : Promise < string | null > {
2020-10-24 21:22:48 +02:00
const is404Page = pathname === '/404'
2020-10-27 10:40:17 +01:00
const isLikeServerless =
typeof components . Component === 'object' &&
typeof ( components . Component as any ) . renderReqToHTML === 'function'
const isSSG = ! ! components . getStaticProps
2021-01-25 19:26:32 +01:00
const hasServerProps = ! ! components . getServerSideProps
2020-10-27 10:40:17 +01:00
const hasStaticPaths = ! ! components . getStaticPaths
2021-01-25 19:26:32 +01:00
const hasGetInitialProps = ! ! ( components . Component as any ) . getInitialProps
2020-10-27 10:40:17 +01:00
// Toggle whether or not this is a Data request
2021-01-25 19:26:32 +01:00
const isDataReq = ! ! query . _nextDataReq && ( isSSG || hasServerProps )
2020-10-27 10:40:17 +01:00
delete query . _nextDataReq
2020-02-01 15:47:42 +01:00
// we need to ensure the status code if /404 is visited directly
2020-10-27 10:40:17 +01:00
if ( is404Page && ! isDataReq ) {
2020-02-01 15:47:42 +01:00
res . statusCode = 404
}
2021-02-22 17:29:50 +01:00
// ensure correct status is set when visiting a status page
// directly e.g. /500
if ( STATIC_STATUS_PAGES . includes ( pathname ) ) {
res . statusCode = parseInt ( pathname . substr ( 1 ) , 10 )
}
2019-05-22 18:36:53 +02:00
// handle static page
2020-02-13 05:28:31 +01:00
if ( typeof components . Component === 'string' ) {
return components . Component
2019-05-26 23:22:43 +02:00
}
2020-03-20 09:46:52 +01:00
if ( ! query . amp ) {
delete query . amp
}
2020-10-12 12:05:47 +02:00
const locale = query . __nextLocale as string
2020-11-11 03:09:45 +01:00
const defaultLocale = isSSG
? this . nextConfig . i18n ? . defaultLocale
: ( query . __nextDefaultLocale as string )
2020-10-27 16:30:34 +01:00
const { i18n } = this . nextConfig
2020-12-04 11:14:55 +01:00
const locales = i18n ? . locales
2020-10-12 12:05:47 +02:00
2020-03-04 13:58:12 +01:00
let previewData : string | false | object | undefined
let isPreviewMode = false
2021-01-25 19:26:32 +01:00
if ( hasServerProps || isSSG ) {
2020-03-04 13:58:12 +01:00
previewData = tryGetPreviewData ( req , res , this . renderOpts . previewProps )
isPreviewMode = previewData !== false
}
2020-05-29 18:33:09 +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
2020-09-15 21:19:07 +02:00
let urlPathname = parseUrl ( req . url || '' ) . pathname || '/'
let resolvedUrlPathname = ( req as any ) . _nextRewroteUrl
2020-05-29 18:33:09 +02:00
? ( req as any ) . _nextRewroteUrl
2020-09-15 21:19:07 +02:00
: urlPathname
2020-04-06 20:55:22 +02:00
2020-09-15 21:19:07 +02:00
urlPathname = removePathTrailingSlash ( urlPathname )
2020-12-28 19:21:28 +01:00
resolvedUrlPathname = normalizeLocalePath (
removePathTrailingSlash ( resolvedUrlPathname ) ,
this . nextConfig . i18n ? . locales
) . pathname
2020-09-15 21:19:07 +02:00
const stripNextDataPath = ( path : string ) = > {
if ( path . includes ( this . buildId ) ) {
2021-01-19 19:27:28 +01:00
const splitPath = path . substring (
path . indexOf ( this . buildId ) + this . buildId . length
2020-09-15 21:19:07 +02:00
)
2021-01-19 19:27:28 +01:00
path = denormalizePagePath ( splitPath . replace ( /\.json$/ , '' ) )
2020-09-15 21:19:07 +02:00
}
2020-10-07 23:11:01 +02:00
2020-10-27 16:30:34 +01:00
if ( this . nextConfig . i18n ) {
2020-10-12 12:05:47 +02:00
return normalizeLocalePath ( path , locales ) . pathname
2020-10-07 23:11:01 +02:00
}
2020-09-15 21:19:07 +02:00
return path
}
2020-06-01 19:08:34 +02:00
2020-11-04 23:18:44 +01:00
const handleRedirect = ( pageData : any ) = > {
const redirect = {
destination : pageData.pageProps.__N_REDIRECT ,
statusCode : pageData.pageProps.__N_REDIRECT_STATUS ,
2020-11-11 08:13:18 +01:00
basePath : pageData.pageProps.__N_REDIRECT_BASE_PATH ,
2020-11-04 23:18:44 +01:00
}
const statusCode = getRedirectStatus ( redirect )
2020-11-11 08:13:18 +01:00
const { basePath } = this . nextConfig
if ( basePath && redirect . basePath !== false ) {
redirect . destination = ` ${ basePath } ${ redirect . destination } `
}
2020-11-04 23:18:44 +01:00
if ( statusCode === PERMANENT_REDIRECT_STATUS ) {
res . setHeader ( 'Refresh' , ` 0;url= ${ redirect . destination } ` )
}
res . statusCode = statusCode
res . setHeader ( 'Location' , redirect . destination )
res . end ( )
}
2020-04-06 20:55:22 +02:00
// remove /_next/data prefix from urlPathname so it matches
// for direct page visit and /_next/data visit
2020-09-15 21:19:07 +02:00
if ( isDataReq ) {
resolvedUrlPathname = stripNextDataPath ( resolvedUrlPathname )
urlPathname = stripNextDataPath ( urlPathname )
2020-04-06 20:55:22 +02:00
}
2020-10-24 21:22:48 +02:00
let ssgCacheKey =
2020-12-16 21:46:55 +01:00
isPreviewMode || ! isSSG || this . minimalMode
2020-05-23 22:30:32 +02:00
? undefined // Preview mode bypasses the cache
2020-11-11 03:09:45 +01:00
: ` ${ locale ? ` / ${ locale } ` : '' } ${
2020-11-12 19:50:32 +01:00
( pathname === '/' || resolvedUrlPathname === '/' ) && locale
? ''
: resolvedUrlPathname
2020-11-11 03:09:45 +01:00
} $ { query . amp ? '.amp' : '' } `
2019-09-24 10:50:04 +02:00
2020-10-24 21:22:48 +02:00
if ( is404Page && isSSG ) {
ssgCacheKey = ` ${ locale ? ` / ${ locale } ` : '' } ${ pathname } ${
query . amp ? '.amp' : ''
} `
}
2020-12-28 21:08:58 +01:00
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, this is considered
// a bad request (400)
const err : Error & { code? : string } = new Error (
'failed to decode param'
)
err . code = 'DECODE_FAILED'
throw err
}
return seg
} )
. join ( '/' )
}
2019-09-24 10:50:04 +02:00
// Complete the response with cached data if its present
2020-06-28 22:58:43 +02:00
const cachedData = ssgCacheKey
? await this . incrementalCache . get ( ssgCacheKey )
: undefined
2020-05-19 13:58:50 +02:00
2019-09-24 10:50:04 +02:00
if ( cachedData ) {
2020-01-15 02:22:15 +01:00
const data = isDataReq
2019-09-24 10:50:04 +02:00
? JSON . stringify ( cachedData . pageData )
: cachedData . html
2020-11-14 08:12:47 +01:00
const revalidateOptions = ! 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
2020-11-04 23:18:44 +01:00
if ( ! isDataReq && cachedData . pageData ? . pageProps ? . __N_REDIRECT ) {
await handleRedirect ( cachedData . pageData )
2020-11-14 08:12:47 +01:00
} else if ( cachedData . isNotFound ) {
if ( revalidateOptions ) {
setRevalidateHeaders ( res , revalidateOptions )
}
2020-12-30 23:35:02 +01:00
if ( isDataReq ) {
res . statusCode = 404
res . end ( '{"notFound":true}' )
} else {
await this . render404 ( req , res , {
pathname ,
query ,
} as UrlWithParsedQuery )
}
2020-11-04 23:18:44 +01:00
} else {
sendPayload (
req ,
res ,
data ,
isDataReq ? 'json' : 'html' ,
{
generateEtags : this.renderOpts.generateEtags ,
poweredByHeader : this.renderOpts.poweredByHeader ,
} ,
2020-11-14 08:12:47 +01:00
revalidateOptions
2020-11-04 23:18:44 +01:00
)
}
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
}
2019-05-22 18:36:53 +02:00
}
2019-05-26 23:22:43 +02:00
2019-09-24 10:50:04 +02:00
// If we're here, that means data is missing or it's stale.
2020-05-04 22:57:45 +02:00
const maybeCoalesceInvoke = ssgCacheKey
2020-10-24 21:22:48 +02:00
? ( fn : any ) = > withCoalescedInvoke ( fn ) . bind ( null , ssgCacheKey ! , [ ] )
2020-05-04 22:57:45 +02:00
: ( fn : any ) = > async ( ) = > {
const value = await fn ( )
return { isOrigin : true , value }
}
2019-09-24 10:50:04 +02:00
2020-07-01 16:59:18 +02:00
const doRender = maybeCoalesceInvoke (
async ( ) : Promise < {
html : string | null
pageData : any
sprRevalidate : number | false
2020-10-15 23:55:38 +02:00
isNotFound? : boolean
2020-11-04 23:18:44 +01:00
isRedirect? : boolean
2020-07-01 16:59:18 +02:00
} > = > {
let pageData : any
let html : string | null
let sprRevalidate : number | false
2020-10-15 23:55:38 +02:00
let isNotFound : boolean | undefined
2020-11-04 23:18:44 +01:00
let isRedirect : boolean | undefined
2020-07-01 16:59:18 +02:00
let renderResult
// handle serverless
if ( isLikeServerless ) {
renderResult = await ( components . Component as any ) . renderReqToHTML (
req ,
res ,
2020-07-28 12:19:28 +02:00
'passthrough' ,
{
2020-10-09 11:13:05 +02:00
locale ,
2020-10-10 12:22:45 +02:00
locales ,
2020-11-11 03:09:45 +01:00
defaultLocale ,
2021-01-29 14:53:07 +01:00
optimizeCss : this.renderOpts.optimizeCss ,
distDir : this.distDir ,
2020-12-30 07:44:07 +01:00
fontManifest : this.renderOpts.fontManifest ,
domainLocales : this.renderOpts.domainLocales ,
2020-07-28 12:19:28 +02:00
}
2020-07-01 16:59:18 +02:00
)
2019-09-24 10:50:04 +02:00
2020-07-01 16:59:18 +02:00
html = renderResult . html
pageData = renderResult . renderOpts . pageData
sprRevalidate = renderResult . renderOpts . revalidate
2020-10-27 06:42:12 +01:00
isNotFound = renderResult . renderOpts . isNotFound
2020-11-04 23:18:44 +01:00
isRedirect = renderResult . renderOpts . isRedirect
2020-07-01 16:59:18 +02:00
} else {
2020-09-15 21:19:07 +02:00
const origQuery = parseUrl ( req . url || '' , true ) . query
2020-11-20 19:45:47 +01:00
const hadTrailingSlash =
urlPathname !== '/' && this . nextConfig . trailingSlash
2020-09-15 21:19:07 +02:00
const resolvedUrl = formatUrl ( {
2020-11-20 19:45:47 +01:00
pathname : ` ${ resolvedUrlPathname } ${ hadTrailingSlash ? '/' : '' } ` ,
2020-09-15 21:19:07 +02:00
// make sure to only add query values from original URL
query : origQuery ,
} )
2020-07-01 16:59:18 +02:00
const renderOpts : RenderOpts = {
. . . components ,
. . . opts ,
isDataReq ,
2020-09-15 21:19:07 +02:00
resolvedUrl ,
2020-10-09 11:13:05 +02:00
locale ,
2020-10-10 12:22:45 +02:00
locales ,
2020-11-11 03:09:45 +01:00
defaultLocale ,
2021-01-25 19:26:32 +01:00
// For getServerSideProps and getInitialProps we need to ensure we use the original URL
2020-09-15 21:19:07 +02:00
// and not the resolved URL to prevent a hydration mismatch on
// asPath
2021-01-25 19:26:32 +01:00
resolvedAsPath :
hasServerProps || hasGetInitialProps
? formatUrl ( {
// we use the original URL pathname less the _next/data prefix if
// present
pathname : ` ${ urlPathname } ${ hadTrailingSlash ? '/' : '' } ` ,
query : origQuery ,
} )
: resolvedUrl ,
2020-07-01 16:59:18 +02:00
}
2020-09-14 18:48:04 +02:00
2020-07-01 16:59:18 +02:00
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
2020-10-27 06:42:12 +01:00
isNotFound = ( renderOpts as any ) . isNotFound
2020-11-04 23:18:44 +01:00
isRedirect = ( renderOpts as any ) . isRedirect
2019-09-24 10:50:04 +02:00
}
2020-11-04 23:18:44 +01:00
return { html , pageData , sprRevalidate , isNotFound , isRedirect }
2019-09-24 10:50:04 +02:00
}
2020-07-01 16:59:18 +02:00
)
2019-09-24 10:50:04 +02:00
2020-02-13 02:06:07 +01:00
const isProduction = ! this . renderOpts . dev
2020-02-12 02:16:42 +01:00
const isDynamicPathname = isDynamicRoute ( pathname )
2020-02-13 02:06:07 +01:00
const didRespond = isResSent ( res )
2020-02-24 22:36:59 +01:00
2020-08-04 17:10:31 +02:00
const { staticPaths , fallbackMode } = hasStaticPaths
2020-02-28 17:56:50 +01:00
? await this . getStaticPaths ( pathname )
2020-08-04 17:10:31 +02:00
: { staticPaths : undefined , fallbackMode : false }
2020-02-24 22:36:59 +01:00
2020-02-13 02:06:07 +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.
//
2020-08-04 17:10:31 +02:00
// * Blocking mode fallback always blocks.
//
2020-02-13 02:06:07 +01:00
// * Preview mode toggles all pages to be resolved in a blocking manner.
//
2020-02-24 22:36:59 +01:00
// * Non-dynamic pages should block (though this is an impossible
2020-02-13 02:06:07 +01:00
// case in production).
//
2020-02-24 22:36:59 +01:00
// * Dynamic pages should return their skeleton if not defined in
// getStaticPaths, then finish the data request on the client-side.
2020-02-13 02:06:07 +01:00
//
2020-02-12 02:16:42 +01:00
if (
2020-12-16 21:46:55 +01:00
this . minimalMode !== true &&
2020-08-04 17:10:31 +02:00
fallbackMode !== 'blocking' &&
2020-05-23 22:30:32 +02:00
ssgCacheKey &&
2020-02-13 02:06:07 +01:00
! didRespond &&
! isPreviewMode &&
isDynamicPathname &&
2020-02-24 22:36:59 +01:00
// Development should trigger fallback when the path is not in
// `getStaticPaths`
2020-09-15 21:19:07 +02:00
( isProduction ||
! staticPaths ||
2020-10-07 23:11:01 +02:00
! staticPaths . includes (
2020-12-28 21:08:58 +01:00
// 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
2020-10-07 23:11:01 +02:00
) )
2020-02-12 02:16:42 +01:00
) {
2020-02-27 13:23:28 +01:00
if (
// In development, fall through to render to handle missing
// getStaticPaths.
( isProduction || staticPaths ) &&
// When fallback isn't present, abort this render so we 404
2020-08-04 17:10:31 +02:00
fallbackMode !== 'static'
2020-02-27 13:23:28 +01:00
) {
2020-03-03 19:25:45 +01:00
throw new NoFallbackError ( )
2020-02-27 13:23:28 +01:00
}
2020-08-20 19:59:03 +02:00
if ( ! isDataReq ) {
let html : string
2020-02-07 14:09:06 +01:00
2020-08-20 19:59:03 +02:00
// Production already emitted the fallback as static HTML.
if ( isProduction ) {
2020-10-09 11:13:05 +02:00
html = await this . incrementalCache . getFallback (
locale ? ` / ${ locale } ${ pathname } ` : pathname
)
2020-08-20 19:59:03 +02:00
}
// 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
2020-02-07 14:09:06 +01:00
}
2020-08-20 19:59:03 +02:00
sendPayload ( req , res , html , 'html' , {
generateEtags : this.renderOpts.generateEtags ,
poweredByHeader : this.renderOpts.poweredByHeader ,
} )
return null
}
2020-02-07 14:09:06 +01:00
}
2020-02-13 05:28:31 +01:00
const {
isOrigin ,
2020-11-04 23:18:44 +01:00
value : { html , pageData , sprRevalidate , isNotFound , isRedirect } ,
2020-05-04 22:57:45 +02:00
} = await doRender ( )
2020-05-23 22:30:32 +02:00
let resHtml = html
2020-10-15 23:55:38 +02:00
2020-11-14 08:12:47 +01:00
const revalidateOptions =
2021-01-25 19:26:32 +01:00
! this . renderOpts . dev || ( hasServerProps && ! isDataReq )
2020-11-14 08:12:47 +01:00
? {
private : isPreviewMode ,
stateful : ! isSSG ,
revalidate : sprRevalidate ,
}
: undefined
2020-10-15 23:55:38 +02:00
if (
! isResSent ( res ) &&
! isNotFound &&
2021-01-25 19:26:32 +01:00
( isSSG || isDataReq || hasServerProps )
2020-10-15 23:55:38 +02:00
) {
2020-11-04 23:18:44 +01:00
if ( isRedirect && ! isDataReq ) {
await handleRedirect ( pageData )
} else {
sendPayload (
req ,
res ,
isDataReq ? JSON . stringify ( pageData ) : html ,
isDataReq ? 'json' : 'html' ,
{
generateEtags : this.renderOpts.generateEtags ,
poweredByHeader : this.renderOpts.poweredByHeader ,
} ,
2020-11-14 08:12:47 +01:00
revalidateOptions
2020-11-04 23:18:44 +01:00
)
}
2020-05-23 22:30:32 +02:00
resHtml = null
2020-02-13 05:28:31 +01:00
}
2019-09-24 10:50:04 +02:00
2020-06-28 22:58:43 +02:00
// Update the cache if the head request and cacheable
2020-05-04 22:57:45 +02:00
if ( isOrigin && ssgCacheKey ) {
2020-06-28 22:58:43 +02:00
await this . incrementalCache . set (
ssgCacheKey ,
2020-11-04 23:18:44 +01:00
{ html : html ! , pageData , isNotFound , isRedirect } ,
2020-06-28 22:58:43 +02:00
sprRevalidate
)
2020-02-13 05:28:31 +01:00
}
2020-11-14 08:12:47 +01:00
if ( ! isResSent ( res ) && isNotFound ) {
if ( revalidateOptions ) {
setRevalidateHeaders ( res , revalidateOptions )
}
2020-12-30 23:35:02 +01:00
if ( isDataReq ) {
res . statusCode = 404
res . end ( '{"notFound":true}' )
} else {
await this . render404 ( req , res , {
pathname ,
query ,
} as UrlWithParsedQuery )
}
2020-10-15 23:55:38 +02:00
}
2020-05-23 22:30:32 +02:00
return resHtml
2018-12-18 17:12:49 +01:00
}
2020-02-13 05:28:31 +01:00
public async renderToHTML (
2019-03-01 20:51:13 +01:00
req : IncomingMessage ,
res : ServerResponse ,
pathname : string ,
2020-03-01 18:26:31 +01:00
query : ParsedUrlQuery = { }
2019-03-01 20:51:13 +01:00
) : Promise < string | null > {
2020-02-13 05:28:31 +01:00
try {
const result = await this . findPageComponents ( pathname , query )
if ( result ) {
2020-03-03 19:25:45 +01:00
try {
return await this . renderToHTMLWithComponents (
req ,
res ,
pathname ,
result ,
{ . . . this . renderOpts }
)
} catch ( err ) {
if ( ! ( err instanceof NoFallbackError ) ) {
throw err
}
2020-02-27 13:23:28 +01:00
}
2020-02-13 05:28:31 +01:00
}
2019-05-27 20:20:33 +02:00
2020-02-13 05:28:31 +01:00
if ( this . dynamicRoutes ) {
for ( const dynamicRoute of this . dynamicRoutes ) {
const params = dynamicRoute . match ( pathname )
if ( ! params ) {
continue
}
2019-05-27 20:20:33 +02:00
2020-06-01 23:00:22 +02:00
const dynamicRouteResult = await this . findPageComponents (
2020-02-13 05:28:31 +01:00
dynamicRoute . page ,
query ,
params
)
2020-06-01 23:00:22 +02:00
if ( dynamicRouteResult ) {
2020-03-03 19:25:45 +01:00
try {
return await this . renderToHTMLWithComponents (
req ,
res ,
dynamicRoute . page ,
2020-06-01 23:00:22 +02:00
dynamicRouteResult ,
2020-03-03 19:25:45 +01:00
{ . . . this . renderOpts , params }
)
} catch ( err ) {
if ( ! ( err instanceof NoFallbackError ) ) {
throw err
}
2020-02-27 13:23:28 +01:00
}
2019-05-27 20:20:33 +02:00
}
}
2020-02-13 05:28:31 +01:00
}
} catch ( err ) {
2020-09-24 08:05:40 +02:00
if ( err && err . code === 'DECODE_FAILED' ) {
2021-02-11 10:31:49 +01:00
this . logError ( err )
2020-09-24 08:05:40 +02:00
res . statusCode = 400
return await this . renderErrorToHTML ( err , req , res , pathname , query )
}
2020-02-13 05:28:31 +01:00
res . statusCode = 500
2021-02-11 10:31:49 +01:00
const html = await this . renderErrorToHTML ( err , req , res , pathname , query )
if ( this . minimalMode ) {
throw err
}
this . logError ( err )
return html
2020-02-13 05:28:31 +01:00
}
res . statusCode = 404
return await this . renderErrorToHTML ( null , req , res , pathname , query )
2016-11-24 15:03:16 +01:00
}
2019-03-01 20:51:13 +01:00
public async renderError (
err : Error | null ,
req : IncomingMessage ,
res : ServerResponse ,
pathname : string ,
2020-11-14 08:12:47 +01:00
query : ParsedUrlQuery = { } ,
setHeaders = true
2019-03-01 20:51:13 +01:00
) : Promise < void > {
2020-11-14 08:12:47 +01:00
if ( setHeaders ) {
res . setHeader (
'Cache-Control' ,
'no-cache, no-store, max-age=0, must-revalidate'
)
}
2016-12-16 21:33:08 +01:00
const html = await this . renderErrorToHTML ( err , req , res , pathname , query )
2021-02-11 10:31:49 +01:00
if ( this . minimalMode && res . statusCode === 500 ) {
throw err
}
2018-12-31 14:44:27 +01:00
if ( html === null ) {
2018-12-13 01:00:46 +01:00
return
}
2018-12-09 22:46:45 +01:00
return this . sendHTML ( req , res , html )
2016-10-06 01:52:50 +02:00
}
2020-04-05 13:19:14 +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 ,
2019-05-29 13:57:26 +02:00
query : ParsedUrlQuery = { }
2019-03-01 20:51:13 +01:00
) {
2020-02-13 05:28:31 +01:00
let result : null | FindComponentsResult = null
2020-01-20 15:10:24 +01:00
2020-02-01 15:47:42 +01:00
const is404 = res . statusCode === 404
let using404Page = false
2020-01-20 15:10:24 +01:00
// use static 404 page if available and is 404 response
2020-02-01 15:47:42 +01:00
if ( is404 ) {
2020-10-09 11:13:05 +02:00
result = await this . findPageComponents ( '/404' , query )
2020-02-19 20:54:38 +01:00
using404Page = result !== null
2020-01-20 15:10:24 +01:00
}
2021-02-22 17:29:50 +01:00
let statusPage = ` / ${ res . statusCode } `
if ( ! result && STATIC_STATUS_PAGES . includes ( statusPage ) ) {
result = await this . findPageComponents ( statusPage , query )
}
2020-01-20 15:10:24 +01:00
if ( ! result ) {
result = await this . findPageComponents ( '/_error' , query )
2021-02-22 17:29:50 +01:00
statusPage = '/_error'
2020-01-20 15:10:24 +01:00
}
2020-04-05 13:19:14 +02:00
if (
process . env . NODE_ENV !== 'production' &&
! using404Page &&
2020-04-21 14:22:39 +02:00
( await this . hasPage ( '/_error' ) ) &&
! ( await this . hasPage ( '/404' ) )
2020-04-05 13:19:14 +02:00
) {
this . customErrorNo404Warn ( )
}
2020-02-27 13:23:28 +01:00
let html : string | null
2019-06-19 18:26:22 +02:00
try {
2020-03-03 19:25:45 +01:00
try {
html = await this . renderToHTMLWithComponents (
req ,
res ,
2021-02-22 17:29:50 +01:00
statusPage ,
2020-03-03 19:25:45 +01:00
result ! ,
{
. . . this . renderOpts ,
err ,
}
)
2020-06-01 23:00:22 +02:00
} catch ( maybeFallbackError ) {
if ( maybeFallbackError instanceof NoFallbackError ) {
2020-03-03 19:25:45 +01:00
throw new Error ( 'invariant: failed to render error page' )
2019-06-19 18:26:22 +02:00
}
2020-06-01 23:00:22 +02:00
throw maybeFallbackError
2020-02-27 13:23:28 +01:00
}
2020-06-01 23:00:22 +02:00
} catch ( renderToHtmlError ) {
console . error ( renderToHtmlError )
2019-06-19 18:26:22 +02:00
res . statusCode = 500
html = 'Internal Server Error'
}
return html
2016-11-24 15:03:16 +01:00
}
2019-03-01 20:51:13 +01:00
public async render404 (
req : IncomingMessage ,
res : ServerResponse ,
2020-11-14 08:12:47 +01:00
parsedUrl? : UrlWithParsedQuery ,
setHeaders = true
2019-03-01 20:51:13 +01:00
) : Promise < void > {
2018-12-09 22:46:45 +01:00
const url : any = req . url
const { pathname , query } = parsedUrl ? parsedUrl : parseUrl ( url , true )
2020-11-20 16:07:00 +01:00
const { i18n } = this . nextConfig
if ( i18n ) {
query . __nextLocale = query . __nextLocale || i18n . defaultLocale
query . __nextDefaultLocale =
query . __nextDefaultLocale || i18n . defaultLocale
}
2016-12-16 21:33:08 +01:00
res . statusCode = 404
2020-11-14 08:12:47 +01:00
return this . renderError ( null , req , res , pathname ! , query , setHeaders )
2016-12-16 21:33:08 +01:00
}
2016-11-03 16:12:37 +01:00
2019-03-01 20:51:13 +01:00
public async serveStatic (
req : IncomingMessage ,
res : ServerResponse ,
path : string ,
2019-05-29 13:57:26 +02:00
parsedUrl? : UrlWithParsedQuery
2019-03-01 20:51:13 +01:00
) : Promise < void > {
2017-06-01 02:16:32 +02:00
if ( ! this . isServeableUrl ( path ) ) {
2018-12-09 22:46:45 +01:00
return this . render404 ( req , res , parsedUrl )
2017-06-01 02:16:32 +02:00
}
2019-07-04 10:22:25 +02:00
if ( ! ( req . method === 'GET' || req . method === 'HEAD' ) ) {
res . statusCode = 405
res . setHeader ( 'Allow' , [ 'GET' , 'HEAD' ] )
return this . renderError ( null , req , res , path )
}
2017-01-01 20:36:37 +01:00
try {
2018-12-09 22:46:45 +01:00
await serveStatic ( req , res , path )
2017-01-01 20:36:37 +01:00
} catch ( err ) {
2018-11-30 17:09:23 +01:00
if ( err . code === 'ENOENT' || err . statusCode === 404 ) {
2018-12-09 22:46:45 +01:00
this . render404 ( req , res , parsedUrl )
2019-07-16 13:30:29 +02:00
} else if ( err . statusCode === 412 ) {
res . statusCode = 412
return this . renderError ( err , req , res , path )
2017-01-01 20:36:37 +01:00
} else {
throw err
}
}
}
2020-03-26 17:58:15 +01:00
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 ) = >
2020-03-26 17:58:15 +01:00
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 ) = >
2020-03-26 17:58:15 +01:00
join ( '.' , 'public' , f )
)
}
let nextFilesStatic : string [ ] = [ ]
2021-02-11 10:31:49 +01:00
nextFilesStatic = ! this . minimalMode
? recursiveReadDirSync ( join ( this . distDir , 'static' ) ) . map ( ( f ) = >
join ( '.' , relative ( this . dir , this . distDir ) , 'static' , f )
)
: [ ]
2020-03-26 17:58:15 +01:00
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 (
2020-03-26 17:58:15 +01:00
( 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
}
2020-03-26 17:58:15 +01:00
// 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
}
2019-10-04 18:11:39 +02:00
protected readBuildId ( ) : string {
2018-12-06 16:46:53 +01:00
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 (
2020-12-08 16:16:56 +01:00
` Could not find a production build in the ' ${ this . distDir } ' directory. Try building your app with 'next build' before starting the production server. https://err.sh/vercel/next.js/production-start-no-build-id `
2019-03-01 20:51:13 +01:00
)
2018-12-06 16:46:53 +01:00
}
throw err
2018-07-25 13:45:42 +02:00
}
2017-01-11 21:16:18 +01:00
}
2019-08-06 00:26:20 +02:00
2020-07-01 16:59:18 +02:00
protected get _isLikeServerless ( ) : boolean {
2019-08-06 00:26:20 +02:00
return isTargetLikeServerless ( this . nextConfig . target )
}
2017-02-21 00:48:17 +01:00
}
2020-02-28 17:36:19 +01:00
2020-05-27 17:43:10 +02:00
function prepareServerlessUrl (
req : IncomingMessage ,
query : ParsedUrlQuery
) : void {
2020-02-28 17:36:19 +01:00
const curUrl = parseUrl ( req . url ! , true )
req . url = formatUrl ( {
. . . curUrl ,
search : undefined ,
query : {
. . . curUrl . query ,
. . . query ,
} ,
} )
}
2020-03-03 19:25:45 +01:00
class NoFallbackError extends Error { }