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'
2021-06-30 13:44:40 +02: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 ,
2021-07-13 21:38:14 +02:00
modifyRouteRegex ,
2021-06-30 13:44:40 +02:00
} from '../lib/load-custom-routes'
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 ,
2021-06-30 13:44:40 +02:00
} from '../shared/lib/constants'
2019-05-31 11:27:56 +02:00
import {
getRouteMatcher ,
getRouteRegex ,
getSortedRoutes ,
2019-06-20 20:41:02 +02:00
isDynamicRoute ,
2021-06-30 13:44:40 +02:00
} from '../shared/lib/router/utils'
import * as envConfig from '../shared/lib/runtime-config'
2021-07-05 18:31:32 +02:00
import {
DecodeError ,
isResSent ,
NextApiRequest ,
NextApiResponse ,
} from '../shared/lib/utils'
2020-10-07 23:11:01 +02:00
import {
apiResolver ,
setLazyProp ,
getCookieParser ,
tryGetPreviewData ,
__ApiPreviewProps ,
} from './api-utils'
2021-07-21 18:12:33 +02:00
import { DomainLocale , isTargetLikeServerless , NextConfig } from './config'
2021-06-30 13:44:40 +02:00
import pathMatch from '../shared/lib/router/utils/path-match'
2019-05-27 20:20:33 +02:00
import { recursiveReadDirSync } from './lib/recursive-readdir-sync'
2021-06-30 01:02:10 +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 ,
2021-06-30 13:44:40 +02:00
} from '../shared/lib/router/utils/prepare-destination'
Add `RenderResult` (#27319)
Adds `RenderResult`, replacing the `string` that `renderToHTML` used to return, with an `Observable`-like API that callers can use to subscribe and get a callback when chunks are available to flush, etc.
This is the last architectural change needed for streaming. There are, however, other things currently standing in the way of streaming. For example, it is common to mutate `res` in `getServerSideProps` to do routing work, or write headers, before fetching page data. This pattern effectively nullifies any advantages of streaming. I may do a follow-up PR that adds an experimental alternative for applications not using React 18, but the main purpose for this support is for Suspense and Server Components.
For that reason, there's no actual streaming here yet: instead we just flush a single chunk. A follow-up PR will add support for streaming suspense boundaries in React 18.
2021-07-27 21:18:21 +02:00
import { sendRenderResult , setRevalidateHeaders } from './send-payload'
2019-05-27 20:20:33 +02:00
import { serveStatic } from './serve-static'
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
import { IncrementalCache } from './incremental-cache'
2021-06-30 13:44:40 +02:00
import { execOnce } from '../shared/lib/utils'
Add `RenderResult` (#27319)
Adds `RenderResult`, replacing the `string` that `renderToHTML` used to return, with an `Observable`-like API that callers can use to subscribe and get a callback when chunks are available to flush, etc.
This is the last architectural change needed for streaming. There are, however, other things currently standing in the way of streaming. For example, it is common to mutate `res` in `getServerSideProps` to do routing work, or write headers, before fetching page data. This pattern effectively nullifies any advantages of streaming. I may do a follow-up PR that adds an experimental alternative for applications not using React 18, but the main purpose for this support is for Suspense and Server Components.
For that reason, there's no actual streaming here yet: instead we just flush a single chunk. A follow-up PR will add support for streaming suspense boundaries in React 18.
2021-07-27 21:18:21 +02:00
import {
isBlockedPage ,
RenderResult ,
resultFromChunks ,
resultToChunks ,
} 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'
2021-06-30 13:44:40 +02:00
import { PagesManifest } from '../build/webpack/plugins/pages-manifest-plugin'
import { removePathTrailingSlash } from '../client/normalize-trailing-slash'
import getRouteFromAssetPath from '../shared/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'
2021-06-30 13:44:40 +02:00
import { normalizeLocalePath } from '../shared/lib/i18n/normalize-locale-path'
import * as Log from '../build/output/log'
2020-10-16 13:10:01 +02:00
import { imageOptimizer } from './image-optimizer'
2021-06-30 13:44:40 +02:00
import { detectDomainLocale } from '../shared/lib/i18n/detect-domain-locale'
import escapePathDelimiters from '../shared/lib/router/utils/escape-path-delimiters'
import { getUtils } from '../build/webpack/loaders/next-serverless-loader/utils'
2021-04-20 20:13:48 +02:00
import { PreviewData } from 'next/types'
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
import ResponseCache , {
ResponseCacheEntry ,
ResponseCacheValue ,
} from './response-cache'
2021-07-12 23:38:57 +02:00
import { NextConfigComplete } from './config-shared'
2021-07-21 18:12:33 +02:00
import { parseNextUrl } from '../shared/lib/router/utils/parse-next-url'
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
2021-06-30 01:02:10 +02:00
export type FindComponentsResult = {
2020-02-13 05:28:31 +01:00
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
2021-07-20 18:04:31 +02:00
type RequestContext = {
req : IncomingMessage
res : ServerResponse
pathname : string
query : ParsedUrlQuery
renderOpts : RenderOptsPartial
}
2016-10-06 01:52:50 +02:00
export default class Server {
2021-03-31 18:37:44 +02:00
protected dir : string
protected quiet : boolean
2021-07-12 23:38:57 +02:00
protected nextConfig : NextConfigComplete
2021-03-31 18:37:44 +02:00
protected distDir : string
protected pagesDir? : string
protected publicDir : string
protected hasStaticDir : boolean
protected serverBuildDir : string
protected pagesManifest? : PagesManifest
protected buildId : string
protected minimalMode : boolean
protected 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
2021-05-18 23:49:50 +02:00
disableOptimizedLoading? : 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
2021-07-21 18:12:33 +02:00
domainLocales? : DomainLocale [ ]
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
2020-06-28 22:58:43 +02:00
private incrementalCache : IncrementalCache
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
private responseCache : ResponseCache
2021-03-31 18:37:44 +02:00
protected 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-07-12 23:38:57 +02:00
this . nextConfig = conf as NextConfigComplete
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 ,
2021-07-12 23:38:57 +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-04-05 19:47:03 +02:00
optimizeFonts : ! ! this . nextConfig . optimizeFonts && ! dev ,
2020-12-21 20:26:00 +01:00
fontManifest :
2021-04-05 19:47:03 +02:00
this . nextConfig . optimizeFonts && ! dev
2020-12-21 20:26:00 +01:00
? 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 ,
2021-05-13 12:39:36 +02:00
disableOptimizedLoading : this.nextConfig.experimental
. disableOptimizedLoading ,
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
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 ,
2021-07-21 19:39:38 +02:00
max : this.nextConfig.experimental.isrMemoryCacheSize ,
2021-07-22 04:42:33 +02:00
flushToDisk : ! minimalMode && this . nextConfig . experimental . isrFlushToDisk ,
2019-09-24 10:50:04 +02:00
} )
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
this . responseCache = new ResponseCache ( this . incrementalCache )
2020-07-28 12:19:28 +02:00
/ * *
* This sets environment variable to be used at the time of SSR by head . tsx .
2021-04-05 19:47:03 +02:00
* Using this from process . env allows targeting both serverless and SSR by calling
2020-12-04 10:52:54 +01:00
* ` process.env.__NEXT_OPTIMIZE_IMAGES ` .
2021-04-05 19:47:03 +02:00
* TODO ( atcastle @ ) : Remove this when experimental . optimizeImages are being cleaned 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 {
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 > {
2021-07-21 18:12:33 +02:00
setLazyProp ( { req : req as any } , 'cookies' , getCookieParser ( req . headers ) )
2020-10-07 23:11:01 +02:00
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
2021-07-21 18:12:33 +02:00
const url = parseNextUrl ( {
headers : req.headers ,
nextConfig : this.nextConfig ,
url : req.url?.replace ( /^\/+/ , '/' ) ,
} )
if ( url . basePath ) {
2020-07-12 21:03:49 +02:00
; ( 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
} `
}
2021-07-21 18:12:33 +02:00
; ( req as any ) . __nextHadTrailingSlash = url . locale ? . trailingSlash
if ( url . locale ? . domain ) {
; ( req as any ) . __nextIsLocaleDomain = true
}
2020-10-21 11:35:01 +02:00
2021-07-21 18:12:33 +02:00
if ( url . locale ? . path . detectedLocale ) {
req . url = formatUrl ( url )
; ( req as any ) . __nextStrippedLocale = true
if ( url . pathname === '/api' || url . pathname . startsWith ( '/api/' ) ) {
return this . render404 ( req , res , parsedUrl )
2020-10-14 11:56:58 +02:00
}
2021-07-21 18:12:33 +02:00
}
2020-10-14 11:56:58 +02:00
2021-07-21 18:12:33 +02:00
if ( ! this . minimalMode || ! parsedUrl . query . __nextLocale ) {
if ( url ? . locale ? . locale ) {
parsedUrl . query . __nextLocale = url . locale . locale
2020-10-07 23:11:01 +02:00
}
2021-07-21 18:12:33 +02:00
}
2020-11-03 00:00:41 +01:00
2021-07-21 18:12:33 +02:00
if ( url ? . locale ? . defaultLocale ) {
parsedUrl . query . __nextDefaultLocale = url . locale . defaultLocale
}
2020-11-11 03:09:45 +01:00
2021-07-21 18:12:33 +02:00
if ( url . locale ? . redirect ) {
res . setHeader ( 'Location' , url . locale . redirect )
res . statusCode = TEMPORARY_REDIRECT_STATUS
res . end ( )
return
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' ||
2021-07-02 00:14:42 +02:00
params . path [ 0 ] === 'image' ||
2020-01-21 14:19:53 +01:00
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 ) = >
2021-03-31 18:37:44 +02:00
imageOptimizer (
server ,
req ,
res ,
parsedUrl ,
server . nextConfig ,
2021-06-30 23:26:20 +02:00
server . distDir ,
this . renderOpts . dev
2021-03-31 18:37:44 +02:00
) ,
2020-10-16 13:10:01 +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
]
2018-09-07 14:38:01 +02:00
2021-07-13 21:38:14 +02:00
const restrictedRedirectPaths = [ '/_next' ] . map ( ( p ) = >
this . nextConfig . basePath ? ` ${ this . nextConfig . basePath } ${ p } ` : p
)
2020-11-14 04:35:42 +01:00
const getCustomRoute = (
r : Rewrite | Redirect | Header ,
type : RouteType
) = > {
2021-07-13 21:38:14 +02:00
const match = getCustomRouteMatcher (
r . source ,
! ( r as any ) . internal
? ( regex : string ) = >
modifyRouteRegex (
regex ,
type === 'redirect' ? restrictedRedirectPaths : undefined
)
: undefined
)
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
2021-05-13 13:40:25 +02:00
const headers = this . minimalMode
? [ ]
: this . customRoutes . headers . map ( ( r ) = > {
const headerRoute = getCustomRoute ( r , 'header' )
return {
match : headerRoute.match ,
has : headerRoute.has ,
type : headerRoute . type ,
name : ` ${ headerRoute . type } ${ headerRoute . source } header route ` ,
fn : async ( _req , res , params , _parsedUrl ) = > {
const hasParams = Object . keys ( params ) . length > 0
for ( const header of ( headerRoute as Header ) . headers ) {
let { key , value } = header
if ( hasParams ) {
key = compileNonPath ( key , params )
value = compileNonPath ( value , params )
}
res . setHeader ( key , value )
}
return { finished : false }
} ,
} as Route
} )
2020-06-09 22:16:23 +02:00
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-05-24 19:59:55 +02:00
const buildRewrite = ( rewrite : Rewrite , check = true ) = > {
2020-06-09 22:16:23 +02:00
const rewriteRoute = getCustomRoute ( rewrite , 'rewrite' )
return {
2020-07-12 21:03:49 +02:00
. . . rewriteRoute ,
2021-05-24 19:59:55 +02:00
check ,
2020-06-09 22:16:23 +02:00
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 ,
2021-07-16 17:11:12 +02:00
xfwd : true ,
2021-04-26 15:40:32 +02:00
proxyTimeout : 30_000 , // limit proxying to 30 seconds
2020-06-09 22:16:23 +02:00
} )
2021-04-26 15:40:32 +02:00
await new Promise ( ( proxyResolve , proxyReject ) = > {
let finished = false
proxy . on ( 'proxyReq' , ( proxyReq ) = > {
proxyReq . on ( 'close' , ( ) = > {
if ( ! finished ) {
finished = true
proxyResolve ( true )
}
} )
} )
proxy . on ( 'error' , ( err ) = > {
if ( ! finished ) {
finished = true
proxyReject ( err )
}
} )
proxy . web ( req , res )
2020-06-09 22:16:23 +02:00
} )
2021-04-26 15:40:32 +02:00
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 [ ] = [ ]
2021-05-13 13:40:25 +02:00
if ( ! this . minimalMode ) {
if ( Array . isArray ( this . customRoutes . rewrites ) ) {
2021-05-24 19:59:55 +02:00
afterFiles = this . customRoutes . rewrites . map ( ( r ) = > buildRewrite ( r ) )
2021-05-13 13:40:25 +02:00
} else {
2021-05-24 19:59:55 +02:00
beforeFiles = this . customRoutes . rewrites . beforeFiles . map ( ( r ) = >
buildRewrite ( r , false )
)
afterFiles = this . customRoutes . rewrites . afterFiles . map ( ( r ) = >
buildRewrite ( r )
)
fallback = this . customRoutes . rewrites . fallback . map ( ( r ) = >
buildRewrite ( r )
)
2021-05-13 13:40:25 +02:00
}
2021-03-26 16:19:48 +01:00
}
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-04-16 17:07:24 +02:00
const bubbleNoFallback = ! ! query . _nextBubbleNoFallback
2020-11-14 04:35:42 +01:00
2021-01-06 10:54:45 +01:00
if ( pathname === '/api' || pathname . startsWith ( '/api/' ) ) {
2021-04-16 17:07:24 +02:00
delete query . _nextBubbleNoFallback
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 }
}
}
2021-04-16 17:07:24 +02:00
try {
await this . render ( req , res , pathname , query , parsedUrl )
return {
finished : true ,
}
} catch ( err ) {
if ( err instanceof NoFallbackError && bubbleNoFallback ) {
return {
finished : false ,
}
}
throw err
2019-11-20 16:53:31 +01:00
}
} ,
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
}
2021-04-06 12:25:04 +02:00
private async getPagePath (
pathname : string ,
locales? : string [ ]
) : Promise < string > {
2019-12-10 16:08:42 +01:00
return getPagePath (
pathname ,
this . distDir ,
this . _isLikeServerless ,
2021-04-06 12:25:04 +02:00
this . renderOpts . dev ,
locales
2019-12-10 16:08:42 +01:00
)
}
protected async hasPage ( pathname : string ) : Promise < boolean > {
let found = false
try {
2021-04-06 12:25:04 +02:00
found = ! ! ( await this . getPagePath (
pathname ,
this . nextConfig . i18n ? . locales
) )
2019-12-10 16:08:42 +01:00
} 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 ,
2021-07-02 22:54:53 +02:00
this . minimalMode
2020-02-12 02:16:42 +01:00
)
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 ) {
2021-07-05 18:31:32 +02:00
if ( err instanceof DecodeError ) {
2018-11-04 01:22:33 +01:00
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
}
2021-07-20 18:04:31 +02:00
private async pipe (
fn : ( ctx : RequestContext ) = > Promise < ResponsePayload | null > ,
partialContext : {
req : IncomingMessage
res : ServerResponse
pathname : string
query : ParsedUrlQuery
}
2020-05-27 17:43:10 +02:00
) : Promise < void > {
2021-07-20 18:04:31 +02:00
// TODO: Determine when dynamic HTML is allowed
const requireStaticHTML = true
const ctx = {
. . . partialContext ,
renderOpts : {
. . . this . renderOpts ,
requireStaticHTML ,
} ,
} as const
const payload = await fn ( ctx )
if ( payload === null ) {
return
}
const { req , res } = ctx
const { body , type , revalidateOptions } = payload
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
if ( ! isResSent ( res ) ) {
const { generateEtags , poweredByHeader , dev } = this . renderOpts
if ( dev ) {
// In dev, we should not cache pages for any reason.
res . setHeader ( 'Cache-Control' , 'no-store, must-revalidate' )
}
Add `RenderResult` (#27319)
Adds `RenderResult`, replacing the `string` that `renderToHTML` used to return, with an `Observable`-like API that callers can use to subscribe and get a callback when chunks are available to flush, etc.
This is the last architectural change needed for streaming. There are, however, other things currently standing in the way of streaming. For example, it is common to mutate `res` in `getServerSideProps` to do routing work, or write headers, before fetching page data. This pattern effectively nullifies any advantages of streaming. I may do a follow-up PR that adds an experimental alternative for applications not using React 18, but the main purpose for this support is for Suspense and Server Components.
For that reason, there's no actual streaming here yet: instead we just flush a single chunk. A follow-up PR will add support for streaming suspense boundaries in React 18.
2021-07-27 21:18:21 +02:00
return sendRenderResult ( {
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
req ,
res ,
Add `RenderResult` (#27319)
Adds `RenderResult`, replacing the `string` that `renderToHTML` used to return, with an `Observable`-like API that callers can use to subscribe and get a callback when chunks are available to flush, etc.
This is the last architectural change needed for streaming. There are, however, other things currently standing in the way of streaming. For example, it is common to mutate `res` in `getServerSideProps` to do routing work, or write headers, before fetching page data. This pattern effectively nullifies any advantages of streaming. I may do a follow-up PR that adds an experimental alternative for applications not using React 18, but the main purpose for this support is for Suspense and Server Components.
For that reason, there's no actual streaming here yet: instead we just flush a single chunk. A follow-up PR will add support for streaming suspense boundaries in React 18.
2021-07-27 21:18:21 +02:00
resultOrPayload : requireStaticHTML
? ( await resultToChunks ( body ) ) . join ( '' )
: body ,
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
type ,
Add `RenderResult` (#27319)
Adds `RenderResult`, replacing the `string` that `renderToHTML` used to return, with an `Observable`-like API that callers can use to subscribe and get a callback when chunks are available to flush, etc.
This is the last architectural change needed for streaming. There are, however, other things currently standing in the way of streaming. For example, it is common to mutate `res` in `getServerSideProps` to do routing work, or write headers, before fetching page data. This pattern effectively nullifies any advantages of streaming. I may do a follow-up PR that adds an experimental alternative for applications not using React 18, but the main purpose for this support is for Suspense and Server Components.
For that reason, there's no actual streaming here yet: instead we just flush a single chunk. A follow-up PR will add support for streaming suspense boundaries in React 18.
2021-07-27 21:18:21 +02:00
generateEtags ,
poweredByHeader ,
options : revalidateOptions ,
} )
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
}
2018-12-09 22:46:45 +01:00
}
2021-07-20 18:04:31 +02:00
private async getStaticHTML (
fn : ( ctx : RequestContext ) = > Promise < ResponsePayload | null > ,
partialContext : {
req : IncomingMessage
res : ServerResponse
pathname : string
query : ParsedUrlQuery
}
) : Promise < string | null > {
const payload = await fn ( {
. . . partialContext ,
renderOpts : {
. . . this . renderOpts ,
requireStaticHTML : true ,
} ,
} )
Add `RenderResult` (#27319)
Adds `RenderResult`, replacing the `string` that `renderToHTML` used to return, with an `Observable`-like API that callers can use to subscribe and get a callback when chunks are available to flush, etc.
This is the last architectural change needed for streaming. There are, however, other things currently standing in the way of streaming. For example, it is common to mutate `res` in `getServerSideProps` to do routing work, or write headers, before fetching page data. This pattern effectively nullifies any advantages of streaming. I may do a follow-up PR that adds an experimental alternative for applications not using React 18, but the main purpose for this support is for Suspense and Server Components.
For that reason, there's no actual streaming here yet: instead we just flush a single chunk. A follow-up PR will add support for streaming suspense boundaries in React 18.
2021-07-27 21:18:21 +02:00
if ( payload === null ) {
return null
}
const chunks = await resultToChunks ( payload . body )
return chunks . join ( '' )
2021-07-20 18:04:31 +02: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 (
2021-03-29 10:25:00 +02:00
` Cannot render page with path " ${ pathname } ", did you mean "/ ${ pathname } "?. See more info here: https://nextjs.org/docs/messages/render-no-starting-slash `
2020-04-06 18:54:42 +02:00
)
}
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 (
2021-04-12 18:31:51 +02:00
! this . minimalMode &&
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 )
}
2021-05-07 19:37:51 +02:00
// Custom server users can run `app.render()` which needs compression.
if ( this . renderOpts . customServer ) {
this . handleCompression ( req , res )
}
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
}
2021-07-20 18:04:31 +02:00
return this . pipe ( ( ctx ) = > this . renderToResponse ( ctx ) , {
req ,
res ,
pathname ,
query ,
} )
2016-12-16 21:33:08 +01:00
}
2016-10-19 14:41:45 +02:00
2021-06-30 01:02:10 +02:00
protected 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
)
2021-07-05 18:31:32 +02:00
2020-10-10 12:22:45 +02:00
if (
query . __nextLocale &&
typeof components . Component === 'string' &&
! pagePath ? . startsWith ( ` / ${ query . __nextLocale } ` )
) {
2021-07-05 18:31:32 +02:00
// if loading an static HTML file the locale is required
// to be present since all HTML files are output under their locale
continue
2020-10-10 12:22:45 +02:00
}
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
}
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
private async renderToResponseWithComponents (
2021-07-20 18:04:31 +02:00
{ req , res , pathname , renderOpts : opts } : RequestContext ,
{ components , query } : FindComponentsResult
2021-07-06 11:41:51 +02:00
) : Promise < ResponsePayload | null > {
2020-10-24 21:22:48 +02:00
const is404Page = pathname === '/404'
2021-06-11 11:29:40 +02:00
const is500Page = pathname === '/500'
2020-10-24 21:22:48 +02:00
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' ) {
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
return {
2021-07-06 11:41:51 +02:00
type : 'html' ,
Add `RenderResult` (#27319)
Adds `RenderResult`, replacing the `string` that `renderToHTML` used to return, with an `Observable`-like API that callers can use to subscribe and get a callback when chunks are available to flush, etc.
This is the last architectural change needed for streaming. There are, however, other things currently standing in the way of streaming. For example, it is common to mutate `res` in `getServerSideProps` to do routing work, or write headers, before fetching page data. This pattern effectively nullifies any advantages of streaming. I may do a follow-up PR that adds an experimental alternative for applications not using React 18, but the main purpose for this support is for Suspense and Server Components.
For that reason, there's no actual streaming here yet: instead we just flush a single chunk. A follow-up PR will add support for streaming suspense boundaries in React 18.
2021-07-27 21:18:21 +02:00
// TODO: Static pages should be written as chunks
body : resultFromChunks ( [ components . Component ] ) ,
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
}
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
2021-04-20 20:13:48 +02:00
let previewData : PreviewData
2020-03-04 13:58:12 +01:00
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
2021-04-06 12:02:13 +02:00
if (
basePath &&
redirect . basePath !== false &&
redirect . destination . startsWith ( '/' )
) {
2020-11-11 08:13:18 +01:00
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
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
? null // 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
2021-06-11 11:29:40 +02:00
if ( ( is404Page || is500Page ) && isSSG ) {
2020-10-24 21:22:48 +02:00
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 ( _ ) {
2021-07-05 18:31:32 +02:00
// An improperly encoded URL was provided
throw new DecodeError ( 'failed to decode param' )
2020-12-28 21:08:58 +01:00
}
return seg
} )
. join ( '/' )
}
2021-07-22 23:04:58 +02:00
const doRender : ( ) = > Promise < ResponseCacheEntry | null > = async ( ) = > {
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
let pageData : any
Add `RenderResult` (#27319)
Adds `RenderResult`, replacing the `string` that `renderToHTML` used to return, with an `Observable`-like API that callers can use to subscribe and get a callback when chunks are available to flush, etc.
This is the last architectural change needed for streaming. There are, however, other things currently standing in the way of streaming. For example, it is common to mutate `res` in `getServerSideProps` to do routing work, or write headers, before fetching page data. This pattern effectively nullifies any advantages of streaming. I may do a follow-up PR that adds an experimental alternative for applications not using React 18, but the main purpose for this support is for Suspense and Server Components.
For that reason, there's no actual streaming here yet: instead we just flush a single chunk. A follow-up PR will add support for streaming suspense boundaries in React 18.
2021-07-27 21:18:21 +02:00
let body : RenderResult | null
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
let sprRevalidate : number | false
let isNotFound : boolean | undefined
let isRedirect : boolean | undefined
2020-09-15 21:19:07 +02:00
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
// handle serverless
if ( isLikeServerless ) {
2021-07-22 23:04:58 +02:00
const renderResult = await ( components . Component as any ) . renderReqToHTML (
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
req ,
res ,
'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 ,
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
optimizeCss : this.renderOpts.optimizeCss ,
distDir : this.distDir ,
fontManifest : this.renderOpts.fontManifest ,
domainLocales : this.renderOpts.domainLocales ,
2020-07-01 16:59:18 +02:00
}
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
)
2020-09-14 18:48:04 +02:00
Add `RenderResult` (#27319)
Adds `RenderResult`, replacing the `string` that `renderToHTML` used to return, with an `Observable`-like API that callers can use to subscribe and get a callback when chunks are available to flush, etc.
This is the last architectural change needed for streaming. There are, however, other things currently standing in the way of streaming. For example, it is common to mutate `res` in `getServerSideProps` to do routing work, or write headers, before fetching page data. This pattern effectively nullifies any advantages of streaming. I may do a follow-up PR that adds an experimental alternative for applications not using React 18, but the main purpose for this support is for Suspense and Server Components.
For that reason, there's no actual streaming here yet: instead we just flush a single chunk. A follow-up PR will add support for streaming suspense boundaries in React 18.
2021-07-27 21:18:21 +02:00
body = renderResult . html
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
pageData = renderResult . renderOpts . pageData
sprRevalidate = renderResult . renderOpts . revalidate
isNotFound = renderResult . renderOpts . isNotFound
isRedirect = renderResult . renderOpts . isRedirect
} else {
const origQuery = parseUrl ( req . url || '' , true ) . query
const hadTrailingSlash =
urlPathname !== '/' && this . nextConfig . trailingSlash
const resolvedUrl = formatUrl ( {
pathname : ` ${ resolvedUrlPathname } ${ hadTrailingSlash ? '/' : '' } ` ,
// make sure to only add query values from original URL
query : origQuery ,
} )
2020-07-01 16:59:18 +02:00
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
const renderOpts : RenderOpts = {
. . . components ,
. . . opts ,
isDataReq ,
resolvedUrl ,
locale ,
locales ,
defaultLocale ,
// For getServerSideProps and getInitialProps we need to ensure we use the original URL
// and not the resolved URL to prevent a hydration mismatch on
// asPath
resolvedAsPath :
hasServerProps || hasGetInitialProps
? formatUrl ( {
// we use the original URL pathname less the _next/data prefix if
// present
pathname : ` ${ urlPathname } ${ hadTrailingSlash ? '/' : '' } ` ,
query : origQuery ,
} )
: resolvedUrl ,
2019-09-24 10:50:04 +02:00
}
2021-07-22 23:04:58 +02:00
const renderResult = await renderToHTML (
req ,
res ,
pathname ,
query ,
renderOpts
)
2019-09-24 10:50:04 +02:00
Add `RenderResult` (#27319)
Adds `RenderResult`, replacing the `string` that `renderToHTML` used to return, with an `Observable`-like API that callers can use to subscribe and get a callback when chunks are available to flush, etc.
This is the last architectural change needed for streaming. There are, however, other things currently standing in the way of streaming. For example, it is common to mutate `res` in `getServerSideProps` to do routing work, or write headers, before fetching page data. This pattern effectively nullifies any advantages of streaming. I may do a follow-up PR that adds an experimental alternative for applications not using React 18, but the main purpose for this support is for Suspense and Server Components.
For that reason, there's no actual streaming here yet: instead we just flush a single chunk. A follow-up PR will add support for streaming suspense boundaries in React 18.
2021-07-27 21:18:21 +02:00
body = renderResult
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
// TODO: change this to a different passing mechanism
pageData = ( renderOpts as any ) . pageData
sprRevalidate = ( renderOpts as any ) . revalidate
isNotFound = ( renderOpts as any ) . isNotFound
isRedirect = ( renderOpts as any ) . isRedirect
2020-02-27 13:23:28 +01:00
}
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
let value : ResponseCacheValue | null
if ( isNotFound ) {
value = null
} else if ( isRedirect ) {
value = { kind : 'REDIRECT' , props : pageData }
} else {
Add `RenderResult` (#27319)
Adds `RenderResult`, replacing the `string` that `renderToHTML` used to return, with an `Observable`-like API that callers can use to subscribe and get a callback when chunks are available to flush, etc.
This is the last architectural change needed for streaming. There are, however, other things currently standing in the way of streaming. For example, it is common to mutate `res` in `getServerSideProps` to do routing work, or write headers, before fetching page data. This pattern effectively nullifies any advantages of streaming. I may do a follow-up PR that adds an experimental alternative for applications not using React 18, but the main purpose for this support is for Suspense and Server Components.
For that reason, there's no actual streaming here yet: instead we just flush a single chunk. A follow-up PR will add support for streaming suspense boundaries in React 18.
2021-07-27 21:18:21 +02:00
if ( ! body ) {
2021-07-22 23:04:58 +02:00
return null
}
Add `RenderResult` (#27319)
Adds `RenderResult`, replacing the `string` that `renderToHTML` used to return, with an `Observable`-like API that callers can use to subscribe and get a callback when chunks are available to flush, etc.
This is the last architectural change needed for streaming. There are, however, other things currently standing in the way of streaming. For example, it is common to mutate `res` in `getServerSideProps` to do routing work, or write headers, before fetching page data. This pattern effectively nullifies any advantages of streaming. I may do a follow-up PR that adds an experimental alternative for applications not using React 18, but the main purpose for this support is for Suspense and Server Components.
For that reason, there's no actual streaming here yet: instead we just flush a single chunk. A follow-up PR will add support for streaming suspense boundaries in React 18.
2021-07-27 21:18:21 +02:00
value = { kind : 'PAGE' , html : body , pageData }
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
}
return { revalidate : sprRevalidate , value }
}
const cacheEntry = await this . responseCache . get (
ssgCacheKey ,
async ( hasResolved ) = > {
const isProduction = ! this . renderOpts . dev
const isDynamicPathname = isDynamicRoute ( pathname )
const didRespond = hasResolved || isResSent ( res )
const { staticPaths , fallbackMode } = hasStaticPaths
? await this . getStaticPaths ( pathname )
: { staticPaths : undefined , fallbackMode : false }
// When we did not respond from cache, we need to choose to block on
// rendering or return a skeleton.
//
// * Data requests always block.
//
// * Blocking mode fallback always blocks.
//
// * Preview mode toggles all pages to be resolved in a blocking manner.
//
// * Non-dynamic pages should block (though this is an impossible
// case in production).
//
// * Dynamic pages should return their skeleton if not defined in
// getStaticPaths, then finish the data request on the client-side.
//
if (
this . minimalMode !== true &&
fallbackMode !== 'blocking' &&
ssgCacheKey &&
! didRespond &&
! isPreviewMode &&
isDynamicPathname &&
// Development should trigger fallback when the path is not in
// `getStaticPaths`
( isProduction ||
! staticPaths ||
! staticPaths . includes (
// we use ssgCacheKey here as it is normalized to match the
// encoding from getStaticPaths along with including the locale
query . amp ? ssgCacheKey . replace ( /\.amp$/ , '' ) : ssgCacheKey
) )
) {
if (
// In development, fall through to render to handle missing
// getStaticPaths.
( isProduction || staticPaths ) &&
// When fallback isn't present, abort this render so we 404
fallbackMode !== 'static'
) {
throw new NoFallbackError ( )
}
2020-02-07 14:09:06 +01:00
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
if ( ! isDataReq ) {
// Production already emitted the fallback as static HTML.
if ( isProduction ) {
const html = await this . incrementalCache . getFallback (
locale ? ` / ${ locale } ${ pathname } ` : pathname
)
return {
value : {
kind : 'PAGE' ,
Add `RenderResult` (#27319)
Adds `RenderResult`, replacing the `string` that `renderToHTML` used to return, with an `Observable`-like API that callers can use to subscribe and get a callback when chunks are available to flush, etc.
This is the last architectural change needed for streaming. There are, however, other things currently standing in the way of streaming. For example, it is common to mutate `res` in `getServerSideProps` to do routing work, or write headers, before fetching page data. This pattern effectively nullifies any advantages of streaming. I may do a follow-up PR that adds an experimental alternative for applications not using React 18, but the main purpose for this support is for Suspense and Server Components.
For that reason, there's no actual streaming here yet: instead we just flush a single chunk. A follow-up PR will add support for streaming suspense boundaries in React 18.
2021-07-27 21:18:21 +02:00
html : resultFromChunks ( [ html ] ) ,
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
pageData : { } ,
} ,
}
}
// We need to generate the fallback on-demand for development.
else {
query . __nextFallback = 'true'
if ( isLikeServerless ) {
prepareServerlessUrl ( req , query )
}
const result = await doRender ( )
2021-07-22 23:04:58 +02:00
if ( ! result ) {
return null
}
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
// Prevent caching this result
delete result . revalidate
return result
}
2020-08-20 19:59:03 +02:00
}
2020-02-07 14:09:06 +01:00
}
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
const result = await doRender ( )
2021-07-22 23:04:58 +02:00
if ( ! result ) {
return null
}
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
return {
. . . result ,
revalidate :
result . revalidate !== undefined
? result . revalidate
: /* default to minimum revalidate (this should be an invariant) */ 1 ,
}
2020-08-20 19:59:03 +02:00
}
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
)
2020-10-15 23:55:38 +02:00
2021-07-22 23:04:58 +02:00
if ( ! cacheEntry ) {
if ( ssgCacheKey ) {
// A cache entry might not be generated if a response is written
// in `getInitialProps` or `getServerSideProps`, but those shouldn't
// have a cache key. If we do have a cache key but we don't end up
// with a cache entry, then either Next.js or the application has a
// bug that needs fixing.
throw new Error ( 'invariant: cache entry required but not generated' )
}
return null
}
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
const { revalidate , value : cachedData } = cacheEntry
const revalidateOptions : any =
typeof revalidate !== 'undefined' &&
( ! this . renderOpts . dev || ( hasServerProps && ! isDataReq ) )
2020-11-14 08:12:47 +01:00
? {
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
// When the page is 404 cache-control should not be added
2021-07-02 10:40:13 +02:00
private : isPreviewMode || is404Page ,
2020-11-14 08:12:47 +01:00
stateful : ! isSSG ,
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
revalidate ,
2020-11-14 08:12:47 +01:00
}
: undefined
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
if ( ! cachedData ) {
2020-11-14 08:12:47 +01:00
if ( revalidateOptions ) {
setRevalidateHeaders ( res , revalidateOptions )
}
2020-12-30 23:35:02 +01:00
if ( isDataReq ) {
res . statusCode = 404
res . end ( '{"notFound":true}' )
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
return null
2020-12-30 23:35:02 +01:00
} else {
await this . render404 ( req , res , {
pathname ,
query ,
} as UrlWithParsedQuery )
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
return null
}
} else if ( cachedData . kind === 'REDIRECT' ) {
if ( isDataReq ) {
return {
type : 'json' ,
Add `RenderResult` (#27319)
Adds `RenderResult`, replacing the `string` that `renderToHTML` used to return, with an `Observable`-like API that callers can use to subscribe and get a callback when chunks are available to flush, etc.
This is the last architectural change needed for streaming. There are, however, other things currently standing in the way of streaming. For example, it is common to mutate `res` in `getServerSideProps` to do routing work, or write headers, before fetching page data. This pattern effectively nullifies any advantages of streaming. I may do a follow-up PR that adds an experimental alternative for applications not using React 18, but the main purpose for this support is for Suspense and Server Components.
For that reason, there's no actual streaming here yet: instead we just flush a single chunk. A follow-up PR will add support for streaming suspense boundaries in React 18.
2021-07-27 21:18:21 +02:00
body : resultFromChunks ( [ JSON . stringify ( cachedData . props ) ] ) ,
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
revalidateOptions ,
}
} else {
await handleRedirect ( cachedData . props )
return null
}
} else {
return {
type : isDataReq ? 'json' : 'html' ,
Add `RenderResult` (#27319)
Adds `RenderResult`, replacing the `string` that `renderToHTML` used to return, with an `Observable`-like API that callers can use to subscribe and get a callback when chunks are available to flush, etc.
This is the last architectural change needed for streaming. There are, however, other things currently standing in the way of streaming. For example, it is common to mutate `res` in `getServerSideProps` to do routing work, or write headers, before fetching page data. This pattern effectively nullifies any advantages of streaming. I may do a follow-up PR that adds an experimental alternative for applications not using React 18, but the main purpose for this support is for Suspense and Server Components.
For that reason, there's no actual streaming here yet: instead we just flush a single chunk. A follow-up PR will add support for streaming suspense boundaries in React 18.
2021-07-27 21:18:21 +02:00
body : isDataReq
? resultFromChunks ( [ JSON . stringify ( cachedData . pageData ) ] )
: cachedData . html ,
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
revalidateOptions ,
2020-12-30 23:35:02 +01:00
}
2020-10-15 23:55:38 +02:00
}
2018-12-18 17:12:49 +01:00
}
2021-07-06 11:41:51 +02:00
private async renderToResponse (
2021-07-20 18:04:31 +02:00
ctx : RequestContext
2021-07-06 11:41:51 +02:00
) : Promise < ResponsePayload | null > {
2021-07-20 18:04:31 +02:00
const { res , query , pathname } = ctx
2021-04-16 17:07:24 +02:00
const bubbleNoFallback = ! ! query . _nextBubbleNoFallback
delete query . _nextBubbleNoFallback
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 {
2021-07-20 18:04:31 +02:00
return await this . renderToResponseWithComponents ( ctx , result )
2020-03-03 19:25:45 +01:00
} catch ( err ) {
2021-04-16 17:07:24 +02:00
const isNoFallbackError = err instanceof NoFallbackError
if ( ! isNoFallbackError || ( isNoFallbackError && bubbleNoFallback ) ) {
2020-03-03 19:25:45 +01:00
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 {
2021-07-06 11:41:51 +02:00
return await this . renderToResponseWithComponents (
2021-07-20 18:04:31 +02:00
{
. . . ctx ,
pathname : dynamicRoute.page ,
renderOpts : {
. . . ctx . renderOpts ,
params ,
} ,
} ,
dynamicRouteResult
2020-03-03 19:25:45 +01:00
)
} catch ( err ) {
2021-04-16 17:07:24 +02:00
const isNoFallbackError = err instanceof NoFallbackError
if (
! isNoFallbackError ||
( isNoFallbackError && bubbleNoFallback )
) {
2020-03-03 19:25:45 +01:00
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 ) {
2021-07-05 18:31:32 +02:00
if ( err instanceof NoFallbackError && bubbleNoFallback ) {
2021-04-16 17:07:24 +02:00
throw err
}
2021-07-05 18:31:32 +02:00
if ( err instanceof DecodeError ) {
2020-09-24 08:05:40 +02:00
res . statusCode = 400
2021-07-20 18:04:31 +02:00
return await this . renderErrorToResponse ( ctx , err )
2020-09-24 08:05:40 +02:00
}
2021-07-05 18:31:32 +02:00
2020-02-13 05:28:31 +01:00
res . statusCode = 500
2021-06-30 01:02:10 +02:00
const isWrappedError = err instanceof WrappedBuildError
2021-07-06 11:41:51 +02:00
const response = await this . renderErrorToResponse (
2021-07-20 18:04:31 +02:00
ctx ,
isWrappedError ? err.innerError : err
2021-06-30 01:02:10 +02:00
)
2021-02-11 10:31:49 +01:00
2021-06-30 01:02:10 +02:00
if ( ! isWrappedError ) {
if ( this . minimalMode ) {
throw err
}
this . logError ( err )
2021-02-11 10:31:49 +01:00
}
2021-07-06 11:41:51 +02:00
return response
2020-02-13 05:28:31 +01:00
}
res . statusCode = 404
2021-07-20 18:04:31 +02:00
return this . renderErrorToResponse ( ctx , null )
2021-07-06 11:41:51 +02:00
}
public async renderToHTML (
req : IncomingMessage ,
res : ServerResponse ,
pathname : string ,
query : ParsedUrlQuery = { }
) : Promise < string | null > {
2021-07-20 18:04:31 +02:00
return this . getStaticHTML ( ( ctx ) = > this . renderToResponse ( ctx ) , {
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'
)
}
2021-02-11 10:31:49 +01:00
2021-07-20 18:04:31 +02:00
return this . pipe (
async ( ctx ) = > {
const response = await this . renderErrorToResponse ( ctx , err )
if ( this . minimalMode && res . statusCode === 500 ) {
throw err
}
return response
} ,
{ req , res , pathname , query }
)
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 (
2021-03-29 10:25:00 +02:00
` You have added a custom /_error page without a custom /404 page. This prevents the 404 page from being auto statically optimized. \ nSee here for info: https://nextjs.org/docs/messages/custom-error-no-custom-404 `
2020-04-05 13:19:14 +02:00
)
)
} )
2021-07-06 11:41:51 +02:00
private async renderErrorToResponse (
2021-07-20 18:04:31 +02:00
ctx : RequestContext ,
_err : Error | null
2021-07-06 11:41:51 +02:00
) : Promise < ResponsePayload | null > {
2021-07-20 18:04:31 +02:00
const { res , query } = ctx
2021-06-30 01:02:10 +02:00
let err = _err
if ( this . renderOpts . dev && ! err && res . statusCode === 500 ) {
err = new Error (
'An undefined error was thrown sometime during render... ' +
'See https://nextjs.org/docs/messages/threw-undefined'
)
}
2021-04-15 12:19:19 +02:00
try {
let result : null | FindComponentsResult = null
2020-01-20 15:10:24 +01:00
2021-04-15 12:19:19 +02:00
const is404 = res . statusCode === 404
let using404Page = false
2020-02-01 15:47:42 +01:00
2021-04-15 12:19:19 +02:00
// use static 404 page if available and is 404 response
if ( is404 ) {
result = await this . findPageComponents ( '/404' , query )
using404Page = result !== null
}
let statusPage = ` / ${ res . statusCode } `
2021-02-22 17:29:50 +01:00
2021-04-15 12:19:19 +02:00
if ( ! result && STATIC_STATUS_PAGES . includes ( statusPage ) ) {
result = await this . findPageComponents ( statusPage , query )
}
2020-01-20 15:10:24 +01:00
2021-04-15 12:19:19 +02:00
if ( ! result ) {
result = await this . findPageComponents ( '/_error' , query )
statusPage = '/_error'
}
2020-01-20 15:10:24 +01:00
2021-04-15 12:19:19 +02:00
if (
process . env . NODE_ENV !== 'production' &&
! using404Page &&
( await this . hasPage ( '/_error' ) ) &&
! ( await this . hasPage ( '/404' ) )
) {
this . customErrorNo404Warn ( )
}
2020-04-05 13:19:14 +02:00
2020-03-03 19:25:45 +01:00
try {
2021-07-06 11:41:51 +02:00
return await this . renderToResponseWithComponents (
2020-03-03 19:25:45 +01:00
{
2021-07-20 18:04:31 +02:00
. . . ctx ,
pathname : statusPage ,
renderOpts : {
. . . ctx . renderOpts ,
err ,
} ,
} ,
result !
2020-03-03 19:25:45 +01:00
)
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 ) {
2021-06-30 01:02:10 +02:00
const isWrappedError = renderToHtmlError instanceof WrappedBuildError
if ( ! isWrappedError ) {
this . logError ( renderToHtmlError )
}
2019-06-19 18:26:22 +02:00
res . statusCode = 500
2021-06-30 01:02:10 +02:00
const fallbackComponents = await this . getFallbackErrorComponents ( )
2021-04-15 12:19:19 +02:00
2021-06-30 01:02:10 +02:00
if ( fallbackComponents ) {
2021-07-06 11:41:51 +02:00
return this . renderToResponseWithComponents (
2021-04-15 12:19:19 +02:00
{
2021-07-20 18:04:31 +02:00
. . . ctx ,
pathname : '/_error' ,
renderOpts : {
. . . ctx . renderOpts ,
// We render `renderToHtmlError` here because `err` is
// already captured in the stacktrace.
err : isWrappedError
? renderToHtmlError . innerError
: renderToHtmlError ,
} ,
2021-04-15 12:19:19 +02:00
} ,
{
2021-07-20 18:04:31 +02:00
query ,
components : fallbackComponents ,
2021-04-15 12:19:19 +02:00
}
)
}
2021-07-06 11:41:51 +02:00
return {
type : 'html' ,
Add `RenderResult` (#27319)
Adds `RenderResult`, replacing the `string` that `renderToHTML` used to return, with an `Observable`-like API that callers can use to subscribe and get a callback when chunks are available to flush, etc.
This is the last architectural change needed for streaming. There are, however, other things currently standing in the way of streaming. For example, it is common to mutate `res` in `getServerSideProps` to do routing work, or write headers, before fetching page data. This pattern effectively nullifies any advantages of streaming. I may do a follow-up PR that adds an experimental alternative for applications not using React 18, but the main purpose for this support is for Suspense and Server Components.
For that reason, there's no actual streaming here yet: instead we just flush a single chunk. A follow-up PR will add support for streaming suspense boundaries in React 18.
2021-07-27 21:18:21 +02:00
body : resultFromChunks ( [ 'Internal Server Error' ] ) ,
2021-07-06 11:41:51 +02:00
}
2019-06-19 18:26:22 +02:00
}
2021-07-06 11:41:51 +02:00
}
public async renderErrorToHTML (
err : Error | null ,
req : IncomingMessage ,
res : ServerResponse ,
pathname : string ,
query : ParsedUrlQuery = { }
) : Promise < string | null > {
2021-07-20 18:04:31 +02:00
return this . getStaticHTML ( ( ctx ) = > this . renderErrorToResponse ( ctx , err ) , {
2021-07-06 11:41:51 +02:00
req ,
res ,
pathname ,
2021-07-20 18:04:31 +02:00
query ,
} )
2016-11-24 15:03:16 +01:00
}
2021-06-30 01:02:10 +02:00
protected async getFallbackErrorComponents ( ) : Promise < LoadComponentsReturnType | null > {
// The development server will provide an implementation for this
return null
}
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
}
2021-07-20 18:04:31 +02:00
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 (
2021-03-29 10:25:00 +02: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://nextjs.org/docs/messages/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 { }
2021-06-30 01:02:10 +02:00
// Internal wrapper around build errors at development
// time, to prevent us from propagating or logging them
export class WrappedBuildError extends Error {
innerError : Error
constructor ( innerError : Error ) {
super ( )
this . innerError = innerError
}
}
2021-07-06 11:41:51 +02:00
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
type ResponsePayload = {
2021-07-06 11:41:51 +02:00
type : 'html' | 'json'
Add `RenderResult` (#27319)
Adds `RenderResult`, replacing the `string` that `renderToHTML` used to return, with an `Observable`-like API that callers can use to subscribe and get a callback when chunks are available to flush, etc.
This is the last architectural change needed for streaming. There are, however, other things currently standing in the way of streaming. For example, it is common to mutate `res` in `getServerSideProps` to do routing work, or write headers, before fetching page data. This pattern effectively nullifies any advantages of streaming. I may do a follow-up PR that adds an experimental alternative for applications not using React 18, but the main purpose for this support is for Suspense and Server Components.
For that reason, there's no actual streaming here yet: instead we just flush a single chunk. A follow-up PR will add support for streaming suspense boundaries in React 18.
2021-07-27 21:18:21 +02:00
body : RenderResult
2021-07-06 11:41:51 +02:00
revalidateOptions? : any
}