next-server.ts 38.1 KB
Newer Older
1
import compression from 'compression'
J
Joe Haddad 已提交
2
import fs from 'fs'
J
Joe Haddad 已提交
3
import { IncomingMessage, ServerResponse } from 'http'
J
Joe Haddad 已提交
4 5
import Proxy from 'http-proxy'
import nanoid from 'next/dist/compiled/nanoid/index.js'
J
Joe Haddad 已提交
6
import { join, resolve, sep } from 'path'
7
import { parse as parseQs, ParsedUrlQuery } from 'querystring'
8
import { format as formatUrl, parse as parseUrl, UrlWithParsedQuery } from 'url'
9
import { PrerenderManifest } from '../../build'
J
Joe Haddad 已提交
10 11 12 13 14 15 16
import {
  getRedirectStatus,
  Header,
  Redirect,
  Rewrite,
  RouteType,
} from '../../lib/check-custom-routes'
J
JJ Kasper 已提交
17
import { withCoalescedInvoke } from '../../lib/coalesced-function'
J
Joe Haddad 已提交
18 19
import {
  BUILD_ID_FILE,
20
  CLIENT_PUBLIC_FILES_PATH,
J
Joe Haddad 已提交
21 22
  CLIENT_STATIC_FILES_PATH,
  CLIENT_STATIC_FILES_RUNTIME,
23
  PAGES_MANIFEST,
J
Joe Haddad 已提交
24
  PHASE_PRODUCTION_SERVER,
J
Joe Haddad 已提交
25
  PRERENDER_MANIFEST,
26
  ROUTES_MANIFEST,
27
  SERVERLESS_DIRECTORY,
J
Joe Haddad 已提交
28
  SERVER_DIRECTORY,
T
Tim Neutkens 已提交
29
} from '../lib/constants'
J
Joe Haddad 已提交
30 31 32 33
import {
  getRouteMatcher,
  getRouteRegex,
  getSortedRoutes,
34
  isDynamicRoute,
J
Joe Haddad 已提交
35
} from '../lib/router/utils'
36
import * as envConfig from '../lib/runtime-config'
J
Joe Haddad 已提交
37
import { isResSent, NextApiRequest, NextApiResponse } from '../lib/utils'
J
Joe Haddad 已提交
38
import { apiResolver, tryGetPreviewData, __ApiPreviewProps } from './api-utils'
39
import loadConfig, { isTargetLikeServerless } from './config'
40
import pathMatch from './lib/path-match'
J
Joe Haddad 已提交
41
import { recursiveReadDirSync } from './lib/recursive-readdir-sync'
42
import { loadComponents, LoadComponentsReturnType } from './load-components'
J
Joe Haddad 已提交
43
import { normalizePagePath } from './normalize-page-path'
44
import { RenderOpts, RenderOptsPartial, renderToHTML } from './render'
J
Joe Haddad 已提交
45
import { getPagePath } from './require'
46 47 48
import Router, {
  DynamicRoutes,
  PageChecker,
J
Joe Haddad 已提交
49
  Params,
50
  prepareDestination,
J
Joe Haddad 已提交
51 52
  route,
  Route,
53
} from './router'
J
Joe Haddad 已提交
54
import { sendHTML } from './send-html'
55
import { sendPayload } from './send-payload'
J
Joe Haddad 已提交
56
import { serveStatic } from './serve-static'
57
import {
J
Joe Haddad 已提交
58
  getFallback,
59 60 61 62
  getSprCache,
  initializeSprCache,
  setSprCache,
} from './spr-cache'
63
import { isBlockedPage } from './utils'
J
JJ Kasper 已提交
64 65

const getCustomRouteMatcher = pathMatch(true)
66 67 68

type NextConfig = any

69 70 71 72 73 74
type Middleware = (
  req: IncomingMessage,
  res: ServerResponse,
  next: (err?: Error) => void
) => void

75 76 77 78 79
type FindComponentsResult = {
  components: LoadComponentsReturnType
  query: ParsedUrlQuery
}

T
Tim Neutkens 已提交
80
export type ServerConstructor = {
81 82 83
  /**
   * Where the Next project is located - @default '.'
   */
J
Joe Haddad 已提交
84 85
  dir?: string
  staticMarkup?: boolean
86 87 88
  /**
   * Hide error messages containing server information - @default false
   */
J
Joe Haddad 已提交
89
  quiet?: boolean
90 91 92
  /**
   * Object what you would use in next.config.js - @default {}
   */
93
  conf?: NextConfig
J
JJ Kasper 已提交
94
  dev?: boolean
95
  customServer?: boolean
96
}
97

