next-server.ts 37.3 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'
J
Joe Haddad 已提交
44
import { 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 55
import { sendHTML } from './send-html'
import { serveStatic } from './serve-static'
56
import {
J
Joe Haddad 已提交
57
  getFallback,
58 59 60 61
  getSprCache,
  initializeSprCache,
  setSprCache,
} from './spr-cache'
62
import { isBlockedPage } from './utils'
J
JJ Kasper 已提交
63 64

const getCustomRouteMatcher = pathMatch(true)
65 66 67

type NextConfig = any

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

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

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

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

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

147 148
    // Only serverRuntimeConfig needs the default
    // publicRuntimeConfig gets it's default in client/index.js
J
Joe Haddad 已提交
149 150 151 152 153
    const {
      serverRuntimeConfig = {},
      publicRuntimeConfig,
      assetPrefix,
      generateEtags,
154
      compress,
J
Joe Haddad 已提交
155
    } = this.nextConfig
156

T
Tim Neutkens 已提交
157
    this.buildId = this.readBuildId()
158

159
    this.renderOpts = {
T
Tim Neutkens 已提交
160
      poweredByHeader: this.nextConfig.poweredByHeader,
161
      canonicalBase: this.nextConfig.amp.canonicalBase,
162 163
      documentMiddlewareEnabled: this.nextConfig.experimental
        .documentMiddleware,
J
Joe Haddad 已提交
164
      hasCssMode: this.nextConfig.experimental.css,
165
      staticMarkup,
166
      buildId: this.buildId,
167
      generateEtags,
168
    }
N
Naoyuki Kanezawa 已提交
169

170 171
    // Only the `publicRuntimeConfig` key is exposed to the client side
    // It'll be rendered as part of __NEXT_DATA__ on the client side
172
    if (Object.keys(publicRuntimeConfig).length > 0) {
173
      this.renderOpts.runtimeConfig = publicRuntimeConfig
174 175
    }

176
    if (compress && this.nextConfig.target === 'server') {
177 178 179
      this.compression = compression() as Middleware
    }

180
    // Initialize next/config with the environment configuration
181 182 183 184
    envConfig.setConfig({
      serverRuntimeConfig,
      publicRuntimeConfig,
    })
185

186 187 188 189 190 191 192 193 194 195
    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 已提交
196
    this.router = new Router(this.generateRoutes())
197
    this.setAssetPrefix(assetPrefix)
J
JJ Kasper 已提交
198

199 200 201
    // call init-server middleware, this is also handled
    // individually in serverless bundles when deployed
    if (!dev && this.nextConfig.experimental.plugins) {
202 203
      const initServer = require(join(this.serverBuildDir, 'init-server.js'))
        .default
204
      this.onErrorMiddleware = require(join(
205
        this.serverBuildDir,
206 207 208 209 210
        'on-error-server.js'
      )).default
      initServer()
    }

J
JJ Kasper 已提交
211 212 213 214 215 216 217 218 219 220 221 222
    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 已提交
223
  }
N
nkzawa 已提交
224

225
  protected currentPhase(): string {
226
    return PHASE_PRODUCTION_SERVER
227 228
  }

229 230 231 232
  private logError(err: Error): void {
    if (this.onErrorMiddleware) {
      this.onErrorMiddleware({ err })
    }
233 234
    if (this.quiet) return
    // tslint:disable-next-line
235
    console.error(err)
236 237
  }

238
  private async handleRequest(
J
Joe Haddad 已提交
239 240
    req: IncomingMessage,
    res: ServerResponse,
241
    parsedUrl?: UrlWithParsedQuery
J
Joe Haddad 已提交
242
  ): Promise<void> {
243
    // Parse url if parsedUrl not provided
244
    if (!parsedUrl || typeof parsedUrl !== 'object') {
245 246
      const url: any = req.url
      parsedUrl = parseUrl(url, true)
247
    }
248

249 250 251
    // 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 已提交
252
    }
253

T
Tim Neutkens 已提交
254 255 256 257 258 259 260 261 262 263
    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, '')
    }

264
    res.statusCode = 200
265 266 267
    try {
      return await this.run(req, res, parsedUrl)
    } catch (err) {
J
Joe Haddad 已提交
268 269 270
      this.logError(err)
      res.statusCode = 500
      res.end('Internal Server Error')
271
    }
