2019-08-06 00:26:20 +02:00
import compression from 'compression'
2019-05-27 20:20:33 +02:00
import fs from 'fs'
2019-03-01 20:51:13 +01:00
import { IncomingMessage , ServerResponse } from 'http'
2019-05-27 20:20:33 +02:00
import { join , resolve , sep } from 'path'
2019-11-27 22:48:28 +01:00
import { compile as compilePathToRegex } from 'path-to-regexp'
2018-12-09 22:46:45 +01:00
import { parse as parseQs , ParsedUrlQuery } from 'querystring'
2019-11-11 04:24:53 +01:00
import { format as formatUrl , parse as parseUrl , UrlWithParsedQuery } from 'url'
2019-10-23 02:40:33 +02:00
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 ,
2019-05-27 20:20:33 +02:00
PHASE_PRODUCTION_SERVER ,
2019-11-11 04:24:53 +01:00
ROUTES_MANIFEST ,
2019-05-27 20:20:33 +02:00
SERVER_DIRECTORY ,
2019-06-10 02:18:38 +02:00
SERVERLESS_DIRECTORY ,
2019-11-18 01:12:48 +01:00
DEFAULT_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'
2019-07-07 23:45:40 +02:00
import { apiResolver } from './api-utils'
2019-08-06 00:26:20 +02:00
import loadConfig , { isTargetLikeServerless } from './config'
2019-11-11 04:24:53 +01:00
import pathMatch from './lib/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'
2019-05-27 20:20:33 +02:00
import { renderToHTML } from './render'
2019-05-26 23:22:43 +02:00
import { getPagePath } from './require'
2019-12-23 22:20:17 +01:00
import Router , {
Params ,
route ,
Route ,
DynamicRoutes ,
PageChecker ,
} from './router'
2019-05-27 20:20:33 +02:00
import { sendHTML } from './send-html'
import { serveStatic } from './serve-static'
2019-10-23 02:40:33 +02:00
import { getSprCache , initializeSprCache , setSprCache } from './spr-cache'
2019-11-13 05:07:19 +01:00
import { isBlockedPage } from './utils'
2020-01-01 13:47:58 +01:00
import {
Redirect ,
Rewrite ,
RouteType ,
Header ,
} from '../../lib/check-custom-routes'
2019-11-09 23:34:53 +01:00
const getCustomRouteMatcher = pathMatch ( true )
2018-12-09 22:46:45 +01:00
type NextConfig = any
2019-07-29 20:35:09 +02:00
type Middleware = (
req : IncomingMessage ,
res : ServerResponse ,
next : ( err? : Error ) = > void
) = > void
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
staticMarkup? : boolean
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 { }
* /
2019-05-29 13:57:26 +02:00
conf? : NextConfig
2019-09-24 10:50:04 +02:00
dev? : 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
pagesManifest ? : { [ name : string ] : string }
2018-12-09 22:46:45 +01:00
buildId : string
renderOpts : {
2019-04-15 09:48:14 +02:00
poweredByHeader : boolean
2019-04-02 16:09:34 +02:00
ampBindInitData : boolean
2019-03-01 20:51:13 +01:00
staticMarkup : boolean
buildId : string
generateEtags : boolean
runtimeConfig ? : { [ key : string ] : any }
2019-05-29 13:57:26 +02:00
assetPrefix? : string
canonicalBase : string
2019-06-24 17:31:27 +02:00
documentMiddlewareEnabled : boolean
2019-09-17 22:05:20 +02:00
hasCssMode : boolean
2019-05-29 13:57:26 +02:00
dev? : boolean
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 >
2018-12-09 22:46:45 +01:00
router : Router
2019-12-23 22:20:17 +01:00
protected dynamicRoutes? : DynamicRoutes
2019-11-09 23:34:53 +01:00
protected customRoutes ? : {
rewrites : Rewrite [ ]
redirects : Redirect [ ]
2020-01-01 13:47:58 +01:00
headers : Header [ ]
2019-11-09 23:34:53 +01:00
}
2018-12-09 22:46:45 +01:00
2019-03-01 20:51:13 +01:00
public constructor ( {
dir = '.' ,
staticMarkup = false ,
quiet = false ,
conf = null ,
2019-09-24 10:50:04 +02:00
dev = false ,
2019-03-01 20:51:13 +01:00
} : ServerConstructor = { } ) {
2016-10-06 13:05:52 +02:00
this . dir = resolve ( dir )
2016-12-16 21:33:08 +01:00
this . quiet = quiet
2018-09-28 14:05:23 +02:00
const phase = this . currentPhase ( )
2018-06-04 11:38:46 +02:00
this . nextConfig = loadConfig ( phase , this . dir , conf )
2018-06-13 20:30:55 +02:00
this . distDir = join ( this . dir , this . nextConfig . distDir )
2019-05-03 18:57:47 +02:00
this . publicDir = join ( this . dir , CLIENT_PUBLIC_FILES_PATH )
2019-11-13 05:07:19 +01:00
this . hasStaticDir = 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 ( )
2019-05-29 02:32:18 +02:00
2017-03-07 19:43:56 +01:00
this . renderOpts = {
2019-04-02 16:09:34 +02:00
ampBindInitData : this.nextConfig.experimental.ampBindInitData ,
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 ,
2019-06-24 17:31:27 +02:00
documentMiddlewareEnabled : this.nextConfig.experimental
. documentMiddleware ,
2019-09-17 22:05:20 +02:00
hasCssMode : this.nextConfig.experimental.css ,
2017-03-07 19:43:56 +01:00
staticMarkup ,
2017-04-18 06:18:43 +02:00
buildId : this.buildId ,
2018-12-31 14:44:27 +01:00
generateEtags ,
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
2019-10-10 23:56:38 +02:00
if ( this . nextConfig . target === 'server' ) {
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 )
}
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 ( )
}
2019-09-24 10:50:04 +02:00
initializeSprCache ( {
dev ,
distDir : this.distDir ,
pagesDir : join (
this . distDir ,
this . _isLikeServerless
? SERVERLESS_DIRECTORY
: ` ${ SERVER_DIRECTORY } /static/ ${ this . buildId } ` ,
'pages'
) ,
flushToDisk : this.nextConfig.experimental.sprFlushToDisk ,
} )
2016-12-16 21:33:08 +01:00
}
2016-10-06 01:52:50 +02:00
2019-10-04 18:11:39 +02:00
protected currentPhase ( ) : string {
2019-01-26 02:01:16 +01:00
return PHASE_PRODUCTION_SERVER
2017-07-15 12:29:10 +02:00
}
2019-11-01 20:13:13 +01:00
private logError ( err : Error ) : void {
if ( this . onErrorMiddleware ) {
this . onErrorMiddleware ( { err } )
}
2018-12-31 14:44:27 +01:00
if ( this . quiet ) return
// tslint:disable-next-line
2019-11-01 20:13:13 +01:00
console . error ( err )
2018-12-18 17:12:49 +01:00
}
2019-03-01 20:51:13 +01:00
private handleRequest (
req : IncomingMessage ,
res : ServerResponse ,
2019-05-29 13:57:26 +02:00
parsedUrl? : UrlWithParsedQuery
2019-03-01 20:51:13 +01:00
) : Promise < void > {
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
}
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
}
2017-04-07 19:58:35 +02:00
2019-12-29 20:03:12 +01:00
if ( parsedUrl . pathname ! . startsWith ( this . nextConfig . experimental . basePath ) ) {
// If replace ends up replacing the full url it'll be `undefined`, meaning we have to default it to `/`
parsedUrl . pathname =
parsedUrl . pathname ! . replace (
this . nextConfig . experimental . basePath ,
''
) || '/'
req . url = req . url ! . replace ( this . nextConfig . experimental . basePath , '' )
}
2017-06-19 09:27:35 +02:00
res . statusCode = 200
2019-05-29 13:57:26 +02:00
return this . run ( req , res , parsedUrl ) . catch ( err = > {
2019-03-01 20:51:13 +01:00
this . logError ( err )
res . statusCode = 500
res . end ( 'Internal Server Error' )
} )
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
}
2018-12-31 14:44:27 +01:00
public setAssetPrefix ( prefix? : string ) {
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
2019-10-04 18:11:39 +02:00
protected setImmutableAssetCacheControl ( res : ServerResponse ) {
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
}
2019-11-09 23:34:53 +01:00
protected getCustomRoutes() {
return require ( join ( this . distDir , ROUTES_MANIFEST ) )
}
2019-12-23 22:20:17 +01:00
protected generateRoutes ( ) : {
routes : Route [ ]
fsRoutes : Route [ ]
catchAllRoute : Route
pageChecker : PageChecker
dynamicRoutes : DynamicRoutes | undefined
} {
2019-11-09 23:34:53 +01:00
this . customRoutes = this . getCustomRoutes ( )
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.
// See more: https://github.com/zeit/next.js/issues/2617
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 ) = > {
const p = join ( this . dir , 'static' , . . . ( params . path || [ ] ) )
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 ) = > {
2018-09-28 14:05:23 +02:00
// The commons folder holds commonschunk files
// The chunks folder holds dynamic entries
// The buildId folder holds pages and potentially other assets. As buildId changes per build it can be long-term cached.
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' ||
params . path [ 0 ] === this . buildId
) {
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
const pathname = ` / ${ params . path . join ( '/' ) } `
. replace ( /\.json$/ , '' )
. replace ( /\/index$/ , '/' )
2019-09-24 10:50:04 +02:00
req . url = pathname
const parsedUrl = parseUrl ( pathname , true )
await this . render (
req ,
res ,
pathname ,
{ _nextSprData : '1' } ,
parsedUrl
)
2019-11-18 01:12:48 +01:00
return {
finished : true ,
}
2019-09-24 10:50:04 +02:00
} ,
} ,
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
]
2019-12-23 22:20:17 +01:00
const routes : Route [ ] = [ ]
2018-09-07 14:38:01 +02:00
2019-11-09 23:34:53 +01:00
if ( this . customRoutes ) {
2020-01-01 13:47:58 +01:00
const { redirects , rewrites , headers } = this . customRoutes
2019-11-09 23:34:53 +01:00
const getCustomRoute = (
2020-01-01 13:47:58 +01:00
r : Rewrite | Redirect | Header ,
type : RouteType
2019-11-09 23:34:53 +01:00
) = > ( {
. . . r ,
type ,
matcher : getCustomRouteMatcher ( r . source ) ,
} )
2020-01-01 13:47:58 +01:00
// Headers come very first
routes . push (
. . . headers . map ( r = > {
const route = getCustomRoute ( r , 'header' )
return {
check : true ,
match : route.matcher ,
type : route . type ,
name : ` ${ route . type } ${ route . source } header route ` ,
fn : async ( _req , res , _params , _parsedUrl ) = > {
for ( const header of ( route as Header ) . headers ) {
res . setHeader ( header . key , header . value )
}
return { finished : false }
} ,
} as Route
} )
)
2019-11-09 23:34:53 +01:00
const customRoutes = [
. . . redirects . map ( r = > getCustomRoute ( r , 'redirect' ) ) ,
. . . rewrites . map ( r = > getCustomRoute ( r , 'rewrite' ) ) ,
]
routes . push (
2019-11-18 01:12:48 +01:00
. . . customRoutes . map ( route = > {
2019-11-09 23:34:53 +01:00
return {
2019-12-23 22:20:17 +01:00
check : true ,
2019-11-18 01:12:48 +01:00
match : route.matcher ,
type : route . type ,
2020-01-01 13:47:58 +01:00
statusCode : ( route as Redirect ) . statusCode ,
2019-11-18 01:12:48 +01:00
name : ` ${ route . type } ${ route . source } route ` ,
fn : async ( _req , res , params , _parsedUrl ) = > {
2019-12-17 18:02:39 +01:00
const parsedDestination = parseUrl ( route . destination , true )
2019-12-31 21:13:55 +01:00
const destQuery = parsedDestination . query
2019-12-17 18:02:39 +01:00
let destinationCompiler = compilePathToRegex (
` ${ parsedDestination . pathname ! } ${ parsedDestination . hash || '' } `
)
2019-11-23 02:23:01 +01:00
let newUrl
2019-12-31 21:13:55 +01:00
Object . keys ( destQuery ) . forEach ( key = > {
const val = destQuery [ key ]
if (
typeof val === 'string' &&
val . startsWith ( ':' ) &&
params [ val . substr ( 1 ) ]
) {
destQuery [ key ] = params [ val . substr ( 1 ) ]
}
} )
2019-11-23 02:23:01 +01:00
try {
newUrl = destinationCompiler ( params )
} catch ( err ) {
if (
2019-11-27 22:48:28 +01:00
err . message . match (
/Expected .*? to not repeat, but got an array/
)
2019-11-23 02:23:01 +01:00
) {
throw new Error (
` To use a multi-match in the destination you must add \` * \` at the end of the param name to signify it should repeat. https://err.sh/zeit/next.js/invalid-multi-match `
)
}
throw err
}
2019-11-18 01:12:48 +01:00
if ( route . type === 'redirect' ) {
2019-12-17 18:02:39 +01:00
const parsedNewUrl = parseUrl ( newUrl )
res . setHeader (
'Location' ,
formatUrl ( {
. . . parsedDestination ,
pathname : parsedNewUrl.pathname ,
hash : parsedNewUrl.hash ,
2019-12-31 21:13:55 +01:00
search : undefined ,
2019-12-17 18:02:39 +01:00
} )
)
2020-01-01 13:47:58 +01:00
res . statusCode =
( route as Redirect ) . statusCode || DEFAULT_REDIRECT_STATUS
2019-11-18 01:12:48 +01:00
res . end ( )
return {
finished : true ,
2019-11-09 23:34:53 +01:00
}
2019-12-30 20:06:38 +01:00
} else {
; ( _req as any ) . _nextDidRewrite = true
2019-11-09 23:34:53 +01:00
}
2019-11-18 01:12:48 +01:00
return {
finished : false ,
pathname : newUrl ,
2019-12-31 21:13:55 +01:00
query : parsedDestination.query ,
2019-11-09 23:34:53 +01:00
}
} ,
} as Route
} )
)
}
2019-12-23 22:20:17 +01:00
const catchAllRoute : Route = {
match : route ( '/:path*' ) ,
2019-11-20 16:53:31 +01:00
type : 'route' ,
2019-12-23 22:20:17 +01:00
name : 'Catchall render' ,
2019-11-20 16:53:31 +01:00
fn : async ( req , res , params , parsedUrl ) = > {
2019-12-23 22:20:17 +01:00
const { pathname , query } = parsedUrl
if ( ! pathname ) {
throw new Error ( 'pathname is undefined' )
}
// Used in development to check public directory paths
if ( await this . _beforeCatchAllRender ( req , res , params , parsedUrl ) ) {
return {
finished : true ,
}
}
if ( params && params . path && params . path [ 0 ] === 'api' ) {
const handled = await this . handleApiRequest (
req as NextApiRequest ,
res as NextApiResponse ,
pathname !
)
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
2018-09-09 22:32:23 +02:00
if ( this . nextConfig . useFileSystemPublicRoutes ) {
2019-06-25 16:28:48 +02:00
this . dynamicRoutes = this . getDynamicRoutes ( )
2019-05-27 20:20:33 +02:00
2018-11-14 09:55:25 +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.
2018-07-24 11:24:40 +02:00
// See more: https://github.com/zeit/next.js/issues/2617
2019-12-23 22:20:17 +01:00
routes . push ( catchAllRoute )
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 {
routes ,
fsRoutes ,
catchAllRoute ,
dynamicRoutes : this.dynamicRoutes ,
pageChecker : this.hasPage.bind ( this ) ,
}
2018-09-28 14:05:23 +02:00
}
2019-12-10 16:08:42 +01:00
private async getPagePath ( pathname : string ) {
return getPagePath (
pathname ,
this . distDir ,
this . _isLikeServerless ,
this . renderOpts . dev
)
}
protected async hasPage ( pathname : string ) : Promise < boolean > {
let found = false
try {
found = ! ! ( await this . getPagePath ( pathname ) )
} catch ( _ ) { }
return found
}
2019-11-18 01:12:48 +01:00
protected async _beforeCatchAllRender (
_req : IncomingMessage ,
_res : ServerResponse ,
_params : Params ,
_parsedUrl : UrlWithParsedQuery
) {
return false
}
2019-12-10 16:08:42 +01:00
// Used to build API page in development
protected async ensureApiPage ( pathname : string ) { }
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 ,
2019-05-29 13:57:26 +02:00
pathname : string
2019-05-26 23:22:43 +02:00
) {
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 )
if ( 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 )
const builtPagePath = await this . getPagePath ( page )
const pageModule = require ( builtPagePath )
2019-07-09 00:50:01 +02:00
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' ) {
2019-12-23 22:20:17 +01:00
await pageModule . default ( req , res )
return true
2019-07-09 00:50:01 +02:00
}
}
2019-12-10 16:08:42 +01:00
await apiResolver ( req , res , params , pageModule , 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 [ ] {
2019-05-03 18:57:47 +02:00
const routes : Route [ ] = [ ]
const publicFiles = recursiveReadDirSync ( this . publicDir )
2019-05-29 13:57:26 +02:00
publicFiles . forEach ( path = > {
2019-05-03 18:57:47 +02:00
const unixPath = path . replace ( /\\/g , '/' )
// Only include public files that will not replace a page path
2019-12-10 16:08:42 +01:00
// this should not occur now that we check this during build
if ( ! this . pagesManifest ! [ unixPath ] ) {
2019-05-03 18:57:47 +02:00
routes . push ( {
match : route ( unixPath ) ,
2019-11-18 01:12:48 +01:00
type : 'route' ,
name : 'public catchall' ,
2019-05-03 18:57:47 +02:00
fn : async ( req , res , _params , parsedUrl ) = > {
const p = join ( this . publicDir , unixPath )
await this . serveStatic ( req , res , p , parsedUrl )
2019-11-18 01:12:48 +01:00
return {
finished : true ,
}
2019-05-03 18:57:47 +02:00
} ,
} )
}
} )
return routes
}
2019-10-04 18:11:39 +02:00
protected getDynamicRoutes() {
2019-12-10 16:08:42 +01:00
const dynamicRoutedPages = Object . keys ( this . pagesManifest ! ) . filter (
isDynamicRoute
)
2019-05-30 22:42:45 +02:00
return getSortedRoutes ( dynamicRoutedPages ) . map ( page = > ( {
page ,
match : getRouteMatcher ( getRouteRegex ( page ) ) ,
} ) )
2019-05-27 20:20:33 +02:00
}
2019-07-29 20:35:09 +02:00
private handleCompression ( req : IncomingMessage , res : ServerResponse ) {
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
2019-03-01 20:51:13 +01:00
) {
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
2019-03-01 20:51:13 +01:00
) {
2019-04-15 09:48:14 +02:00
const { generateEtags , poweredByHeader } = this . renderOpts
return sendHTML ( req , res , 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 > {
2018-12-09 22:46:45 +01:00
const url : any = req . url
2019-11-13 05:07:19 +01:00
if (
url . match ( /^\/_next\// ) ||
( this . hasStaticDir && url . match ( /^\/static\// ) )
) {
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
}
2019-03-01 21:35:43 +01:00
const html = await this . renderToHTML ( req , res , pathname , query , {
2019-05-26 23:22:43 +02:00
dataOnly :
( this . renderOpts . ampBindInitData && Boolean ( query . dataOnly ) ) ||
( req . headers &&
( req . headers . accept || '' ) . indexOf ( 'application/amp.bind+json' ) !==
- 1 ) ,
2019-03-01 21:35:43 +01:00
} )
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 ,
2019-05-29 13:57:26 +02:00
query : ParsedUrlQuery = { }
2019-03-01 20:51:13 +01:00
) {
2019-08-06 00:26:20 +02:00
const serverless = ! this . renderOpts . dev && this . _isLikeServerless
2019-05-22 18:36:53 +02:00
// try serving a static AMP version first
if ( query . amp ) {
try {
2019-05-26 23:22:43 +02:00
return await loadComponents (
this . distDir ,
this . buildId ,
( pathname === '/' ? '/index' : pathname ) + '.amp' ,
2019-05-29 13:57:26 +02:00
serverless
2019-05-26 23:22:43 +02:00
)
2019-05-22 18:36:53 +02:00
} catch ( err ) {
if ( err . code !== 'ENOENT' ) throw err
}
}
2019-05-26 23:22:43 +02:00
return await loadComponents (
this . distDir ,
this . buildId ,
pathname ,
2019-05-29 13:57:26 +02:00
serverless
2019-05-26 23:22:43 +02:00
)
}
2019-09-25 17:29:22 +02:00
private __sendPayload (
res : ServerResponse ,
payload : any ,
type : string ,
revalidate? : number | false
) {
2019-09-24 10:50:04 +02:00
// TODO: ETag? Cache-Control headers? Next-specific headers?
2019-09-25 17:29:22 +02:00
res . setHeader ( 'Content-Type' , type )
2019-09-24 10:50:04 +02:00
res . setHeader ( 'Content-Length' , Buffer . byteLength ( payload ) )
2019-10-23 02:40:33 +02:00
if ( ! this . renderOpts . dev ) {
if ( revalidate ) {
res . setHeader (
'Cache-Control' ,
` s-maxage= ${ revalidate } , stale-while-revalidate `
)
} else if ( revalidate === false ) {
res . setHeader (
'Cache-Control' ,
` s-maxage=31536000, stale-while-revalidate `
)
}
2019-09-25 17:29:22 +02:00
}
2019-09-24 10:50:04 +02:00
res . end ( payload )
}
2019-05-26 23:22:43 +02:00
private async renderToHTMLWithComponents (
req : IncomingMessage ,
res : ServerResponse ,
pathname : string ,
query : ParsedUrlQuery = { } ,
result : LoadComponentsReturnType ,
2019-05-29 13:57:26 +02:00
opts : any
2019-09-24 10:50:04 +02:00
) : Promise < string | null > {
2019-05-22 18:36:53 +02:00
// handle static page
2019-05-26 23:22:43 +02:00
if ( typeof result . Component === 'string' ) {
return result . Component
}
2019-09-24 10:50:04 +02:00
// check request state
const isLikeServerless =
2019-05-26 23:22:43 +02:00
typeof result . Component === 'object' &&
2019-05-22 18:36:53 +02:00
typeof result . Component . renderReqToHTML === 'function'
2019-09-24 10:50:04 +02:00
const isSpr = ! ! result . unstable_getStaticProps
// non-spr requests should render like normal
if ( ! isSpr ) {
// handle serverless
if ( isLikeServerless ) {
2019-11-10 05:00:54 +01:00
const curUrl = parseUrl ( req . url ! , true )
req . url = formatUrl ( {
. . . curUrl ,
2019-12-31 21:13:55 +01:00
search : undefined ,
2019-11-10 05:00:54 +01:00
query : {
. . . curUrl . query ,
. . . query ,
} ,
} )
2019-09-24 10:50:04 +02:00
return result . Component . renderReqToHTML ( req , res )
}
return renderToHTML ( req , res , pathname , query , {
. . . result ,
. . . opts ,
} )
}
// Toggle whether or not this is an SPR Data request
const isSprData = isSpr && query . _nextSprData
2019-12-13 19:14:09 +01:00
delete query . _nextSprData
2019-09-24 10:50:04 +02:00
// Compute the SPR cache key
const sprCacheKey = parseUrl ( req . url || '' ) . pathname !
// Complete the response with cached data if its present
const cachedData = await getSprCache ( sprCacheKey )
if ( cachedData ) {
const data = isSprData
? JSON . stringify ( cachedData . pageData )
: cachedData . html
this . __sendPayload (
res ,
data ,
2019-09-25 17:29:22 +02:00
isSprData ? 'application/json' : 'text/html; charset=utf-8' ,
cachedData . curRevalidate
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.
// Serverless requests need its URL transformed back into the original
// request path (to emulate lambda behavior in production)
if ( isLikeServerless && isSprData ) {
2019-10-10 19:07:51 +02:00
let { pathname } = parseUrl ( req . url || '' , true )
pathname = ! pathname || pathname === '/' ? '/index' : pathname
req . url = ` /_next/data/ ${ this . buildId } ${ pathname } .json `
2019-09-24 10:50:04 +02:00
}
const doRender = withCoalescedInvoke ( async function ( ) : Promise < {
html : string | null
sprData : any
sprRevalidate : number | false
} > {
let sprData : any
let html : string | null
let sprRevalidate : number | false
let renderResult
// handle serverless
if ( isLikeServerless ) {
renderResult = await result . Component . renderReqToHTML ( req , res , true )
html = renderResult . html
sprData = renderResult . renderOpts . sprData
sprRevalidate = renderResult . renderOpts . revalidate
} else {
const renderOpts = {
. . . result ,
. . . opts ,
}
renderResult = await renderToHTML ( req , res , pathname , query , renderOpts )
html = renderResult
sprData = renderOpts . sprData
sprRevalidate = renderOpts . revalidate
}
return { html , sprData , sprRevalidate }
2019-06-10 02:16:14 +02:00
} )
2019-09-24 10:50:04 +02:00
return doRender ( sprCacheKey , [ ] ) . then (
async ( { isOrigin , value : { html , sprData , sprRevalidate } } ) = > {
// Respond to the request if a payload wasn't sent above (from cache)
if ( ! isResSent ( res ) ) {
this . __sendPayload (
res ,
isSprData ? JSON . stringify ( sprData ) : html ,
2019-09-25 17:29:22 +02:00
isSprData ? 'application/json' : 'text/html; charset=utf-8' ,
sprRevalidate
2019-09-24 10:50:04 +02:00
)
}
// Update the SPR cache if the head request
if ( isOrigin ) {
await setSprCache (
sprCacheKey ,
{ html : html ! , pageData : sprData } ,
sprRevalidate
)
}
return null
}
)
2018-12-18 17:12:49 +01:00
}
2019-05-27 20:20:33 +02:00
public renderToHTML (
2019-03-01 20:51:13 +01:00
req : IncomingMessage ,
res : ServerResponse ,
pathname : string ,
query : ParsedUrlQuery = { } ,
2019-05-26 23:22:43 +02:00
{
amphtml ,
dataOnly ,
hasAmp ,
} : {
amphtml? : boolean
hasAmp? : boolean
2019-05-29 13:57:26 +02:00
dataOnly? : boolean
} = { }
2019-03-01 20:51:13 +01:00
) : Promise < string | null > {
2019-05-27 20:20:33 +02:00
return this . findPageComponents ( pathname , query )
. then (
2019-05-29 13:57:26 +02:00
result = > {
2019-05-27 20:20:33 +02:00
return this . renderToHTMLWithComponents (
req ,
res ,
pathname ,
2019-12-13 19:14:09 +01:00
result . unstable_getStaticProps
? { _nextSprData : query._nextSprData }
: query ,
2019-05-27 20:20:33 +02:00
result ,
2019-05-29 13:57:26 +02:00
{ . . . this . renderOpts , amphtml , hasAmp , dataOnly }
2019-05-27 20:20:33 +02:00
)
} ,
2019-05-29 13:57:26 +02:00
err = > {
2019-05-27 20:20:33 +02:00
if ( err . code !== 'ENOENT' || ! this . dynamicRoutes ) {
return Promise . reject ( err )
}
for ( const dynamicRoute of this . dynamicRoutes ) {
const params = dynamicRoute . match ( pathname )
if ( ! params ) {
continue
}
return this . findPageComponents ( dynamicRoute . page , query ) . then (
2019-07-04 10:22:25 +02:00
result = > {
return this . renderToHTMLWithComponents (
2019-05-27 20:20:33 +02:00
req ,
res ,
dynamicRoute . page ,
2019-09-24 10:50:04 +02:00
// only add params for SPR enabled pages
{
. . . ( result . unstable_getStaticProps
? { _nextSprData : query._nextSprData }
: query ) ,
. . . params ,
} ,
2019-05-27 20:20:33 +02:00
result ,
2019-09-24 10:50:04 +02:00
{
. . . this . renderOpts ,
amphtml ,
hasAmp ,
dataOnly ,
}
2019-05-29 13:57:26 +02:00
)
2019-07-04 10:22:25 +02:00
}
2019-05-27 20:20:33 +02:00
)
}
return Promise . reject ( err )
2019-05-29 13:57:26 +02:00
}
2019-03-01 20:51:13 +01:00
)
2019-05-29 13:57:26 +02:00
. catch ( err = > {
2019-05-27 20:20:33 +02:00
if ( err && err . code === 'ENOENT' ) {
res . statusCode = 404
return this . renderErrorToHTML ( null , req , res , pathname , query )
} else {
this . logError ( err )
res . statusCode = 500
return this . renderErrorToHTML ( err , 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 ,
2019-05-29 13:57:26 +02:00
query : ParsedUrlQuery = { }
2019-03-01 20:51:13 +01:00
) : Promise < void > {
res . setHeader (
'Cache-Control' ,
2019-05-29 13:57:26 +02:00
'no-cache, no-store, max-age=0, must-revalidate'
2019-03-01 20:51:13 +01:00
)
2016-12-16 21:33:08 +01:00
const html = await this . renderErrorToHTML ( err , req , res , pathname , query )
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
}
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
) {
2019-05-26 23:22:43 +02:00
const result = await this . findPageComponents ( '/_error' , query )
2019-06-19 18:26:22 +02:00
let html
try {
html = await this . renderToHTMLWithComponents (
req ,
res ,
'/_error' ,
query ,
result ,
{
. . . this . renderOpts ,
err ,
}
)
} catch ( err ) {
console . error ( err )
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 ,
2019-05-29 13:57:26 +02:00
parsedUrl? : UrlWithParsedQuery
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 )
2018-12-31 14:44:27 +01:00
if ( ! pathname ) {
2018-12-09 22:46:45 +01:00
throw new Error ( 'pathname is undefined' )
}
2016-12-16 21:33:08 +01:00
res . statusCode = 404
2017-02-16 03:48:35 +01:00
return this . renderError ( null , req , res , pathname , query )
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
}
}
}
2018-12-31 14:44:27 +01:00
private isServeableUrl ( path : string ) : boolean {
2017-06-01 02:16:32 +02:00
const resolved = resolve ( path )
if (
2018-06-04 15:45:39 +02:00
resolved . indexOf ( join ( this . distDir ) + sep ) !== 0 &&
2019-05-03 18:57:47 +02:00
resolved . indexOf ( join ( this . dir , 'static' ) + sep ) !== 0 &&
resolved . indexOf ( join ( this . dir , 'public' ) + sep ) !== 0
2017-06-01 02:16:32 +02:00
) {
// Seems like the user is trying to traverse the filesystem.
return false
}
return true
}
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 (
2019-11-11 04:24:53 +01:00
` Could not find a valid build in the ' ${ this . distDir } ' directory! Try building your app with 'next build' before starting the server. `
2019-03-01 20:51:13 +01:00
)
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
private get _isLikeServerless ( ) : boolean {
return isTargetLikeServerless ( this . nextConfig . target )
}
2017-02-21 00:48:17 +01:00
}