N
nkzawa 已提交
98
export default class Server {
99 100 101 102
  dir: string
  quiet: boolean
  nextConfig: NextConfig
  distDir: string
103
  pagesDir?: string
104
  publicDir: string
105
  hasStaticDir: boolean
106 107
  serverBuildDir: string
  pagesManifest?: { [name: string]: string }
108 109
  buildId: string
  renderOpts: {
T
Tim Neutkens 已提交
110
    poweredByHeader: boolean
J
Joe Haddad 已提交
111 112 113 114
    staticMarkup: boolean
    buildId: string
    generateEtags: boolean
    runtimeConfig?: { [key: string]: any }
115 116
    assetPrefix?: string
    canonicalBase: string
117
    documentMiddlewareEnabled: boolean
J
Joe Haddad 已提交
118
    hasCssMode: boolean
119
    dev?: boolean
120
    previewProps: __ApiPreviewProps
121
    customServer?: boolean
122
    ampOptimizerConfig?: { [key: string]: any }
123
  }
124
  private compression?: Middleware
J
JJ Kasper 已提交
125
  private onErrorMiddleware?: ({ err }: { err: Error }) => Promise<void>
126
  router: Router
127
  protected dynamicRoutes?: DynamicRoutes
J
JJ Kasper 已提交
128 129 130
  protected customRoutes?: {
    rewrites: Rewrite[]
    redirects: Redirect[]
131
    headers: Header[]
J
JJ Kasper 已提交
132
  }
133 134 135
  protected staticPathsWorker?: import('jest-worker').default & {
    loadStaticPaths: typeof import('../../server/static-paths-worker').loadStaticPaths
  }
136

J
Joe Haddad 已提交
137 138 139 140 141
  public constructor({
    dir = '.',
    staticMarkup = false,
    quiet = false,
    conf = null,
J
JJ Kasper 已提交
142
    dev = false,
143
    customServer = true,
J
Joe Haddad 已提交
144
  }: ServerConstructor = {}) {
N
nkzawa 已提交
145
    this.dir = resolve(dir)
N
Naoyuki Kanezawa 已提交
146
    this.quiet = quiet
T
Tim Neutkens 已提交
147
    const phase = this.currentPhase()
148
    this.nextConfig = loadConfig(phase, this.dir, conf)
149
    this.distDir = join(this.dir, this.nextConfig.distDir)
150
    this.publicDir = join(this.dir, CLIENT_PUBLIC_FILES_PATH)
151
    this.hasStaticDir = fs.existsSync(join(this.dir, 'static'))
T
Tim Neutkens 已提交
152

153 154
    // Only serverRuntimeConfig needs the default
    // publicRuntimeConfig gets it's default in client/index.js
J
Joe Haddad 已提交
155 156 157 158 159
    const {
      serverRuntimeConfig = {},
      publicRuntimeConfig,
      assetPrefix,
      generateEtags,
160
      compress,
J
Joe Haddad 已提交
161
    } = this.nextConfig
162

T
Tim Neutkens 已提交
163
    this.buildId = this.readBuildId()
164

165
    this.renderOpts = {
T
Tim Neutkens 已提交
166
      poweredByHeader: this.nextConfig.poweredByHeader,
167
      canonicalBase: this.nextConfig.amp.canonicalBase,
168 169
      documentMiddlewareEnabled: this.nextConfig.experimental
        .documentMiddleware,
J
Joe Haddad 已提交
170
      hasCssMode: this.nextConfig.experimental.css,
171
      staticMarkup,
172
      buildId: this.buildId,
173
      generateEtags,
174
      previewProps: this.getPreviewProps(),
175
      customServer: customServer === true ? true : undefined,
176
      ampOptimizerConfig: this.nextConfig.experimental.amp?.optimizer,
177
    }
N
Naoyuki Kanezawa 已提交
178

179 180
    // Only the `publicRuntimeConfig` key is exposed to the client side
    // It'll be rendered as part of __NEXT_DATA__ on the client side
181
    if (Object.keys(publicRuntimeConfig).length > 0) {
182
      this.renderOpts.runtimeConfig = publicRuntimeConfig
183 184
    }

185
    if (compress && this.nextConfig.target === 'server') {
186 187 188
      this.compression = compression() as Middleware
    }

189
    // Initialize next/config with the environment configuration
190 191 192 193
    envConfig.setConfig({
      serverRuntimeConfig,
      publicRuntimeConfig,
    })
194

195 196 197 198 199 200 201 202 203 204
    this.serverBuildDir = join(
      this.distDir,
      this._isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY
    )
    const pagesManifestPath = join(this.serverBuildDir, PAGES_MANIFEST)

    if (!dev) {
      this.pagesManifest = require(pagesManifestPath)
    }

J
JJ Kasper 已提交
205
    this.router = new Router(this.generateRoutes())
206
    this.setAssetPrefix(assetPrefix)
J
JJ Kasper 已提交
207

208 209 210
    // call init-server middleware, this is also handled
    // individually in serverless bundles when deployed
    if (!dev && this.nextConfig.experimental.plugins) {
211 212
      const initServer = require(join(this.serverBuildDir, 'init-server.js'))
        .default
213
      this.onErrorMiddleware = require(join(
214
        this.serverBuildDir,
215 216 217 218 219
        'on-error-server.js'
      )).default
      initServer()
    }

J
JJ Kasper 已提交
220 221 222 223 224 225 226 227 228 229 230 231
    initializeSprCache({
      dev,
      distDir: this.distDir,
      pagesDir: join(
        this.distDir,
        this._isLikeServerless
          ? SERVERLESS_DIRECTORY
          : `${SERVER_DIRECTORY}/static/${this.buildId}`,
        'pages'
      ),
      flushToDisk: this.nextConfig.experimental.sprFlushToDisk,
    })
N
Naoyuki Kanezawa 已提交
232
  }
N
nkzawa 已提交
233

234
  protected currentPhase(): string {
235
    return PHASE_PRODUCTION_SERVER
236 237
  }

238 239 240 241
  private logError(err: Error): void {
    if (this.onErrorMiddleware) {
      this.onErrorMiddleware({ err })
    }
242 243
    if (this.quiet) return
    // tslint:disable-next-line
244
    console.error(err)
245 246
  }

247
  private async handleRequest(
J
Joe Haddad 已提交
248 249
    req: IncomingMessage,
    res: ServerResponse,
250
    parsedUrl?: UrlWithParsedQuery
J
Joe Haddad 已提交
251
  ): Promise<void> {
252
    // Parse url if parsedUrl not provided
253
    if (!parsedUrl || typeof parsedUrl !== 'object') {
254 255
      const url: any = req.url
      parsedUrl = parseUrl(url, true)
256
    }
257

258 259 260
    // Parse the querystring ourselves if the user doesn't handle querystring parsing
    if (typeof parsedUrl.query === 'string') {
      parsedUrl.query = parseQs(parsedUrl.query)
N
Naoyuki Kanezawa 已提交
261
    }
262

T
Tim Neutkens 已提交
263 264 265 266 267 268 269 270 271 272
    if (parsedUrl.pathname!.startsWith(this.nextConfig.experimental.basePath)) {
      // If replace ends up replacing the full url it'll be `undefined`, meaning we have to default it to `/`
      parsedUrl.pathname =
        parsedUrl.pathname!.replace(
          this.nextConfig.experimental.basePath,
          ''
        ) || '/'
      req.url = req.url!.replace(this.nextConfig.experimental.basePath, '')
    }

273
    res.statusCode = 200
274 275 276
    try {
      return await this.run(req, res, parsedUrl)
    } catch (err) {
J
Joe Haddad 已提交
277 278 279
      this.logError(err)
      res.statusCode = 500
      res.end('Internal Server Error')
280
    }
281 282
  }

283
  public getRequestHandler() {
284
    return this.handleRequest.bind(this)
N
nkzawa 已提交
285 286
  }

287
  public setAssetPrefix(prefix?: string) {
288
    this.renderOpts.assetPrefix = prefix ? prefix.replace(/\/$/, '') : ''
289 290
  }

291
  // Backwards compatibility
292
  public async prepare(): Promise<void> {}
N
nkzawa 已提交
293

T
Tim Neutkens 已提交
294
  // Backwards compatibility
295
  protected async close(): Promise<void> {}
T
Tim Neutkens 已提交
296

297
  protected setImmutableAssetCacheControl(res: ServerResponse) {
T
Tim Neutkens 已提交
298
    res.setHeader('Cache-Control', 'public, max-age=31536000, immutable')
N
nkzawa 已提交
299 300
  }

J
JJ Kasper 已提交
301 302 303 304
  protected getCustomRoutes() {
    return require(join(this.distDir, ROUTES_MANIFEST))
  }

305 306 307 308
  private _cachedPreviewManifest: PrerenderManifest | undefined
  protected getPrerenderManifest(): PrerenderManifest {
    if (this._cachedPreviewManifest) {
      return this._cachedPreviewManifest
J
Joe Haddad 已提交
309
    }
310 311 312 313 314 315
    const manifest = require(join(this.distDir, PRERENDER_MANIFEST))
    return (this._cachedPreviewManifest = manifest)
  }