272 273
  }

274
  public getRequestHandler() {
275
    return this.handleRequest.bind(this)
N
nkzawa 已提交
276 277
  }

278
  public setAssetPrefix(prefix?: string) {
279
    this.renderOpts.assetPrefix = prefix ? prefix.replace(/\/$/, '') : ''
280 281
  }

282
  // Backwards compatibility
283
  public async prepare(): Promise<void> {}
N
nkzawa 已提交
284

T
Tim Neutkens 已提交
285
  // Backwards compatibility
286
  protected async close(): Promise<void> {}
T
Tim Neutkens 已提交
287

288
  protected setImmutableAssetCacheControl(res: ServerResponse) {
T
Tim Neutkens 已提交
289
    res.setHeader('Cache-Control', 'public, max-age=31536000, immutable')
N
nkzawa 已提交
290 291
  }

J
JJ Kasper 已提交
292 293 294 295
  protected getCustomRoutes() {
    return require(join(this.distDir, ROUTES_MANIFEST))
  }

296 297 298 299
  private _cachedPreviewManifest: PrerenderManifest | undefined
  protected getPrerenderManifest(): PrerenderManifest {
    if (this._cachedPreviewManifest) {
      return this._cachedPreviewManifest
J
Joe Haddad 已提交
300
    }
301 302 303 304 305 306
    const manifest = require(join(this.distDir, PRERENDER_MANIFEST))
    return (this._cachedPreviewManifest = manifest)
  }

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

