2020-07-13 18:08:12 +02:00
import { formatUrl } from './router/utils/format-url'
2021-07-21 18:12:33 +02:00
import type { BuildManifest } from '../../server/get-page-files'
import type { ComponentType } from 'react'
import type { DomainLocale } from '../../server/config'
import type { Env } from '@next/env'
import type { IncomingMessage , ServerResponse } from 'http'
import type { NextRouter } from './router/router'
import type { ParsedUrlQuery } from 'querystring'
import type { PreviewData } from 'next/types'
import type { UrlObject } from 'url'
2021-08-13 05:36:54 +02:00
import { createContext } from 'react'
2019-04-22 19:55:03 +02:00
2019-05-23 21:31:22 +02:00
export type NextComponentType <
C extends BaseContext = NextPageContext ,
IP = { } ,
P = { }
> = ComponentType < P > & {
2019-12-03 19:35:20 +01:00
/ * *
* Used for initial page load data population . Data returned from ` getInitialProps ` is serialized when server rendered .
* Make sure to return plain ` Object ` without using ` Date ` , ` Map ` , ` Set ` .
* @param ctx Context of ` page `
* /
2019-07-07 20:52:59 +02:00
getInitialProps ? ( context : C ) : IP | Promise < IP >
2019-04-22 19:55:03 +02:00
}
2019-05-23 21:31:22 +02:00
export type DocumentType = NextComponentType <
DocumentContext ,
DocumentInitialProps ,
DocumentProps
2021-08-13 05:36:54 +02:00
>
2019-04-22 19:55:03 +02:00
2019-05-23 21:31:22 +02:00
export type AppType = NextComponentType <
AppContextType ,
AppInitialProps ,
AppPropsType
>
2019-04-22 19:55:03 +02:00
2019-09-09 18:23:34 +02:00
export type AppTreeType = ComponentType <
AppInitialProps & { [ name : string ] : any }
>
2020-07-03 05:36:13 +02:00
/ * *
* Web vitals provided to _app . reportWebVitals by Core Web Vitals plugin developed by Google Chrome team .
* https : //nextjs.org/blog/next-9-4#integrated-web-vitals-reporting
* /
export type NextWebVitalsMetric = {
id : string
startTime : number
value : number
2021-07-20 03:39:24 +02:00
} & (
| {
label : 'web-vital'
name : 'FCP' | 'LCP' | 'CLS' | 'FID' | 'TTFB'
}
| {
label : 'custom'
name :
| 'Next.js-hydration'
| 'Next.js-route-change-to-render'
| 'Next.js-render'
}
)
2020-07-03 05:36:13 +02:00
2019-04-22 19:55:03 +02:00
export type Enhancer < C > = ( Component : C ) = > C
export type ComponentsEnhancer =
2019-05-23 21:31:22 +02:00
| {
enhanceApp? : Enhancer < AppType >
2019-05-29 13:57:26 +02:00
enhanceComponent? : Enhancer < NextComponentType >
2019-05-23 21:31:22 +02:00
}
2019-04-22 19:55:03 +02:00
| Enhancer < NextComponentType >
2019-05-23 21:31:22 +02:00
export type RenderPageResult = {
html : string
head? : Array < JSX.Element | null >
}
2019-04-22 19:55:03 +02:00
2019-05-23 21:31:22 +02:00
export type RenderPage = (
2019-05-29 13:57:26 +02:00
options? : ComponentsEnhancer
2021-09-09 10:13:50 +02:00
) = > DocumentInitialProps | Promise < DocumentInitialProps >
2019-04-22 19:55:03 +02:00
2019-04-26 09:37:57 +02:00
export type BaseContext = {
2019-04-22 19:55:03 +02:00
res? : ServerResponse
2019-05-29 13:57:26 +02:00
[ k : string ] : any
2019-04-22 19:55:03 +02:00
}
2019-05-06 23:42:04 +02:00
export type NEXT_DATA = {
2020-08-14 00:19:06 +02:00
props : Record < string , any >
2019-04-22 19:55:03 +02:00
page : string
query : ParsedUrlQuery
buildId : string
assetPrefix? : string
runtimeConfig ? : { [ key : string ] : any }
nextExport? : boolean
2019-09-15 20:35:14 +02:00
autoExport? : boolean
2020-02-07 14:09:06 +01:00
isFallback? : boolean
2021-04-21 13:18:05 +02:00
dynamicIds ? : ( string | number ) [ ]
2019-05-29 13:57:26 +02:00
err? : Error & { statusCode? : number }
2020-03-03 19:39:08 +01:00
gsp? : boolean
gssp? : boolean
2020-03-06 17:14:39 +01:00
customServer? : boolean
2020-04-13 11:59:49 +02:00
gip? : boolean
appGip? : boolean
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-03-02 20:17:33 +01:00
scriptLoader? : any [ ]
2021-02-18 19:34:33 +01:00
isPreview? : boolean
2019-04-22 19:55:03 +02:00
}
2019-05-23 21:31:22 +02:00
/ * *
* ` Next ` context
* /
2019-05-06 23:42:04 +02:00
export interface NextPageContext {
2019-05-23 21:31:22 +02:00
/ * *
* Error object if encountered during rendering
* /
2019-11-11 04:24:53 +01:00
err ? : ( Error & { statusCode? : number } ) | null
2019-05-23 21:31:22 +02:00
/ * *
* ` HTTP ` request object .
* /
2019-04-22 19:55:03 +02:00
req? : IncomingMessage
2019-05-23 21:31:22 +02:00
/ * *
* ` HTTP ` response object .
* /
2019-04-22 19:55:03 +02:00
res? : ServerResponse
2019-05-23 21:31:22 +02:00
/ * *
* Path section of ` URL ` .
* /
2019-04-22 19:55:03 +02:00
pathname : string
2019-05-23 21:31:22 +02:00
/ * *
* Query string section of ` URL ` parsed as an object .
* /
2019-04-22 19:55:03 +02:00
query : ParsedUrlQuery
2019-05-23 21:31:22 +02:00
/ * *
* ` String ` of the actual path including query .
* /
2019-04-22 19:55:03 +02:00
asPath? : string
2021-05-22 18:35:57 +02:00
/ * *
* The currently active locale
* /
locale? : string
/ * *
* All configured locales
* /
locales? : string [ ]
/ * *
* The configured default locale
* /
defaultLocale? : string
2019-08-13 11:33:48 +02:00
/ * *
* ` Component ` the tree of the App to use if needing to render separately
* /
2019-09-09 18:23:34 +02:00
AppTree : AppTreeType
2019-04-22 19:55:03 +02:00
}
2019-07-11 19:35:39 +02:00
export type AppContextType < R extends NextRouter = NextRouter > = {
2019-05-06 23:42:04 +02:00
Component : NextComponentType < NextPageContext >
2019-09-09 18:23:34 +02:00
AppTree : AppTreeType
2019-05-29 13:57:26 +02:00
ctx : NextPageContext
2019-07-30 20:00:19 +02:00
router : R
2019-04-22 19:55:03 +02:00
}
2019-04-26 09:37:57 +02:00
export type AppInitialProps = {
2019-05-29 13:57:26 +02:00
pageProps : any
2019-04-22 19:55:03 +02:00
}
2019-05-23 21:31:22 +02:00
export type AppPropsType <
2019-07-11 19:35:39 +02:00
R extends NextRouter = NextRouter ,
2019-05-23 21:31:22 +02:00
P = { }
> = AppInitialProps & {
2019-05-06 23:42:04 +02:00
Component : NextComponentType < NextPageContext , any , P >
2019-05-29 13:57:26 +02:00
router : R
2020-03-06 05:15:10 +01:00
__N_SSG? : boolean
__N_SSP? : boolean
2019-04-22 19:55:03 +02:00
}
2019-05-06 23:42:04 +02:00
export type DocumentContext = NextPageContext & {
2019-05-29 13:57:26 +02:00
renderPage : RenderPage
2021-09-09 10:13:50 +02:00
defaultGetInitialProps ( ctx : DocumentContext ) : Promise < DocumentInitialProps >
2019-04-22 19:55:03 +02:00
}
2019-04-26 09:37:57 +02:00
export type DocumentInitialProps = RenderPageResult & {
2019-07-10 07:39:07 +02:00
styles? : React.ReactElement [ ] | React . ReactFragment
2019-04-22 19:55:03 +02:00
}
2021-08-13 05:36:54 +02:00
export type DocumentProps = DocumentInitialProps & HtmlProps
export type HtmlProps = {
2019-05-06 23:42:04 +02:00
__NEXT_DATA__ : NEXT_DATA
2019-04-22 19:55:03 +02:00
dangerousAsPath : string
2020-08-24 04:42:51 +02:00
docComponentsRendered : {
Html? : boolean
Main? : boolean
Head? : boolean
NextScript? : boolean
}
2020-06-07 01:00:03 +02:00
buildManifest : BuildManifest
2019-04-22 19:55:03 +02:00
ampPath : string
2019-06-27 16:22:24 +02:00
inAmpMode : boolean
hybridAmp : boolean
2019-09-17 22:05:20 +02:00
isDevelopment : boolean
2021-04-21 13:18:05 +02:00
dynamicImports : string [ ]
2019-05-29 13:57:26 +02:00
assetPrefix? : string
canonicalBase : string
2019-11-01 20:13:13 +01:00
headTags : any [ ]
2020-04-17 11:22:03 +02:00
unstable_runtimeJS? : false
2021-01-19 20:38:15 +01:00
unstable_JsPreload? : false
2020-08-03 16:22:55 +02:00
devOnlyCacheBusterQueryString : string
2021-05-12 13:37:57 +02:00
scriptLoader : { afterInteractive? : string [ ] ; beforeInteractive? : any [ ] }
2020-10-07 23:11:01 +02:00
locale? : string
2021-05-13 12:39:36 +02:00
disableOptimizedLoading? : boolean
2021-08-13 05:36:54 +02:00
styles? : React.ReactElement [ ] | React . ReactFragment
head? : Array < JSX.Element | null >
2019-04-22 19:55:03 +02:00
}
/ * *
2019-06-05 13:22:09 +02:00
* Next ` API ` route request
* /
2020-05-07 00:04:24 +02:00
export interface NextApiRequest extends IncomingMessage {
2019-06-05 13:22:09 +02:00
/ * *
* Object of ` query ` values from url
* /
query : {
[ key : string ] : string | string [ ]
}
/ * *
* Object of ` cookies ` from header
* /
cookies : {
[ key : string ] : string
}
body : any
2020-03-26 13:32:41 +01:00
env : Env
2020-07-07 05:41:16 +02:00
preview? : boolean
/ * *
* Preview data set on the request , if any
* * /
2021-04-20 20:13:48 +02:00
previewData? : PreviewData
2019-06-05 13:22:09 +02:00
}
/ * *
* Send body of response
2019-04-22 19:55:03 +02:00
* /
2019-07-09 19:02:46 +02:00
type Send < T > = ( body : T ) = > void
2019-04-22 19:55:03 +02:00
2019-06-05 13:22:09 +02:00
/ * *
* Next ` API ` route response
* /
2019-07-09 19:02:46 +02:00
export type NextApiResponse < T = any > = ServerResponse & {
2019-06-05 13:22:09 +02:00
/ * *
2019-07-10 16:43:04 +02:00
* Send data ` any ` data in response
2019-06-05 13:22:09 +02:00
* /
2019-07-09 19:02:46 +02:00
send : Send < T >
2019-06-05 13:22:09 +02:00
/ * *
2019-07-10 16:43:04 +02:00
* Send data ` json ` data in response
2019-06-05 13:22:09 +02:00
* /
2019-07-09 19:02:46 +02:00
json : Send < T >
status : ( statusCode : number ) = > NextApiResponse < T >
2020-07-29 09:01:21 +02:00
redirect ( url : string ) : NextApiResponse < T >
redirect ( status : number , url : string ) : NextApiResponse < T >
2020-02-12 02:16:42 +01:00
/ * *
* Set preview data for Next . js ' prerender mode
* /
setPreviewData : (
data : object | string ,
options ? : {
/ * *
* Specifies the number ( in seconds ) for the preview session to last for .
* The given number will be converted to an integer by rounding down .
* By default , no maximum age is set and the preview session finishes
* when the client shuts down ( browser is closed ) .
* /
maxAge? : number
}
) = > NextApiResponse < T >
clearPreviewData : ( ) = > NextApiResponse < T >
2019-06-05 13:22:09 +02:00
}
2020-02-19 04:57:31 +01:00
/ * *
* Next ` API ` route handler
* /
export type NextApiHandler < T = any > = (
req : NextApiRequest ,
res : NextApiResponse < T >
2020-04-22 02:14:24 +02:00
) = > void | Promise < void >
2020-02-19 04:57:31 +01:00
2019-06-05 13:22:09 +02:00
/ * *
* Utils
* /
2020-04-06 17:59:44 +02:00
export function execOnce < T extends ( ...args : any [ ] ) = > ReturnType < T > > (
fn : T
) : T {
2019-04-22 19:55:03 +02:00
let used = false
2020-04-06 17:59:44 +02:00
let result : ReturnType < T >
2019-11-01 20:13:13 +01:00
2020-04-06 17:59:44 +02:00
return ( ( . . . args : any [ ] ) = > {
2019-04-22 19:55:03 +02:00
if ( ! used ) {
used = true
2020-04-06 17:59:44 +02:00
result = fn ( . . . args )
2019-04-22 19:55:03 +02:00
}
2019-11-01 20:13:13 +01:00
return result
2020-04-06 17:59:44 +02:00
} ) as T
2019-04-22 19:55:03 +02:00
}
export function getLocationOrigin() {
const { protocol , hostname , port } = window . location
return ` ${ protocol } // ${ hostname } ${ port ? ':' + port : '' } `
}
export function getURL() {
const { href } = window . location
const origin = getLocationOrigin ( )
return href . substring ( origin . length )
}
2020-04-06 17:59:44 +02:00
export function getDisplayName < P > ( Component : ComponentType < P > ) {
2019-05-23 21:31:22 +02:00
return typeof Component === 'string'
? Component
: Component . displayName || Component . name || 'Unknown'
2019-04-22 19:55:03 +02:00
}
export function isResSent ( res : ServerResponse ) {
return res . finished || res . headersSent
}
2021-08-03 17:06:26 +02:00
export function normalizeRepeatedSlashes ( url : string ) {
const urlParts = url . split ( '?' )
const urlNoQuery = urlParts [ 0 ]
return (
urlNoQuery
// first we replace any non-encoded backslashes with forward
// then normalize repeated forward slashes
. replace ( /\\/g , '/' )
. replace ( /\/\/+/g , '/' ) +
( urlParts [ 1 ] ? ` ? ${ urlParts . slice ( 1 ) . join ( '?' ) } ` : '' )
)
}
2019-05-23 21:31:22 +02:00
export async function loadGetInitialProps <
C extends BaseContext ,
IP = { } ,
P = { }
2019-11-02 16:00:55 +01:00
> ( App : NextComponentType < C , IP , P > , ctx : C ) : Promise < IP > {
2019-04-22 19:55:03 +02:00
if ( process . env . NODE_ENV !== 'production' ) {
2020-01-08 17:30:53 +01:00
if ( App . prototype ? . getInitialProps ) {
2019-05-23 21:31:22 +02:00
const message = ` " ${ getDisplayName (
2019-11-02 16:00:55 +01:00
App
2021-03-29 10:25:00 +02:00
) } . getInitialProps ( ) " is defined as an instance method - visit https : //nextjs.org/docs/messages/get-initial-props-as-an-instance-method for more information.`
2019-04-22 19:55:03 +02:00
throw new Error ( message )
}
}
2019-04-30 23:28:25 +02:00
// when called from _app `ctx` is nested in `ctx`
const res = ctx . res || ( ctx . ctx && ctx . ctx . res )
2019-04-22 19:55:03 +02:00
2019-11-02 16:00:55 +01:00
if ( ! App . getInitialProps ) {
if ( ctx . ctx && ctx . Component ) {
// @ts-ignore pageProps default
return {
pageProps : await loadGetInitialProps ( ctx . Component , ctx . ctx ) ,
}
}
2020-04-06 17:59:44 +02:00
return { } as IP
2019-04-25 10:11:05 +02:00
}
2019-04-22 19:55:03 +02:00
2019-11-02 16:00:55 +01:00
const props = await App . getInitialProps ( ctx )
2019-04-22 19:55:03 +02:00
2019-04-30 23:28:25 +02:00
if ( res && isResSent ( res ) ) {
2019-04-22 19:55:03 +02:00
return props
}
if ( ! props ) {
2019-05-23 21:31:22 +02:00
const message = ` " ${ getDisplayName (
2019-11-02 16:00:55 +01:00
App
2019-05-23 21:31:22 +02:00
) } . getInitialProps ( ) " should resolve to an object. But found " $ { props } " instead . `
2019-04-22 19:55:03 +02:00
throw new Error ( message )
}
2019-08-22 19:06:30 +02:00
if ( process . env . NODE_ENV !== 'production' ) {
if ( Object . keys ( props ) . length === 0 && ! ctx . ctx ) {
console . warn (
` ${ getDisplayName (
2019-11-02 16:00:55 +01:00
App
2021-03-29 10:25:00 +02:00
) } returned an empty object from \ ` getInitialProps \` . This de-optimizes and prevents automatic static optimization. https://nextjs.org/docs/messages/empty-object-getInitialProps `
2019-08-22 19:06:30 +02:00
)
}
}
2019-04-22 19:55:03 +02:00
return props
}
2019-05-23 21:31:22 +02:00
export const urlObjectKeys = [
'auth' ,
'hash' ,
'host' ,
'hostname' ,
'href' ,
'path' ,
'pathname' ,
'port' ,
'protocol' ,
'query' ,
'search' ,
'slashes' ,
]
2020-07-13 18:08:12 +02:00
export function formatWithValidation ( url : UrlObject ) : string {
2019-04-22 19:55:03 +02:00
if ( process . env . NODE_ENV === 'development' ) {
if ( url !== null && typeof url === 'object' ) {
2020-05-18 21:24:37 +02:00
Object . keys ( url ) . forEach ( ( key ) = > {
2019-04-22 19:55:03 +02:00
if ( urlObjectKeys . indexOf ( key ) === - 1 ) {
2019-05-23 21:31:22 +02:00
console . warn (
2019-05-29 13:57:26 +02:00
` Unknown key passed via urlObject into url.format: ${ key } `
2019-05-23 21:31:22 +02:00
)
2019-04-22 19:55:03 +02:00
}
} )
}
}
2020-07-13 18:08:12 +02:00
return formatUrl ( url )
2019-04-22 19:55:03 +02:00
}
2019-08-09 21:43:29 +02:00
2020-01-04 21:53:33 +01:00
export const SP = typeof performance !== 'undefined'
export const ST =
SP &&
2019-08-09 21:43:29 +02:00
typeof performance . mark === 'function' &&
typeof performance . measure === 'function'
2021-07-05 18:31:32 +02:00
export class DecodeError extends Error { }
2021-08-13 05:36:54 +02:00
export const HtmlContext = createContext < HtmlProps > ( null as any )
if ( process . env . NODE_ENV !== 'production' ) {
HtmlContext . displayName = 'HtmlContext'
}