  protected getPreviewProps(): __ApiPreviewProps {
    return this.getPrerenderManifest().preview
J
Joe Haddad 已提交
316 317
  }

318
  protected generateRoutes(): {
319 320
    headers: Route[]
    rewrites: Route[]
321
    fsRoutes: Route[]
322
    redirects: Route[]
323 324
    catchAllRoute: Route
    pageChecker: PageChecker
325
    useFileSystemPublicRoutes: boolean
326 327
    dynamicRoutes: DynamicRoutes | undefined
  } {
J
JJ Kasper 已提交
328 329
    this.customRoutes = this.getCustomRoutes()

330 331 332
    const publicRoutes = fs.existsSync(this.publicDir)
      ? this.generatePublicRoutes()
      : []
J
JJ Kasper 已提交
333

334
    const staticFilesRoute = this.hasStaticDir
335 336 337 338 339 340 341
      ? [
          {
            // It's very important to keep this route's param optional.
            // (but it should support as many params as needed, separated by '/')
            // Otherwise this will lead to a pretty simple DOS attack.
            // See more: https://github.com/zeit/next.js/issues/2617
            match: route('/static/:path*'),
342
            name: 'static catchall',
343 344 345
            fn: async (req, res, params, parsedUrl) => {
              const p = join(this.dir, 'static', ...(params.path || []))
              await this.serveStatic(req, res, p, parsedUrl)
346 347 348
              return {
                finished: true,
              }
349 350 351 352
            },
          } as Route,
        ]
      : []
353

354 355 356 357
    let headers: Route[] = []
    let rewrites: Route[] = []
    let redirects: Route[] = []

358
    const fsRoutes: Route[] = [
T
Tim Neutkens 已提交
359
      {
360
        match: route('/_next/static/:path*'),
361 362
        type: 'route',
        name: '_next/static catchall',
363
        fn: async (req, res, params, parsedUrl) => {
T
Tim Neutkens 已提交
364 365 366
          // The commons folder holds commonschunk files
          // The chunks folder holds dynamic entries
          // The buildId folder holds pages and potentially other assets. As buildId changes per build it can be long-term cached.
367 368

          // make sure to 404 for /_next/static itself
369 370 371 372 373 374
          if (!params.path) {
            await this.render404(req, res, parsedUrl)
            return {
              finished: true,
            }
          }
375

J
Joe Haddad 已提交
376 377 378
          if (
            params.path[0] === CLIENT_STATIC_FILES_RUNTIME ||
            params.path[0] === 'chunks' ||
379 380
            params.path[0] === 'css' ||
            params.path[0] === 'media' ||
J
Joe Haddad 已提交
381 382
            params.path[0] === this.buildId
          ) {
T
Tim Neutkens 已提交
383
            this.setImmutableAssetCacheControl(res)
384
          }
J
Joe Haddad 已提交
385 386 387
          const p = join(
            this.distDir,
            CLIENT_STATIC_FILES_PATH,
388
            ...(params.path || [])
J
Joe Haddad 已提交
389
          )
390
          await this.serveStatic(req, res, p, parsedUrl)
391 392 393
          return {
            finished: true,
          }
394
        },
395
      },
J
JJ Kasper 已提交
396 397
      {
        match: route('/_next/data/:path*'),
398 399
        type: 'route',
        name: '_next/data catchall',
J
JJ Kasper 已提交
400
        fn: async (req, res, params, _parsedUrl) => {
J
JJ Kasper 已提交
401 402 403
          // 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) {
404 405 406 407
            await this.render404(req, res, _parsedUrl)
            return {
              finished: true,
            }
J
JJ Kasper 已提交
408 409 410 411 412 413
          }
          // 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')) {
414 415 416 417
            await this.render404(req, res, _parsedUrl)
            return {
              finished: true,
            }
J
JJ Kasper 已提交
418 419 420 421 422 423 424
          }

          // re-create page's pathname
          const pathname = `/${params.path.join('/')}`
            .replace(/\.json$/, '')
            .replace(/\/index$/, '/')

J
JJ Kasper 已提交
425 426 427 428 429 430
          req.url = pathname
          const parsedUrl = parseUrl(pathname, true)
          await this.render(
            req,
            res,
            pathname,
431
            { ..._parsedUrl.query, _nextDataReq: '1' },
J
JJ Kasper 已提交
432 433
            parsedUrl
          )
434 435 436
          return {
            finished: true,
          }
J
JJ Kasper 已提交
437 438
        },
      },
T
Tim Neutkens 已提交
439
      {
440
        match: route('/_next/:path*'),
441 442
        type: 'route',
        name: '_next catchall',
T
Tim Neutkens 已提交
443
        // This path is needed because `render()` does a check for `/_next` and the calls the routing again
444
        fn: async (req, res, _params, parsedUrl) => {
T
Tim Neutkens 已提交
445
          await this.render404(req, res, parsedUrl)
446 447 448
          return {
            finished: true,
          }
L
Lukáš Huvar 已提交
449 450
        },
      },
451 452
      ...publicRoutes,
      ...staticFilesRoute,
T
Tim Neutkens 已提交
453
    ]
454