309
  protected generateRoutes(): {
310 311
    headers: Route[]
    rewrites: Route[]
312
    fsRoutes: Route[]
313
    redirects: Route[]
314 315
    catchAllRoute: Route
    pageChecker: PageChecker
316
    useFileSystemPublicRoutes: boolean
317 318
    dynamicRoutes: DynamicRoutes | undefined
  } {
J
JJ Kasper 已提交
319 320
    this.customRoutes = this.getCustomRoutes()

321 322 323
    const publicRoutes = fs.existsSync(this.publicDir)
      ? this.generatePublicRoutes()
      : []
J
JJ Kasper 已提交
324

325
    const staticFilesRoute = this.hasStaticDir
326 327 328 329 330 331 332
      ? [
          {
            // 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*'),
333
            name: 'static catchall',
334 335 336
            fn: async (req, res, params, parsedUrl) => {
              const p = join(this.dir, 'static', ...(params.path || []))
              await this.serveStatic(req, res, p, parsedUrl)
337 338 339
              return {
                finished: true,
              }
340 341 342 343
            },
          } as Route,
        ]
      : []
344

345 346 347 348
    let headers: Route[] = []
    let rewrites: Route[] = []
    let redirects: Route[] = []

349
    const fsRoutes: Route[] = [
T
Tim Neutkens 已提交
350
      {
351
        match: route('/_next/static/:path*'),
352 353
        type: 'route',
        name: '_next/static catchall',
354
        fn: async (req, res, params, parsedUrl) => {
T
Tim Neutkens 已提交
355 356 357
          // 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.
358 359

          // make sure to 404 for /_next/static itself
360 361 362 363 364 365
          if (!params.path) {
            await this.render404(req, res, parsedUrl)
            return {
              finished: true,
            }
          }
366

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

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

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

J
JJ Kasper 已提交
446 447
    if (this.customRoutes) {
      const getCustomRoute = (
448 449
        r: Rewrite | Redirect | Header,
        type: RouteType
450 451 452 453 454 455 456 457
      ) =>
        ({
          ...r,
          type,
          match: getCustomRouteMatcher(r.source),
          name: type,
          fn: async (req, res, params, parsedUrl) => ({ finished: false }),
        } as Route & Rewrite & Header)
J
JJ Kasper 已提交
458

459
      // Headers come very first
460 461 462 463 464 465 466 467 468 469 470 471 472 473
      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 已提交
474

475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496
      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,
              params
            )
            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}`)
            }
497

498 499 500 501 502 503 504
            res.end()
            return {
              finished: true,
            }
          },
        } as Route
      })
505

506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525
      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,
526
              })
527
              proxy.web(req, res)
528

529 530 531
              proxy.on('error', (err: Error) => {
                console.error(`Error occurred proxying ${target}`, err)
              })
532
              return {
533
                finished: true,
J
JJ Kasper 已提交
534
              }
535 536
            }
            ;(req as any)._nextDidRewrite = true
537

538 539 540 541 542 543 544 545
            return {
              finished: false,
              pathname: newUrl,
              query: parsedDestination.query,
            }
          },
        } as Route
      })
546 547 548 549 550 551 552 553 554 555 556 557
    }

    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')
        }

558
        if (params?.path?.[0] === 'api') {
559 560 561
          const handled = await this.handleApiRequest(
            req as NextApiRequest,
            res as NextApiResponse,
562 563
            pathname!,
            query
564 565 566 567 568 569 570
          )
          if (handled) {
            return { finished: true }
          }
        }

        await this.render(req, res, pathname, query, parsedUrl)
571 572 573 574
        return {
          finished: true,
        }
      },
575
    }
576

577
    const { useFileSystemPublicRoutes } = this.nextConfig
J
Joe Haddad 已提交
578

579 580
    if (useFileSystemPublicRoutes) {
      this.dynamicRoutes = this.getDynamicRoutes()
581
    }
N
nkzawa 已提交
582

583
    return {
584
      headers,
585
      fsRoutes,
586 587
      rewrites,
      redirects,
588
      catchAllRoute,
589
      useFileSystemPublicRoutes,
590 591 592
      dynamicRoutes: this.dynamicRoutes,
      pageChecker: this.hasPage.bind(this),
    }
T
Tim Neutkens 已提交
593 594
  }

595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612
  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
  }

613 614 615 616 617 618 619 620 621
  protected async _beforeCatchAllRender(
    _req: IncomingMessage,
    _res: ServerResponse,
    _params: Params,
    _parsedUrl: UrlWithParsedQuery
  ) {
    return false
  }

622 623 624
  // Used to build API page in development
  protected async ensureApiPage(pathname: string) {}

L
Lukáš Huvar 已提交
625 626 627 628 629 630
  /**
   * Resolves `API` request, in development builds on demand
   * @param req http request
   * @param res http response
   * @param pathname path of request
   */
J
Joe Haddad 已提交
631
  private async handleApiRequest(
632 633
    req: IncomingMessage,
    res: ServerResponse,
634 635
    pathname: string,
    query: ParsedUrlQuery
J
Joe Haddad 已提交
636
  ) {
637
    let page = pathname
L
Lukáš Huvar 已提交
638
    let params: Params | boolean = false
639
    let pageFound = await this.hasPage(page)
J
JJ Kasper 已提交
640

641
    if (!pageFound && this.dynamicRoutes) {
L
Lukáš Huvar 已提交
642 643
      for (const dynamicRoute of this.dynamicRoutes) {
        params = dynamicRoute.match(pathname)
644
        if (dynamicRoute.page.startsWith('/api') && params) {
645 646
          page = dynamicRoute.page
          pageFound = true
L
Lukáš Huvar 已提交
647 648 649 650 651
          break
        }
      }
    }

652
    if (!pageFound) {
653
      return false
J
JJ Kasper 已提交
654
    }
655 656 657 658 659 660
    // 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)
661
    query = { ...query, ...params }
J
JJ Kasper 已提交
662

663
    if (!this.renderOpts.dev && this._isLikeServerless) {
664
      if (typeof pageModule.default === 'function') {
665
        this.prepareServerlessUrl(req, query)
666 667
        await pageModule.default(req, res)
        return true
J
JJ Kasper 已提交
668 669 670
      }
    }

J
Joe Haddad 已提交
671 672 673 674 675 676 677 678 679
    const previewProps = this.getPreviewProps()
    await apiResolver(
      req,
      res,
      query,
      pageModule,
      { ...previewProps },
      this.onErrorMiddleware
    )
680
    return true
L
Lukáš Huvar 已提交
681 682
  }

683
  protected generatePublicRoutes(): Route[] {
684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702
    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
            )
703 704 705
            return {
              finished: true,
            }
706 707 708 709 710 711 712
          }
          return {
            finished: false,
          }
        },
      } as Route,
    ]
713 714
  }

715
  protected getDynamicRoutes() {
716 717 718
    const dynamicRoutedPages = Object.keys(this.pagesManifest!).filter(
      isDynamicRoute
    )
719 720 721 722
    return getSortedRoutes(dynamicRoutedPages).map(page => ({
      page,
      match: getRouteMatcher(getRouteRegex(page)),
    }))
J
Joe Haddad 已提交
723 724
  }

725 726 727 728 729 730
  private handleCompression(req: IncomingMessage, res: ServerResponse) {
    if (this.compression) {
      this.compression(req, res, () => {})
    }
  }

731
  protected async run(
J
Joe Haddad 已提交
732 733
    req: IncomingMessage,
    res: ServerResponse,
734
    parsedUrl: UrlWithParsedQuery
J
Joe Haddad 已提交
735
  ) {
736 737
    this.handleCompression(req, res)

738
    try {
739 740
      const matched = await this.router.execute(req, res, parsedUrl)
      if (matched) {
741 742 743 744 745 746 747 748
        return
      }
    } catch (err) {
      if (err.code === 'DECODE_FAILED') {
        res.statusCode = 400
        return this.renderError(null, req, res, '/_error', {})
      }
      throw err
749 750
    }

751
    await this.render404(req, res, parsedUrl)
N
nkzawa 已提交
752 753
  }

754
  protected async sendHTML(
J
Joe Haddad 已提交
755 756
    req: IncomingMessage,
    res: ServerResponse,
757
    html: string
J
Joe Haddad 已提交
758
  ) {
T
Tim Neutkens 已提交
759 760
    const { generateEtags, poweredByHeader } = this.renderOpts
    return sendHTML(req, res, html, { generateEtags, poweredByHeader })
761 762
  }

J
Joe Haddad 已提交
763 764 765 766 767
  public async render(
    req: IncomingMessage,
    res: ServerResponse,
    pathname: string,
    query: ParsedUrlQuery = {},
768
    parsedUrl?: UrlWithParsedQuery
J
Joe Haddad 已提交
769
  ): Promise<void> {
770
    const url: any = req.url
771 772 773 774 775

    if (
      url.match(/^\/_next\//) ||
      (this.hasStaticDir && url.match(/^\/static\//))
    ) {
776 777 778
      return this.handleRequest(req, res, parsedUrl)
    }

779
    if (isBlockedPage(pathname)) {
780
      return this.render404(req, res, parsedUrl)
781 782
    }

T
Tim Neutkens 已提交
783
    const html = await this.renderToHTML(req, res, pathname, query, {})
784 785
    // Request was ended by the user
    if (html === null) {
786 787 788
      return
    }

789
    return this.sendHTML(req, res, html)
N
Naoyuki Kanezawa 已提交
790
  }
N
nkzawa 已提交
791

J
Joe Haddad 已提交
792
  private async findPageComponents(
J
Joe Haddad 已提交
793
    pathname: string,
794 795 796 797 798 799 800 801 802
    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 已提交
803
      try {
804
        const components = await loadComponents(
J
Joe Haddad 已提交
805 806
          this.distDir,
          this.buildId,
807 808
          pagePath!,
          !this.renderOpts.dev && this._isLikeServerless
J
Joe Haddad 已提交
809
        )
810 811 812 813 814 815 816 817 818
        return {
          components,
          query: {
            ...(components.unstable_getStaticProps
              ? { _nextDataReq: query._nextDataReq }
              : query),
            ...(params || {}),
          },
        }
J
JJ Kasper 已提交
819 820 821 822
      } catch (err) {
        if (err.code !== 'ENOENT') throw err
      }
    }
823
    return null
J
Joe Haddad 已提交
824 825
  }

J
JJ Kasper 已提交
826 827 828 829
  private __sendPayload(
    res: ServerResponse,
    payload: any,
    type: string,
830
    options?: { revalidate: number | false; private: boolean }
J
JJ Kasper 已提交
831
  ) {
J
JJ Kasper 已提交
832
    // TODO: ETag? Cache-Control headers? Next-specific headers?
J
JJ Kasper 已提交
833
    res.setHeader('Content-Type', type)
J
JJ Kasper 已提交
834
    res.setHeader('Content-Length', Buffer.byteLength(payload))
J
Joe Haddad 已提交
835
    if (!this.renderOpts.dev) {
836
      if (options?.private) {
J
Joe Haddad 已提交
837 838
        res.setHeader(
          'Cache-Control',
839 840 841 842 843 844
          `private, no-cache, no-store, max-age=0, must-revalidate`
        )
      } else if (options?.revalidate) {
        res.setHeader(
          'Cache-Control',
          options.revalidate < 0
845
            ? `no-cache, no-store, must-revalidate`
846
            : `s-maxage=${options.revalidate}, stale-while-revalidate`
J
Joe Haddad 已提交
847
        )
848
      } else if (options?.revalidate === false) {
J
Joe Haddad 已提交
849 850 851 852 853
        res.setHeader(
          'Cache-Control',
          `s-maxage=31536000, stale-while-revalidate`
        )
      }
J
JJ Kasper 已提交
854
    }
J
JJ Kasper 已提交
855 856 857
    res.end(payload)
  }

858 859 860 861 862 863 864 865 866 867 868 869
  private prepareServerlessUrl(req: IncomingMessage, query: ParsedUrlQuery) {
    const curUrl = parseUrl(req.url!, true)
    req.url = formatUrl({
      ...curUrl,
      search: undefined,
      query: {
        ...curUrl.query,
        ...query,
      },
    })
  }

J
Joe Haddad 已提交
870 871 872 873
  private async renderToHTMLWithComponents(
    req: IncomingMessage,
    res: ServerResponse,
    pathname: string,
874
    { components, query }: FindComponentsResult,
875
    opts: any
876
  ): Promise<string | false | null> {
877
    // we need to ensure the status code if /404 is visited directly
878
    if (pathname === '/404') {
879 880 881
      res.statusCode = 404
    }

J
JJ Kasper 已提交
882
    // handle static page
883 884
    if (typeof components.Component === 'string') {
      return components.Component
J
Joe Haddad 已提交
885 886
    }

J
JJ Kasper 已提交
887 888
    // check request state
    const isLikeServerless =
889 890 891
      typeof components.Component === 'object' &&
      typeof (components.Component as any).renderReqToHTML === 'function'
    const isSSG = !!components.unstable_getStaticProps
892
    const isServerProps = !!components.unstable_getServerSideProps
893
    const hasStaticPaths = !!components.unstable_getStaticPaths
894 895 896 897 898 899 900 901 902 903 904 905 906 907 908

    // Toggle whether or not this is a Data request
    const isDataReq = query._nextDataReq
    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 已提交
909 910

    // non-spr requests should render like normal
911
    if (!isSSG) {
J
JJ Kasper 已提交
912 913
      // handle serverless
      if (isLikeServerless) {
914
        if (isDataReq) {
915
          const renderResult = await (components.Component as any).renderReqToHTML(
916 917 918 919 920 921 922 923 924
            req,
            res,
            true
          )

          this.__sendPayload(
            res,
            JSON.stringify(renderResult?.renderOpts?.pageData),
            'application/json',
925 926 927 928
            {
              revalidate: -1,
              private: false, // Leave to user-land caching
            }
929 930 931
          )
          return null
        }
932
        this.prepareServerlessUrl(req, query)
933
        return (components.Component as any).renderReqToHTML(req, res)
J
JJ Kasper 已提交
934 935
      }

936 937
      if (isDataReq && isServerProps) {
        const props = await renderToHTML(req, res, pathname, query, {
938
          ...components,
939 940 941
          ...opts,
          isDataReq,
        })
942 943 944 945
        this.__sendPayload(res, JSON.stringify(props), 'application/json', {
          revalidate: -1,
          private: false, // Leave to user-land caching
        })
946 947 948
        return null
      }

J
JJ Kasper 已提交
949
      return renderToHTML(req, res, pathname, query, {
950
        ...components,
J
JJ Kasper 已提交
951 952 953 954
        ...opts,
      })
    }

J
Joe Haddad 已提交
955 956 957 958
    const previewProps = this.getPreviewProps()
    const previewData = tryGetPreviewData(req, res, { ...previewProps })
    const isPreviewMode = previewData !== false

J
JJ Kasper 已提交
959
    // Compute the SPR cache key
960
    const urlPathname = parseUrl(req.url || '').pathname!
J
Joe Haddad 已提交
961 962
    const ssgCacheKey = isPreviewMode
      ? `__` + nanoid() // Preview mode uses a throw away key to not coalesce preview invokes
963
      : urlPathname
J
JJ Kasper 已提交
964 965

    // Complete the response with cached data if its present
J
Joe Haddad 已提交
966 967 968 969
    const cachedData = isPreviewMode
      ? // Preview data bypasses the cache
        undefined
      : await getSprCache(ssgCacheKey)
J
JJ Kasper 已提交
970
    if (cachedData) {
971
      const data = isDataReq
J
JJ Kasper 已提交
972 973 974 975 976 977
        ? JSON.stringify(cachedData.pageData)
        : cachedData.html

      this.__sendPayload(
        res,
        data,
978
        isDataReq ? 'application/json' : 'text/html; charset=utf-8',
979 980 981
        cachedData.curRevalidate !== undefined
          ? { revalidate: cachedData.curRevalidate, private: isPreviewMode }
          : undefined
J
JJ Kasper 已提交
982 983 984 985 986 987
      )

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

J
JJ Kasper 已提交
990 991 992 993
    // If we're here, that means data is missing or it's stale.

    const doRender = withCoalescedInvoke(async function(): Promise<{
      html: string | null
994
      pageData: any
J
JJ Kasper 已提交
995 996
      sprRevalidate: number | false
    }> {
997
      let pageData: any
J
JJ Kasper 已提交
998 999 1000 1001 1002 1003
      let html: string | null
      let sprRevalidate: number | false

      let renderResult
      // handle serverless
      if (isLikeServerless) {
1004
        renderResult = await (components.Component as any).renderReqToHTML(
1005 1006 1007 1008
          req,
          res,
          true
        )
J
JJ Kasper 已提交
1009 1010

        html = renderResult.html
1011
        pageData = renderResult.renderOpts.pageData
J
JJ Kasper 已提交
1012 1013 1014
        sprRevalidate = renderResult.renderOpts.revalidate
      } else {
        const renderOpts = {
1015
          ...components,
J
JJ Kasper 已提交
1016 1017 1018 1019 1020
          ...opts,
        }
        renderResult = await renderToHTML(req, res, pathname, query, renderOpts)

        html = renderResult
1021
        pageData = renderOpts.pageData
J
JJ Kasper 已提交
1022 1023 1024
        sprRevalidate = renderOpts.revalidate
      }

1025
      return { html, pageData, sprRevalidate }
1026
    })
J
JJ Kasper 已提交
1027

1028
    const isProduction = !this.renderOpts.dev
J
Joe Haddad 已提交
1029
    const isDynamicPathname = isDynamicRoute(pathname)
1030
    const didRespond = isResSent(res)
1031 1032 1033 1034

    // 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
1035
    let hasStaticFallback = false
1036

1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062
    if (hasStaticPaths) {
      if (isProduction) {
        // `staticPaths` is intentionally set to `undefined` as it should've
        // been caught above 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)
1063 1064 1065
      }
    }

1066 1067 1068 1069 1070 1071 1072 1073 1074 1075
    // 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.
    //
1076
    // * Non-dynamic pages should block (though this is an impossible
1077 1078
    //   case in production).
    //
1079 1080
    // * Dynamic pages should return their skeleton if not defined in
    //   getStaticPaths, then finish the data request on the client-side.
1081
    //
J
Joe Haddad 已提交
1082
    if (
1083
      !didRespond &&
J
Joe Haddad 已提交
1084
      !isDataReq &&
1085 1086
      !isPreviewMode &&
      isDynamicPathname &&
1087 1088 1089
      // Development should trigger fallback when the path is not in
      // `getStaticPaths`
      (isProduction || !staticPaths || !staticPaths.includes(urlPathname))
J
Joe Haddad 已提交
1090
    ) {
1091 1092 1093 1094 1095 1096 1097 1098 1099 1100
      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
      ) {
        return false
      }

1101
      let html: string
1102

1103 1104
      // Production already emitted the fallback as static HTML.
      if (isProduction) {
1105
        html = await getFallback(pathname)
1106 1107 1108
      }
      // We need to generate the fallback on-demand for development.
      else {
1109 1110 1111
        query.__nextFallback = 'true'
        if (isLikeServerless) {
          this.prepareServerlessUrl(req, query)
1112
          html = await (components.Component as any).renderReqToHTML(req, res)
1113 1114
        } else {
          html = (await renderToHTML(req, res, pathname, query, {
1115
            ...components,
1116 1117 1118 1119 1120 1121 1122 1123
            ...opts,
          })) as string
        }
      }

      this.__sendPayload(res, html, 'text/html; charset=utf-8')
    }

1124 1125 1126 1127 1128 1129 1130 1131 1132
    const {
      isOrigin,
      value: { html, pageData, sprRevalidate },
    } = await doRender(ssgCacheKey, [])
    if (!isResSent(res)) {
      this.__sendPayload(
        res,
        isDataReq ? JSON.stringify(pageData) : html,
        isDataReq ? 'application/json' : 'text/html; charset=utf-8',
1133
        { revalidate: sprRevalidate, private: isPreviewMode }
1134 1135
      )
    }
J
JJ Kasper 已提交
1136

1137 1138 1139 1140 1141
    // 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 已提交
1142
      }
1143 1144 1145
    }

    return null
1146 1147
  }

1148
  public async renderToHTML(
J
Joe Haddad 已提交
1149 1150 1151 1152
    req: IncomingMessage,
    res: ServerResponse,
    pathname: string,
    query: ParsedUrlQuery = {},
J
Joe Haddad 已提交
1153 1154 1155 1156 1157 1158
    {
      amphtml,
      hasAmp,
    }: {
      amphtml?: boolean
      hasAmp?: boolean
1159
    } = {}
J
Joe Haddad 已提交
1160
  ): Promise<string | null> {
1161 1162 1163
    try {
      const result = await this.findPageComponents(pathname, query)
      if (result) {
1164
        const result2 = await this.renderToHTMLWithComponents(
1165 1166 1167 1168 1169 1170
          req,
          res,
          pathname,
          result,
          { ...this.renderOpts, amphtml, hasAmp }
        )
1171 1172 1173
        if (result2 !== false) {
          return result2
        }
1174
      }
J
Joe Haddad 已提交
1175

1176 1177 1178 1179 1180 1181
      if (this.dynamicRoutes) {
        for (const dynamicRoute of this.dynamicRoutes) {
          const params = dynamicRoute.match(pathname)
          if (!params) {
            continue
          }
J
Joe Haddad 已提交
1182

1183 1184 1185 1186 1187 1188
          const result = await this.findPageComponents(
            dynamicRoute.page,
            query,
            params
          )
          if (result) {
1189
            const result2 = await this.renderToHTMLWithComponents(
1190 1191 1192 1193 1194 1195 1196 1197 1198
              req,
              res,
              dynamicRoute.page,
              result,
              {
                ...this.renderOpts,
                params,
                amphtml,
                hasAmp,
1199
              }
J
Joe Haddad 已提交
1200
            )
1201 1202 1203
            if (result2 !== false) {
              return result2
            }
J
Joe Haddad 已提交
1204 1205
          }
        }
1206 1207 1208 1209 1210 1211 1212 1213 1214
      }
    } 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 已提交
1215 1216
  }

J
Joe Haddad 已提交
1217 1218 1219 1220 1221
  public async renderError(
    err: Error | null,
    req: IncomingMessage,
    res: ServerResponse,
    pathname: string,
1222
    query: ParsedUrlQuery = {}
J
Joe Haddad 已提交
1223 1224 1225
  ): Promise<void> {
    res.setHeader(
      'Cache-Control',
1226
      'no-cache, no-store, max-age=0, must-revalidate'
J
Joe Haddad 已提交
1227
    )
N
Naoyuki Kanezawa 已提交
1228
    const html = await this.renderErrorToHTML(err, req, res, pathname, query)
1229
    if (html === null) {
1230 1231
      return
    }
1232
    return this.sendHTML(req, res, html)
N
nkzawa 已提交
1233 1234
  }

J
Joe Haddad 已提交
1235 1236 1237 1238 1239
  public async renderErrorToHTML(
    err: Error | null,
    req: IncomingMessage,
    res: ServerResponse,
    _pathname: string,
1240
    query: ParsedUrlQuery = {}
J
Joe Haddad 已提交
1241
  ) {
1242
    let result: null | FindComponentsResult = null
1243

1244 1245 1246
    const is404 = res.statusCode === 404
    let using404Page = false

1247
    // use static 404 page if available and is 404 response
1248
    if (is404) {
1249 1250
      result = await this.findPageComponents('/404')
      using404Page = result !== null
1251 1252 1253 1254 1255 1256
    }

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

1257
    let html: string | null
1258
    try {
1259
      const result2 = await this.renderToHTMLWithComponents(
1260 1261
        req,
        res,
1262
        using404Page ? '/404' : '/_error',
1263
        result!,
1264 1265 1266 1267 1268
        {
          ...this.renderOpts,
          err,
        }
      )
1269 1270 1271 1272
      if (result2 === false) {
        throw new Error('invariant: failed to render error page')
      }
      html = result2
1273 1274 1275 1276 1277 1278
    } catch (err) {
      console.error(err)
      res.statusCode = 500
      html = 'Internal Server Error'
    }
    return html
N
Naoyuki Kanezawa 已提交
1279 1280
  }

J
Joe Haddad 已提交
1281 1282 1283
  public async render404(
    req: IncomingMessage,
    res: ServerResponse,
1284
    parsedUrl?: UrlWithParsedQuery
J
Joe Haddad 已提交
1285
  ): Promise<void> {
1286 1287
    const url: any = req.url
    const { pathname, query } = parsedUrl ? parsedUrl : parseUrl(url, true)
N
Naoyuki Kanezawa 已提交
1288
    res.statusCode = 404
1289
    return this.renderError(null, req, res, pathname!, query)
N
Naoyuki Kanezawa 已提交
1290
  }
N
Naoyuki Kanezawa 已提交
1291

J
Joe Haddad 已提交
1292 1293 1294 1295
  public async serveStatic(
    req: IncomingMessage,
    res: ServerResponse,
    path: string,
1296
    parsedUrl?: UrlWithParsedQuery
J
Joe Haddad 已提交
1297
  ): Promise<void> {
A
Arunoda Susiripala 已提交
1298
    if (!this.isServeableUrl(path)) {
1299
      return this.render404(req, res, parsedUrl)
A
Arunoda Susiripala 已提交
1300 1301
    }

1302 1303 1304 1305 1306 1307
    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 已提交
1308
    try {
1309
      await serveStatic(req, res, path)
N
Naoyuki Kanezawa 已提交
1310
    } catch (err) {
T
Tim Neutkens 已提交
1311
      if (err.code === 'ENOENT' || err.statusCode === 404) {
1312
        this.render404(req, res, parsedUrl)
1313 1314 1315
      } else if (err.statusCode === 412) {
        res.statusCode = 412
        return this.renderError(err, req, res, path)
N
Naoyuki Kanezawa 已提交
1316 1317 1318 1319 1320 1321
      } else {
        throw err
      }
    }
  }

1322
  private isServeableUrl(path: string): boolean {
A
Arunoda Susiripala 已提交
1323 1324
    const resolved = resolve(path)
    if (
1325
      resolved.indexOf(join(this.distDir) + sep) !== 0 &&
1326 1327
      resolved.indexOf(join(this.dir, 'static') + sep) !== 0 &&
      resolved.indexOf(join(this.dir, 'public') + sep) !== 0
A
Arunoda Susiripala 已提交
1328 1329 1330 1331 1332 1333 1334 1335
    ) {
      // Seems like the user is trying to traverse the filesystem.
      return false
    }

    return true
  }

1336
  protected readBuildId(): string {
1337 1338 1339 1340 1341
    const buildIdFile = join(this.distDir, BUILD_ID_FILE)
    try {
      return fs.readFileSync(buildIdFile, 'utf8').trim()
    } catch (err) {
      if (!fs.existsSync(buildIdFile)) {
J
Joe Haddad 已提交
1342
        throw new Error(
1343
          `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 已提交
1344
        )
1345 1346 1347
      }

      throw err
1348
    }
1349
  }
1350 1351 1352 1353

  private get _isLikeServerless(): boolean {
    return isTargetLikeServerless(this.nextConfig.target)
  }
1354
}