import fs from 'fs' import { join, relative } from 'path' import { PAGES_MANIFEST, BUILD_ID_FILE } from '../shared/lib/constants' import { PagesManifest } from '../build/webpack/plugins/pages-manifest-plugin' import type { Route } from './router' import { recursiveReadDirSync } from './lib/recursive-readdir-sync' import { route } from './router' import BaseServer from './base-server' export * from './base-server' export default class NextNodeServer extends BaseServer { protected getHasStaticDir(): boolean { return fs.existsSync(join(this.dir, 'static')) } protected getPagesManifest(): PagesManifest | undefined { const pagesManifestPath = join(this.serverBuildDir, PAGES_MANIFEST) return require(pagesManifestPath) } protected getBuildId(): string { const buildIdFile = join(this.distDir, BUILD_ID_FILE) try { return fs.readFileSync(buildIdFile, 'utf8').trim() } catch (err) { if (!fs.existsSync(buildIdFile)) { throw new Error( `Could not find a production build in the '${this.distDir}' directory. Try building your app with 'next build' before starting the production server. https://nextjs.org/docs/messages/production-start-no-build-id` ) } throw err } } protected generatePublicRoutes(): Route[] { if (!fs.existsSync(this.publicDir)) return [] const publicFiles = new Set( recursiveReadDirSync(this.publicDir).map((p) => encodeURI(p.replace(/\\/g, '/')) ) ) return [ { match: route('/:path*'), name: 'public folder catchall', fn: async (req, res, params, parsedUrl) => { const pathParts: string[] = params.path || [] const { basePath } = this.nextConfig // if basePath is defined require it be present if (basePath) { const basePathParts = basePath.split('/') // remove first empty value basePathParts.shift() if ( !basePathParts.every((part: string, idx: number) => { return part === pathParts[idx] }) ) { return { finished: false } } pathParts.splice(0, basePathParts.length) } let path = `/${pathParts.join('/')}` if (!publicFiles.has(path)) { // In `next-dev-server.ts`, we ensure encoded paths match // decoded paths on the filesystem. So we need do the // opposite here: make sure decoded paths match encoded. path = encodeURI(path) } if (publicFiles.has(path)) { await this.serveStatic( req, res, join(this.publicDir, ...pathParts), parsedUrl ) return { finished: true, } } return { finished: false, } }, } as Route, ] } private _validFilesystemPathSet: Set | null = null protected getFilesystemPaths(): Set { if (this._validFilesystemPathSet) { return this._validFilesystemPathSet } const pathUserFilesStatic = join(this.dir, 'static') let userFilesStatic: string[] = [] if (this.hasStaticDir && fs.existsSync(pathUserFilesStatic)) { userFilesStatic = recursiveReadDirSync(pathUserFilesStatic).map((f) => join('.', 'static', f) ) } let userFilesPublic: string[] = [] if (this.publicDir && fs.existsSync(this.publicDir)) { userFilesPublic = recursiveReadDirSync(this.publicDir).map((f) => join('.', 'public', f) ) } let nextFilesStatic: string[] = [] nextFilesStatic = !this.minimalMode && fs.existsSync(join(this.distDir, 'static')) ? recursiveReadDirSync(join(this.distDir, 'static')).map((f) => join('.', relative(this.dir, this.distDir), 'static', f) ) : [] return (this._validFilesystemPathSet = new Set([ ...nextFilesStatic, ...userFilesPublic, ...userFilesStatic, ])) } }