J
JJ Kasper 已提交
455 456
    if (this.customRoutes) {
      const getCustomRoute = (
457 458
        r: Rewrite | Redirect | Header,
        type: RouteType
459 460 461 462 463 464 465 466
      ) =>
        ({
          ...r,
          type,
          match: getCustomRouteMatcher(r.source),
          name: type,
          fn: async (req, res, params, parsedUrl) => ({ finished: false }),
        } as Route & Rewrite & Header)
J
JJ Kasper 已提交
467

468
      // Headers come very first
469 470 471 472 473 474 475 476 477 478 479 480 481 482
      headers = this.customRoutes.headers.map(r => {
        const route = getCustomRoute(r, 'header')
        return {
          match: route.match,
          type: route.type,
          name: `${route.type} ${route.source} header route`,
          fn: async (_req, res, _params, _parsedUrl) => {
            for (const header of (route as Header).headers) {
              res.setHeader(header.key, header.value)
            }
            return { finished: false }
          },
        } as Route
      })
J
JJ Kasper 已提交
483

484 485 486 487 488 489 490 491 492 493
      redirects = this.customRoutes.redirects.map(redirect => {
        const route = getCustomRoute(redirect, 'redirect')
        return {
          type: route.type,
          match: route.match,
          statusCode: route.statusCode,
          name: `Redirect route`,
          fn: async (_req, res, params, _parsedUrl) => {
            const { parsedDestination } = prepareDestination(
              route.destination,
494 495
              params,
              true
496 497 498 499 500 501 502 503 504 505 506
            )
            const updatedDestination = formatUrl(parsedDestination)

            res.setHeader('Location', updatedDestination)
            res.statusCode = getRedirectStatus(route as Redirect)

            // Since IE11 doesn't support the 308 header add backwards
            // compatibility using refresh header
            if (res.statusCode === 308) {
              res.setHeader('Refresh', `0;url=${updatedDestination}`)
            }
507

508 509 510 511 512 513 514
            res.end()
            return {
              finished: true,
            }
          },
        } as Route
      })
515

516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535
      rewrites = this.customRoutes.rewrites.map(rewrite => {
        const route = getCustomRoute(rewrite, 'rewrite')
        return {
          check: true,
          type: route.type,
          name: `Rewrite route`,
          match: route.match,
          fn: async (req, res, params, _parsedUrl) => {
            const { newUrl, parsedDestination } = prepareDestination(
              route.destination,
              params
            )

            // external rewrite, proxy it
            if (parsedDestination.protocol) {
              const target = formatUrl(parsedDestination)
              const proxy = new Proxy({
                target,
                changeOrigin: true,
                ignorePath: true,
536
              })
537
              proxy.web(req, res)
538

539 540 541
              proxy.on('error', (err: Error) => {
                console.error(`Error occurred proxying ${target}`, err)
              })
542
              return {
543
                finished: true,
J
JJ Kasper 已提交
544
              }
545 546
            }
            ;(req as any)._nextDidRewrite = true
547

548 549 550 551 552 553 554 555
            return {
              finished: false,
              pathname: newUrl,
              query: parsedDestination.query,
            }
          },
        } as Route
      })
556 557 558 559 560 561 562 563 564 565 566 567
    }

    const catchAllRoute: Route = {
      match: route('/:path*'),
      type: 'route',
      name: 'Catchall render',
      fn: async (req, res, params, parsedUrl) => {
        const { pathname, query } = parsedUrl
        if (!pathname) {
          throw new Error('pathname is undefined')
        }

568
        if (params?.path?.[0] === 'api') {
569 570 571
          const handled = await this.handleApiRequest(
            req as NextApiRequest,
            res as NextApiResponse,
572 573
            pathname!,
            query
574 575 576 577 578 579 580
          )
          if (handled) {
            return { finished: true }
          }
        }

        await this.render(req, res, pathname, query, parsedUrl)
581 582 583 584
        return {
          finished: true,
        }
      },
585
    }
586

587
    const { useFileSystemPublicRoutes } = this.nextConfig
J
Joe Haddad 已提交
588

589 590
    if (useFileSystemPublicRoutes) {
      this.dynamicRoutes = this.getDynamicRoutes()
591
    }
N
nkzawa 已提交
592

593
    return {
594
      headers,
595
      fsRoutes,
596 597
      rewrites,
      redirects,
598
      catchAllRoute,
599
      useFileSystemPublicRoutes,
600 601 602
      dynamicRoutes: this.dynamicRoutes,
      pageChecker: this.hasPage.bind(this),
    }
T
Tim Neutkens 已提交
603 604
  }

605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622
  private async getPagePath(pathname: string) {
    return getPagePath(
      pathname,
      this.distDir,
      this._isLikeServerless,
      this.renderOpts.dev
    )
  }

  protected async hasPage(pathname: string): Promise<boolean> {
    let found = false
    try {
      found = !!(await this.getPagePath(pathname))
    } catch (_) {}

    return found
  }

623 624 625 626 627 628 629 630 631
  protected async _beforeCatchAllRender(
    _req: IncomingMessage,
    _res: ServerResponse,
    _params: Params,
    _parsedUrl: UrlWithParsedQuery
  ) {
    return false
  }

632 633 634
  // Used to build API page in development
  protected async ensureApiPage(pathname: string) {}

L
Lukáš Huvar 已提交
635 636 637 638 639 640
  /**
   * Resolves `API` request, in development builds on demand
   * @param req http request
   * @param res http response
   * @param pathname path of request
   */
J
Joe Haddad 已提交
641
  private async handleApiRequest(
642 643
    req: IncomingMessage,
    res: ServerResponse,
644 645
    pathname: string,
    query: ParsedUrlQuery
J
Joe Haddad 已提交
646
  ) {
647
    let page = pathname
L
Lukáš Huvar 已提交
648
    let params: Params | boolean = false
649
    let pageFound = await this.hasPage(page)
J
JJ Kasper 已提交
650

651
    if (!pageFound && this.dynamicRoutes) {
L
Lukáš Huvar 已提交
652 653
      for (const dynamicRoute of this.dynamicRoutes) {
        params = dynamicRoute.match(pathname)
654
        if (dynamicRoute.page.startsWith('/api') && params) {
655 656
          page = dynamicRoute.page
          pageFound = true
L
Lukáš Huvar 已提交
657 658 659 660 661
          break
        }
      }
    }

662
    if (!pageFound) {
663
      return false
J
JJ Kasper 已提交
664
    }
665 666 667 668 669 670
    // Make sure the page is built before getting the path
    // or else it won't be in the manifest yet
    await this.ensureApiPage(page)

    const builtPagePath = await this.getPagePath(page)
    const pageModule = require(builtPagePath)
671
    query = { ...query, ...params }
J
JJ Kasper 已提交
672

673
    if (!this.renderOpts.dev && this._isLikeServerless) {
674
      if (typeof pageModule.default === 'function') {
675
        prepareServerlessUrl(req, query)
676 677
        await pageModule.default(req, res)
        return true
J
JJ Kasper 已提交
678 679 680
      }
    }

J
Joe Haddad 已提交
681 682 683 684 685
    await apiResolver(
      req,
      res,
      query,
      pageModule,
686
      this.renderOpts.previewProps,
J
Joe Haddad 已提交
687 688
      this.onErrorMiddleware
    )
689
    return true
L
Lukáš Huvar 已提交
690 691
  }

692
  protected generatePublicRoutes(): Route[] {
693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711
    const publicFiles = new Set(
      recursiveReadDirSync(this.publicDir).map(p => p.replace(/\\/g, '/'))
    )

    return [
      {
        match: route('/:path*'),
        name: 'public folder catchall',
        fn: async (req, res, params, parsedUrl) => {
          const path = `/${(params.path || []).join('/')}`

          if (publicFiles.has(path)) {
            await this.serveStatic(
              req,
              res,
              // we need to re-encode it since send decodes it
              join(this.dir, 'public', encodeURIComponent(path)),
              parsedUrl
            )
712 713 714
            return {
              finished: true,
            }
715 716 717 718 719 720 721
          }
          return {
            finished: false,
          }
        },
      } as Route,
    ]
722 723
  }

724
  protected getDynamicRoutes() {
725 726 727
    const dynamicRoutedPages = Object.keys(this.pagesManifest!).filter(
      isDynamicRoute
    )
728 729 730 731
    return getSortedRoutes(dynamicRoutedPages).map(page => ({
      page,
      match: getRouteMatcher(getRouteRegex(page)),
    }))
J
Joe Haddad 已提交
732 733
  }

734 735 736 737 738 739
  private handleCompression(req: IncomingMessage, res: ServerResponse) {
    if (this.compression) {
      this.compression(req, res, () => {})
    }
  }

740
  protected async run(
J
Joe Haddad 已提交
741 742
    req: IncomingMessage,
    res: ServerResponse,
743
    parsedUrl: UrlWithParsedQuery
J
Joe Haddad 已提交
744
  ) {
745 746
    this.handleCompression(req, res)

747
    try {
748 749
      const matched = await this.router.execute(req, res, parsedUrl)
      if (matched) {
750 751 752 753 754 755 756 757
        return
      }
    } catch (err) {
      if (err.code === 'DECODE_FAILED') {
        res.statusCode = 400
        return this.renderError(null, req, res, '/_error', {})
      }
      throw err
758 759
    }

760
    await this.render404(req, res, parsedUrl)
N
nkzawa 已提交
761 762
  }

763
  protected async sendHTML(
J
Joe Haddad 已提交
764 765
    req: IncomingMessage,
    res: ServerResponse,
766
    html: string
J
Joe Haddad 已提交
767
  ) {
T
Tim Neutkens 已提交
768 769
    const { generateEtags, poweredByHeader } = this.renderOpts
    return sendHTML(req, res, html, { generateEtags, poweredByHeader })
770 771
  }

J
Joe Haddad 已提交
772 773 774 775 776
  public async render(
    req: IncomingMessage,
    res: ServerResponse,
    pathname: string,
    query: ParsedUrlQuery = {},
777
    parsedUrl?: UrlWithParsedQuery
J
Joe Haddad 已提交
778
  ): Promise<void> {
779
    const url: any = req.url
780 781 782 783 784

    if (
      url.match(/^\/_next\//) ||
      (this.hasStaticDir && url.match(/^\/static\//))
    ) {
785 786 787
      return this.handleRequest(req, res, parsedUrl)
    }

788
    if (isBlockedPage(pathname)) {
789
      return this.render404(req, res, parsedUrl)
790 791
    }

792
    const html = await this.renderToHTML(req, res, pathname, query)
793 794
    // Request was ended by the user
    if (html === null) {
795 796 797
      return
    }

798
    return this.sendHTML(req, res, html)
N
Naoyuki Kanezawa 已提交
799
  }
N
nkzawa 已提交
800

J
Joe Haddad 已提交
801
  private async findPageComponents(
J
Joe Haddad 已提交
802
    pathname: string,
803 804 805 806 807 808 809 810 811
    query: ParsedUrlQuery = {},
    params: Params | null = null
  ): Promise<FindComponentsResult | null> {
    const paths = [
      // try serving a static AMP version first
      query.amp ? normalizePagePath(pathname) + '.amp' : null,
      pathname,
    ].filter(Boolean)
    for (const pagePath of paths) {
J
JJ Kasper 已提交
812
      try {
813
        const components = await loadComponents(
J
Joe Haddad 已提交
814 815
          this.distDir,
          this.buildId,
816 817
          pagePath!,
          !this.renderOpts.dev && this._isLikeServerless
J
Joe Haddad 已提交
818
        )
819 820 821
        return {
          components,
          query: {
822
            ...(components.getStaticProps
823
              ? { _nextDataReq: query._nextDataReq, amp: query.amp }
824 825 826 827
              : query),
            ...(params || {}),
          },
        }
J
JJ Kasper 已提交
828 829 830 831
      } catch (err) {
        if (err.code !== 'ENOENT') throw err
      }
    }
832
    return null
J
Joe Haddad 已提交
833 834
  }

835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875
  private async getStaticPaths(
    pathname: string
  ): Promise<{
    staticPaths: string[] | undefined
    hasStaticFallback: boolean
  }> {
    // we lazy load the staticPaths to prevent the user
    // from waiting on them for the page to load in dev mode
    let staticPaths: string[] | undefined
    let hasStaticFallback = false

    if (!this.renderOpts.dev) {
      // `staticPaths` is intentionally set to `undefined` as it should've
      // been caught when checking disk data.
      staticPaths = undefined

      // Read whether or not fallback should exist from the manifest.
      hasStaticFallback =
        typeof this.getPrerenderManifest().dynamicRoutes[pathname].fallback ===
        'string'
    } else {
      const __getStaticPaths = async () => {
        const paths = await this.staticPathsWorker!.loadStaticPaths(
          this.distDir,
          this.buildId,
          pathname,
          !this.renderOpts.dev && this._isLikeServerless
        )
        return paths
      }
      ;({ paths: staticPaths, fallback: hasStaticFallback } = (
        await withCoalescedInvoke(__getStaticPaths)(
          `staticPaths-${pathname}`,
          []
        )
      ).value)
    }

    return { staticPaths, hasStaticFallback }
  }

J
Joe Haddad 已提交
876 877 878 879
  private async renderToHTMLWithComponents(
    req: IncomingMessage,
    res: ServerResponse,
    pathname: string,
880
    { components, query }: FindComponentsResult,
881
    opts: RenderOptsPartial
882
  ): Promise<string | null> {
883
    // we need to ensure the status code if /404 is visited directly
884
    if (pathname === '/404') {
885 886 887
      res.statusCode = 404
    }

J
JJ Kasper 已提交
888
    // handle static page
889 890
    if (typeof components.Component === 'string') {
      return components.Component
J
Joe Haddad 已提交
891 892
    }

J
JJ Kasper 已提交
893 894
    // check request state
    const isLikeServerless =
895 896
      typeof components.Component === 'object' &&
      typeof (components.Component as any).renderReqToHTML === 'function'
897 898 899
    const isSSG = !!components.getStaticProps
    const isServerProps = !!components.getServerSideProps
    const hasStaticPaths = !!components.getStaticPaths
900

901 902 903 904 905 906 907 908
    if (isSSG && query.amp) {
      pathname += `.amp`
    }

    if (!query.amp) {
      delete query.amp
    }

909
    // Toggle whether or not this is a Data request
910
    const isDataReq = !!query._nextDataReq
911 912 913 914 915 916 917 918 919 920 921 922
    delete query._nextDataReq

    // Serverless requests need its URL transformed back into the original
    // request path (to emulate lambda behavior in production)
    if (isLikeServerless && isDataReq) {
      let { pathname } = parseUrl(req.url || '', true)
      pathname = !pathname || pathname === '/' ? '/index' : pathname
      req.url = formatUrl({
        pathname: `/_next/data/${this.buildId}${pathname}.json`,
        query,
      })
    }
J
JJ Kasper 已提交
923

924 925 926 927 928 929 930 931
    let previewData: string | false | object | undefined
    let isPreviewMode = false

    if (isServerProps || isSSG) {
      previewData = tryGetPreviewData(req, res, this.renderOpts.previewProps)
      isPreviewMode = previewData !== false
    }

J
JJ Kasper 已提交
932
    // non-spr requests should render like normal
933
    if (!isSSG) {
J
JJ Kasper 已提交
934 935
      // handle serverless
      if (isLikeServerless) {
936
        if (isDataReq) {
937
          const renderResult = await (components.Component as any).renderReqToHTML(
938 939
            req,
            res,
940
            'passthrough'
941 942
          )

943
          sendPayload(
944 945
            res,
            JSON.stringify(renderResult?.renderOpts?.pageData),
946
            'json',
947 948
            !this.renderOpts.dev
              ? {
949 950
                  private: isPreviewMode,
                  stateful: true, // non-SSG data request
951 952
                }
              : undefined
953 954 955
          )
          return null
        }
956
        prepareServerlessUrl(req, query)
957
        return (components.Component as any).renderReqToHTML(req, res)
J
JJ Kasper 已提交
958 959
      }

960 961
      if (isDataReq && isServerProps) {
        const props = await renderToHTML(req, res, pathname, query, {
962
          ...components,
963 964 965
          ...opts,
          isDataReq,
        })
966 967 968
        sendPayload(
          res,
          JSON.stringify(props),
969
          'json',
970 971
          !this.renderOpts.dev
            ? {
972 973
                private: isPreviewMode,
                stateful: true, // GSSP data request
974 975 976
              }
            : undefined
        )
977 978 979
        return null
      }

980
      const html = await renderToHTML(req, res, pathname, query, {
981
        ...components,
J
JJ Kasper 已提交
982 983 984
        ...opts,
      })

985 986
      if (html && isServerProps) {
        sendPayload(res, html, 'html', {
987
          private: isPreviewMode,
988
          stateful: true, // GSSP request
989
        })
990
        return null
991 992 993 994
      }

      return html
    }
J
Joe Haddad 已提交
995

J
JJ Kasper 已提交
996
    // Compute the SPR cache key
997 998 999
    const urlPathname = `${parseUrl(req.url || '').pathname!}${
      query.amp ? '.amp' : ''
    }`
J
Joe Haddad 已提交
1000 1001
    const ssgCacheKey = isPreviewMode
      ? `__` + nanoid() // Preview mode uses a throw away key to not coalesce preview invokes
1002
      : urlPathname
J
JJ Kasper 已提交
1003 1004

    // Complete the response with cached data if its present
J
Joe Haddad 已提交
1005 1006 1007 1008
    const cachedData = isPreviewMode
      ? // Preview data bypasses the cache
        undefined
      : await getSprCache(ssgCacheKey)
J
JJ Kasper 已提交
1009
    if (cachedData) {
1010
      const data = isDataReq
J
JJ Kasper 已提交
1011 1012 1013
        ? JSON.stringify(cachedData.pageData)
        : cachedData.html

1014
      sendPayload(
J
JJ Kasper 已提交
1015 1016
        res,
        data,
1017 1018 1019 1020 1021 1022 1023 1024 1025 1026
        isDataReq ? 'json' : 'html',
        !this.renderOpts.dev
          ? {
              private: isPreviewMode,
              stateful: false, // GSP response
              revalidate:
                cachedData.curRevalidate !== undefined
                  ? cachedData.curRevalidate
                  : /* default to minimum revalidate (this should be an invariant) */ 1,
            }
1027
          : undefined
J
JJ Kasper 已提交
1028 1029 1030 1031 1032 1033
      )

      // Stop the request chain here if the data we sent was up-to-date
      if (!cachedData.isStale) {
        return null
      }
J
JJ Kasper 已提交
1034
    }
J
Joe Haddad 已提交
1035

J
JJ Kasper 已提交
1036 1037 1038 1039
    // If we're here, that means data is missing or it's stale.

    const doRender = withCoalescedInvoke(async function(): Promise<{
      html: string | null
1040
      pageData: any
J
JJ Kasper 已提交
1041 1042
      sprRevalidate: number | false
    }> {
1043
      let pageData: any
J
JJ Kasper 已提交
1044 1045 1046 1047 1048 1049
      let html: string | null
      let sprRevalidate: number | false

      let renderResult
      // handle serverless
      if (isLikeServerless) {
1050
        renderResult = await (components.Component as any).renderReqToHTML(
1051 1052
          req,
          res,
1053
          'passthrough'
1054
        )
J
JJ Kasper 已提交
1055 1056

        html = renderResult.html
1057
        pageData = renderResult.renderOpts.pageData
J
JJ Kasper 已提交
1058 1059
        sprRevalidate = renderResult.renderOpts.revalidate
      } else {
1060
        const renderOpts: RenderOpts = {
1061
          ...components,
J
JJ Kasper 已提交
1062 1063 1064 1065 1066
          ...opts,
        }
        renderResult = await renderToHTML(req, res, pathname, query, renderOpts)

        html = renderResult
1067 1068 1069
        // TODO: change this to a different passing mechanism
        pageData = (renderOpts as any).pageData
        sprRevalidate = (renderOpts as any).revalidate
J
JJ Kasper 已提交
1070 1071
      }

1072
      return { html, pageData, sprRevalidate }
1073
    })
J
JJ Kasper 已提交
1074

1075
    const isProduction = !this.renderOpts.dev
J
Joe Haddad 已提交
1076
    const isDynamicPathname = isDynamicRoute(pathname)
1077
    const didRespond = isResSent(res)
1078

1079 1080 1081
    const { staticPaths, hasStaticFallback } = hasStaticPaths
      ? await this.getStaticPaths(pathname)
      : { staticPaths: undefined, hasStaticFallback: false }
1082

1083 1084 1085 1086 1087 1088 1089 1090 1091 1092
    // const isForcedBlocking =
    //   req.headers['X-Prerender-Bypass-Mode'] !== 'Blocking'

    // When we did not respond from cache, we need to choose to block on
    // rendering or return a skeleton.
    //
    // * Data requests always block.
    //
    // * Preview mode toggles all pages to be resolved in a blocking manner.
    //
1093
    // * Non-dynamic pages should block (though this is an impossible
1094 1095
    //   case in production).
    //
1096 1097
    // * Dynamic pages should return their skeleton if not defined in
    //   getStaticPaths, then finish the data request on the client-side.
1098
    //
J
Joe Haddad 已提交
1099
    if (
1100
      !didRespond &&
J
Joe Haddad 已提交
1101
      !isDataReq &&
1102 1103
      !isPreviewMode &&
      isDynamicPathname &&
1104 1105 1106
      // Development should trigger fallback when the path is not in
      // `getStaticPaths`
      (isProduction || !staticPaths || !staticPaths.includes(urlPathname))
J
Joe Haddad 已提交
1107
    ) {
1108 1109 1110 1111 1112 1113 1114
      if (
        // In development, fall through to render to handle missing
        // getStaticPaths.
        (isProduction || staticPaths) &&
        // When fallback isn't present, abort this render so we 404
        !hasStaticFallback
      ) {
1115
        throw new NoFallbackError()
1116 1117
      }

1118
      let html: string
1119

1120 1121
      // Production already emitted the fallback as static HTML.
      if (isProduction) {
1122
        html = await getFallback(pathname)
1123 1124 1125
      }
      // We need to generate the fallback on-demand for development.
      else {
1126 1127
        query.__nextFallback = 'true'
        if (isLikeServerless) {
1128
          prepareServerlessUrl(req, query)
1129 1130 1131 1132 1133 1134
          const renderResult = await (components.Component as any).renderReqToHTML(
            req,
            res,
            'passthrough'
          )
          html = renderResult.html
1135 1136
        } else {
          html = (await renderToHTML(req, res, pathname, query, {
1137
            ...components,
1138 1139 1140 1141 1142
            ...opts,
          })) as string
        }
      }

1143
      sendPayload(res, html, 'html')
1144 1145
    }

1146 1147 1148 1149 1150
    const {
      isOrigin,
      value: { html, pageData, sprRevalidate },
    } = await doRender(ssgCacheKey, [])
    if (!isResSent(res)) {
1151
      sendPayload(
1152 1153
        res,
        isDataReq ? JSON.stringify(pageData) : html,
1154
        isDataReq ? 'json' : 'html',
1155
        !this.renderOpts.dev
1156 1157 1158 1159 1160
          ? {
              private: isPreviewMode,
              stateful: false, // GSP response
              revalidate: sprRevalidate,
            }
1161
          : undefined
1162 1163
      )
    }
J
JJ Kasper 已提交
1164

1165 1166 1167 1168 1169
    // Update the SPR cache if the head request
    if (isOrigin) {
      // Preview mode should not be stored in cache
      if (!isPreviewMode) {
        await setSprCache(ssgCacheKey, { html: html!, pageData }, sprRevalidate)
J
JJ Kasper 已提交
1170
      }
1171 1172 1173
    }

    return null
1174 1175
  }

1176
  public async renderToHTML(
J
Joe Haddad 已提交
1177 1178 1179
    req: IncomingMessage,
    res: ServerResponse,
    pathname: string,
1180
    query: ParsedUrlQuery = {}
J
Joe Haddad 已提交
1181
  ): Promise<string | null> {
1182 1183 1184
    try {
      const result = await this.findPageComponents(pathname, query)
      if (result) {
1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196
        try {
          return await this.renderToHTMLWithComponents(
            req,
            res,
            pathname,
            result,
            { ...this.renderOpts }
          )
        } catch (err) {
          if (!(err instanceof NoFallbackError)) {
            throw err
          }
1197
        }
1198
      }
J
Joe Haddad 已提交
1199

1200 1201 1202 1203 1204 1205
      if (this.dynamicRoutes) {
        for (const dynamicRoute of this.dynamicRoutes) {
          const params = dynamicRoute.match(pathname)
          if (!params) {
            continue
          }
J
Joe Haddad 已提交
1206

1207 1208 1209 1210 1211 1212
          const result = await this.findPageComponents(
            dynamicRoute.page,
            query,
            params
          )
          if (result) {
1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224
            try {
              return await this.renderToHTMLWithComponents(
                req,
                res,
                dynamicRoute.page,
                result,
                { ...this.renderOpts, params }
              )
            } catch (err) {
              if (!(err instanceof NoFallbackError)) {
                throw err
              }
1225
            }
J
Joe Haddad 已提交
1226 1227
          }
        }
1228 1229 1230 1231 1232 1233 1234 1235 1236
      }
    } catch (err) {
      this.logError(err)
      res.statusCode = 500
      return await this.renderErrorToHTML(err, req, res, pathname, query)
    }

    res.statusCode = 404
    return await this.renderErrorToHTML(null, req, res, pathname, query)
N
Naoyuki Kanezawa 已提交
1237 1238
  }

J
Joe Haddad 已提交
1239 1240 1241 1242 1243
  public async renderError(
    err: Error | null,
    req: IncomingMessage,
    res: ServerResponse,
    pathname: string,
1244
    query: ParsedUrlQuery = {}
J
Joe Haddad 已提交
1245 1246 1247
  ): Promise<void> {
    res.setHeader(
      'Cache-Control',
1248
      'no-cache, no-store, max-age=0, must-revalidate'
J
Joe Haddad 已提交
1249
    )
N
Naoyuki Kanezawa 已提交
1250
    const html = await this.renderErrorToHTML(err, req, res, pathname, query)
1251
    if (html === null) {
1252 1253
      return
    }
1254
    return this.sendHTML(req, res, html)
N
nkzawa 已提交
1255 1256
  }

J
Joe Haddad 已提交
1257 1258 1259 1260 1261
  public async renderErrorToHTML(
    err: Error | null,
    req: IncomingMessage,
    res: ServerResponse,
    _pathname: string,
1262
    query: ParsedUrlQuery = {}
J
Joe Haddad 已提交
1263
  ) {
1264
    let result: null | FindComponentsResult = null
1265

1266 1267 1268
    const is404 = res.statusCode === 404
    let using404Page = false

1269
    // use static 404 page if available and is 404 response
1270
    if (is404) {
1271 1272
      result = await this.findPageComponents('/404')
      using404Page = result !== null
1273 1274 1275 1276 1277 1278
    }

    if (!result) {
      result = await this.findPageComponents('/_error', query)
    }

1279
    let html: string | null
1280
    try {
1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294
      try {
        html = await this.renderToHTMLWithComponents(
          req,
          res,
          using404Page ? '/404' : '/_error',
          result!,
          {
            ...this.renderOpts,
            err,
          }
        )
      } catch (err) {
        if (err instanceof NoFallbackError) {
          throw new Error('invariant: failed to render error page')
1295
        }
1296
        throw err
1297
      }
1298 1299 1300 1301 1302 1303
    } catch (err) {
      console.error(err)
      res.statusCode = 500
      html = 'Internal Server Error'
    }
    return html
N
Naoyuki Kanezawa 已提交
1304 1305
  }

J
Joe Haddad 已提交
1306 1307 1308
  public async render404(
    req: IncomingMessage,
    res: ServerResponse,
1309
    parsedUrl?: UrlWithParsedQuery
J
Joe Haddad 已提交
1310
  ): Promise<void> {
1311 1312
    const url: any = req.url
    const { pathname, query } = parsedUrl ? parsedUrl : parseUrl(url, true)
N
Naoyuki Kanezawa 已提交
1313
    res.statusCode = 404
1314
    return this.renderError(null, req, res, pathname!, query)
N
Naoyuki Kanezawa 已提交
1315
  }
N
Naoyuki Kanezawa 已提交
1316

J
Joe Haddad 已提交
1317 1318 1319 1320
  public async serveStatic(
    req: IncomingMessage,
    res: ServerResponse,
    path: string,
1321
    parsedUrl?: UrlWithParsedQuery
J
Joe Haddad 已提交
1322
  ): Promise<void> {
A
Arunoda Susiripala 已提交
1323
    if (!this.isServeableUrl(path)) {
1324
      return this.render404(req, res, parsedUrl)
A
Arunoda Susiripala 已提交
1325 1326
    }

1327 1328 1329 1330 1331 1332
    if (!(req.method === 'GET' || req.method === 'HEAD')) {
      res.statusCode = 405
      res.setHeader('Allow', ['GET', 'HEAD'])
      return this.renderError(null, req, res, path)
    }

N
Naoyuki Kanezawa 已提交
1333
    try {
1334
      await serveStatic(req, res, path)
N
Naoyuki Kanezawa 已提交
1335
    } catch (err) {
T
Tim Neutkens 已提交
1336
      if (err.code === 'ENOENT' || err.statusCode === 404) {
1337
        this.render404(req, res, parsedUrl)
1338 1339 1340
      } else if (err.statusCode === 412) {
        res.statusCode = 412
        return this.renderError(err, req, res, path)
N
Naoyuki Kanezawa 已提交
1341 1342 1343 1344 1345 1346
      } else {
        throw err
      }
    }
  }

1347
  private isServeableUrl(path: string): boolean {
A
Arunoda Susiripala 已提交
1348 1349
    const resolved = resolve(path)
    if (
1350
      resolved.indexOf(join(this.distDir) + sep) !== 0 &&
1351 1352
      resolved.indexOf(join(this.dir, 'static') + sep) !== 0 &&
      resolved.indexOf(join(this.dir, 'public') + sep) !== 0
A
Arunoda Susiripala 已提交
1353 1354 1355 1356 1357 1358 1359 1360
    ) {
      // Seems like the user is trying to traverse the filesystem.
      return false
    }

    return true
  }

1361
  protected readBuildId(): string {
1362 1363 1364 1365 1366
    const buildIdFile = join(this.distDir, BUILD_ID_FILE)
    try {
      return fs.readFileSync(buildIdFile, 'utf8').trim()
    } catch (err) {
      if (!fs.existsSync(buildIdFile)) {
J
Joe Haddad 已提交
1367
        throw new Error(
1368
          `Could not find a valid build in the '${this.distDir}' directory! Try building your app with 'next build' before starting the server.`
J
Joe Haddad 已提交
1369
        )
1370 1371 1372
      }

      throw err
1373
    }
1374
  }
1375 1376 1377 1378

  private get _isLikeServerless(): boolean {
    return isTargetLikeServerless(this.nextConfig.target)
  }
1379
}
1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391

function prepareServerlessUrl(req: IncomingMessage, query: ParsedUrlQuery) {
  const curUrl = parseUrl(req.url!, true)
  req.url = formatUrl({
    ...curUrl,
    search: undefined,
    query: {
      ...curUrl.query,
      ...query,
    },
  })
}
1392 1393

class NoFallbackError extends Error {}