next
Version:
The React Framework
1,083 lines • 84.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _compression = _interopRequireDefault(require("next/dist/compiled/compression"));
var _fs = _interopRequireDefault(require("fs"));
var _httpProxy = _interopRequireDefault(require("next/dist/compiled/http-proxy"));
var _path = require("path");
var _querystring = require("querystring");
var _url = require("url");
var _loadCustomRoutes = require("../lib/load-custom-routes");
var _constants = require("../shared/lib/constants");
var _utils = require("../shared/lib/router/utils");
var envConfig = _interopRequireWildcard(require("../shared/lib/runtime-config"));
var _utils1 = require("../shared/lib/utils");
var _apiUtils = require("./api-utils");
var _config = require("./config");
var _pathMatch = _interopRequireDefault(require("../shared/lib/router/utils/path-match"));
var _recursiveReaddirSync = require("./lib/recursive-readdir-sync");
var _loadComponents = require("./load-components");
var _normalizePagePath = require("./normalize-page-path");
var _render = require("./render");
var _require = require("./require");
var _router = _interopRequireWildcard(require("./router"));
var _prepareDestination = _interopRequireWildcard(require("../shared/lib/router/utils/prepare-destination"));
var _sendPayload = require("./send-payload");
var _serveStatic = require("./serve-static");
var _incrementalCache = require("./incremental-cache");
var _utils2 = require("./utils");
var _renderResult = _interopRequireDefault(require("./render-result"));
var _env = require("@next/env");
require("./node-polyfill-fetch");
var _normalizeTrailingSlash = require("../client/normalize-trailing-slash");
var _getRouteFromAssetPath = _interopRequireDefault(require("../shared/lib/router/utils/get-route-from-asset-path"));
var _denormalizePagePath = require("./denormalize-page-path");
var _normalizeLocalePath = require("../shared/lib/i18n/normalize-locale-path");
var Log = _interopRequireWildcard(require("../build/output/log"));
var _detectDomainLocale = require("../shared/lib/i18n/detect-domain-locale");
var _escapePathDelimiters = _interopRequireDefault(require("../shared/lib/router/utils/escape-path-delimiters"));
var _utils3 = require("../build/webpack/loaders/next-serverless-loader/utils");
var _responseCache = _interopRequireDefault(require("./response-cache"));
var _parseNextUrl = require("../shared/lib/router/utils/parse-next-url");
var _isError = _interopRequireDefault(require("../lib/is-error"));
var _parseUrl = require("../shared/lib/router/utils/parse-url");
var _constants1 = require("../lib/constants");
var _response = require("./web/spec-extension/response");
var _sandbox = require("./web/sandbox");
var _utils4 = require("./web/utils");
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
function _interopRequireWildcard(obj) {
if (obj && obj.__esModule) {
return obj;
} else {
var newObj = {
};
if (obj != null) {
for(var key in obj){
if (Object.prototype.hasOwnProperty.call(obj, key)) {
var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {
};
if (desc.get || desc.set) {
Object.defineProperty(newObj, key, desc);
} else {
newObj[key] = obj[key];
}
}
}
}
newObj.default = obj;
return newObj;
}
}
const getCustomRouteMatcher = (0, _pathMatch).default(true);
class Server {
constructor({ dir ='.' , quiet =false , conf , dev =false , minimalMode =false , customServer =true }){
var ref, ref1, ref2;
this.middlewareBetaWarning = (0, _utils1).execOnce(()=>{
Log.warn(`using beta Middleware (not covered by semver) - https://nextjs.org/docs/messages/beta-middleware`);
});
this.customErrorNo404Warn = (0, _utils1).execOnce(()=>{
Log.warn(`You have added a custom /_error page without a custom /404 page. This prevents the 404 page from being auto statically optimized.\nSee here for info: https://nextjs.org/docs/messages/custom-error-no-custom-404`);
});
this._validFilesystemPathSet = null;
this.dir = (0, _path).resolve(dir);
this.quiet = quiet;
(0, _env).loadEnvConfig(this.dir, dev, Log);
this.nextConfig = conf;
this.distDir = (0, _path).join(this.dir, this.nextConfig.distDir);
this.publicDir = (0, _path).join(this.dir, _constants.CLIENT_PUBLIC_FILES_PATH);
this.hasStaticDir = !minimalMode && _fs.default.existsSync((0, _path).join(this.dir, 'static'));
// Only serverRuntimeConfig needs the default
// publicRuntimeConfig gets it's default in client/index.js
const { serverRuntimeConfig ={
} , publicRuntimeConfig , assetPrefix , generateEtags , compress , } = this.nextConfig;
this.buildId = this.readBuildId();
this.minimalMode = minimalMode;
this.renderOpts = {
poweredByHeader: this.nextConfig.poweredByHeader,
canonicalBase: this.nextConfig.amp.canonicalBase || '',
buildId: this.buildId,
generateEtags,
previewProps: this.getPreviewProps(),
customServer: customServer === true ? true : undefined,
ampOptimizerConfig: (ref = this.nextConfig.experimental.amp) === null || ref === void 0 ? void 0 : ref.optimizer,
basePath: this.nextConfig.basePath,
images: JSON.stringify(this.nextConfig.images),
optimizeFonts: !!this.nextConfig.optimizeFonts && !dev,
fontManifest: this.nextConfig.optimizeFonts && !dev ? (0, _require).requireFontManifest(this.distDir, this._isLikeServerless) : null,
optimizeImages: !!this.nextConfig.experimental.optimizeImages,
optimizeCss: this.nextConfig.experimental.optimizeCss,
disableOptimizedLoading: this.nextConfig.experimental.disableOptimizedLoading,
domainLocales: (ref1 = this.nextConfig.i18n) === null || ref1 === void 0 ? void 0 : ref1.domains,
distDir: this.distDir,
concurrentFeatures: this.nextConfig.experimental.concurrentFeatures
};
// Only the `publicRuntimeConfig` key is exposed to the client side
// It'll be rendered as part of __NEXT_DATA__ on the client side
if (Object.keys(publicRuntimeConfig).length > 0) {
this.renderOpts.runtimeConfig = publicRuntimeConfig;
}
if (compress && this.nextConfig.target === 'server') {
this.compression = (0, _compression).default();
}
// Initialize next/config with the environment configuration
envConfig.setConfig({
serverRuntimeConfig,
publicRuntimeConfig
});
this.serverBuildDir = (0, _path).join(this.distDir, this._isLikeServerless ? _constants.SERVERLESS_DIRECTORY : _constants.SERVER_DIRECTORY);
const pagesManifestPath = (0, _path).join(this.serverBuildDir, _constants.PAGES_MANIFEST);
const middlewareManifestPath = (0, _path).join((0, _path).join(this.distDir, _constants.SERVER_DIRECTORY), _constants.MIDDLEWARE_MANIFEST);
if (!dev) {
this.pagesManifest = require(pagesManifestPath);
if (!this.minimalMode) {
this.middlewareManifest = require(middlewareManifestPath);
}
}
this.customRoutes = this.getCustomRoutes();
this.router = new _router.default(this.generateRoutes());
this.setAssetPrefix(assetPrefix);
this.incrementalCache = new _incrementalCache.IncrementalCache({
dev,
distDir: this.distDir,
pagesDir: (0, _path).join(this.distDir, this._isLikeServerless ? _constants.SERVERLESS_DIRECTORY : _constants.SERVER_DIRECTORY, 'pages'),
locales: (ref2 = this.nextConfig.i18n) === null || ref2 === void 0 ? void 0 : ref2.locales,
max: this.nextConfig.experimental.isrMemoryCacheSize,
flushToDisk: !minimalMode && this.nextConfig.experimental.isrFlushToDisk
});
this.responseCache = new _responseCache.default(this.incrementalCache);
/**
* This sets environment variable to be used at the time of SSR by head.tsx.
* Using this from process.env allows targeting both serverless and SSR by calling
* `process.env.__NEXT_OPTIMIZE_IMAGES`.
* TODO(atcastle@): Remove this when experimental.optimizeImages are being cleaned up.
*/ if (this.renderOpts.optimizeFonts) {
process.env.__NEXT_OPTIMIZE_FONTS = JSON.stringify(true);
}
if (this.renderOpts.optimizeImages) {
process.env.__NEXT_OPTIMIZE_IMAGES = JSON.stringify(true);
}
if (this.renderOpts.optimizeCss) {
process.env.__NEXT_OPTIMIZE_CSS = JSON.stringify(true);
}
}
logError(err) {
if (this.quiet) return;
console.error(err);
}
async handleRequest(req, res, parsedUrl) {
var ref23, ref1, ref2, ref3, ref4, ref5;
const urlParts = (req.url || '').split('?');
const urlNoQuery = urlParts[0];
if (urlNoQuery === null || urlNoQuery === void 0 ? void 0 : urlNoQuery.match(/(\\|\/\/)/)) {
const cleanUrl = (0, _utils1).normalizeRepeatedSlashes(req.url);
res.setHeader('Location', cleanUrl);
res.setHeader('Refresh', `0;url=${cleanUrl}`);
res.statusCode = 308;
res.end(cleanUrl);
return;
}
(0, _apiUtils).setLazyProp({
req: req
}, 'cookies', (0, _apiUtils).getCookieParser(req.headers));
// Parse url if parsedUrl not provided
if (!parsedUrl || typeof parsedUrl !== 'object') {
const url = req.url;
parsedUrl = (0, _url).parse(url, true);
}
const { basePath , i18n } = this.nextConfig;
// Parse the querystring ourselves if the user doesn't handle querystring parsing
if (typeof parsedUrl.query === 'string') {
parsedUrl.query = (0, _querystring).parse(parsedUrl.query);
}
req.__NEXT_INIT_URL = req.url;
req.__NEXT_INIT_QUERY = Object.assign({
}, parsedUrl.query);
const url = (0, _parseNextUrl).parseNextUrl({
headers: req.headers,
nextConfig: this.nextConfig,
url: (ref23 = req.url) === null || ref23 === void 0 ? void 0 : ref23.replace(/^\/+/, '/')
});
if (url.basePath) {
req._nextHadBasePath = true;
req.url = req.url.replace(basePath, '') || '/';
}
if (this.minimalMode && req.headers['x-matched-path'] && typeof req.headers['x-matched-path'] === 'string') {
var ref, ref21;
const reqUrlIsDataUrl = (ref = req.url) === null || ref === void 0 ? void 0 : ref.includes('/_next/data');
const matchedPathIsDataUrl = (ref21 = req.headers['x-matched-path']) === null || ref21 === void 0 ? void 0 : ref21.includes('/_next/data');
const isDataUrl = reqUrlIsDataUrl || matchedPathIsDataUrl;
let parsedPath = (0, _url).parse(isDataUrl ? req.url : req.headers['x-matched-path'], true);
const { pathname , query } = parsedPath;
let matchedPathname = pathname;
let matchedPathnameNoExt = isDataUrl ? matchedPathname.replace(/\.json$/, '') : matchedPathname;
if (i18n) {
const localePathResult = (0, _normalizeLocalePath).normalizeLocalePath(matchedPathname || '/', i18n.locales);
if (localePathResult.detectedLocale) {
parsedUrl.query.__nextLocale = localePathResult.detectedLocale;
}
}
if (isDataUrl) {
matchedPathname = (0, _denormalizePagePath).denormalizePagePath(matchedPathname);
matchedPathnameNoExt = (0, _denormalizePagePath).denormalizePagePath(matchedPathnameNoExt);
}
const pageIsDynamic = (0, _utils).isDynamicRoute(matchedPathnameNoExt);
const combinedRewrites = [];
combinedRewrites.push(...this.customRoutes.rewrites.beforeFiles);
combinedRewrites.push(...this.customRoutes.rewrites.afterFiles);
combinedRewrites.push(...this.customRoutes.rewrites.fallback);
const utils = (0, _utils3).getUtils({
pageIsDynamic,
page: matchedPathnameNoExt,
i18n: this.nextConfig.i18n,
basePath: this.nextConfig.basePath,
rewrites: combinedRewrites
});
utils.handleRewrites(req, parsedUrl);
// interpolate dynamic params and normalize URL if needed
if (pageIsDynamic) {
let params = {
};
Object.assign(parsedUrl.query, query);
const paramsResult = utils.normalizeDynamicRouteParams(parsedUrl.query);
if (paramsResult.hasValidParams) {
params = paramsResult.params;
} else if (req.headers['x-now-route-matches']) {
const opts = {
};
params = utils.getParamsFromRouteMatches(req, opts, parsedUrl.query.__nextLocale || '');
if (opts.locale) {
parsedUrl.query.__nextLocale = opts.locale;
}
} else {
params = utils.dynamicRouteMatcher(matchedPathnameNoExt);
}
if (params) {
params = utils.normalizeDynamicRouteParams(params).params;
matchedPathname = utils.interpolateDynamicPath(matchedPathname, params);
req.url = utils.interpolateDynamicPath(req.url, params);
}
if (reqUrlIsDataUrl && matchedPathIsDataUrl) {
req.url = (0, _url).format({
...parsedPath,
pathname: matchedPathname
});
}
Object.assign(parsedUrl.query, params);
utils.normalizeVercelUrl(req, true);
}
parsedUrl.pathname = `${basePath || ''}${matchedPathname === '/' && basePath ? '' : matchedPathname}`;
}
req.__nextHadTrailingSlash = (ref1 = url.locale) === null || ref1 === void 0 ? void 0 : ref1.trailingSlash;
if ((ref2 = url.locale) === null || ref2 === void 0 ? void 0 : ref2.domain) {
req.__nextIsLocaleDomain = true;
}
if ((ref3 = url.locale) === null || ref3 === void 0 ? void 0 : ref3.path.detectedLocale) {
req.url = (0, _url).format(url);
req.__nextStrippedLocale = true;
if (url.pathname === '/api' || url.pathname.startsWith('/api/')) {
return this.render404(req, res, parsedUrl);
}
}
if (!this.minimalMode || !parsedUrl.query.__nextLocale) {
var ref;
if (url === null || url === void 0 ? void 0 : (ref = url.locale) === null || ref === void 0 ? void 0 : ref.locale) {
parsedUrl.query.__nextLocale = url.locale.locale;
}
}
if (url === null || url === void 0 ? void 0 : (ref4 = url.locale) === null || ref4 === void 0 ? void 0 : ref4.defaultLocale) {
parsedUrl.query.__nextDefaultLocale = url.locale.defaultLocale;
}
if ((ref5 = url.locale) === null || ref5 === void 0 ? void 0 : ref5.redirect) {
res.setHeader('Location', url.locale.redirect);
res.statusCode = _constants.TEMPORARY_REDIRECT_STATUS;
res.end();
return;
}
res.statusCode = 200;
try {
return await this.run(req, res, parsedUrl);
} catch (err) {
if (this.minimalMode || this.renderOpts.dev) {
throw err;
}
this.logError((0, _isError).default(err) ? err : new Error(err + ''));
res.statusCode = 500;
res.end('Internal Server Error');
}
}
getRequestHandler() {
return this.handleRequest.bind(this);
}
setAssetPrefix(prefix) {
this.renderOpts.assetPrefix = prefix ? prefix.replace(/\/$/, '') : '';
}
// Backwards compatibility
async prepare() {
}
// Backwards compatibility
async close() {
}
setImmutableAssetCacheControl(res) {
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
}
getCustomRoutes() {
const customRoutes = require((0, _path).join(this.distDir, _constants.ROUTES_MANIFEST));
let rewrites;
// rewrites can be stored as an array when an array is
// returned in next.config.js so massage them into
// the expected object format
if (Array.isArray(customRoutes.rewrites)) {
rewrites = {
beforeFiles: [],
afterFiles: customRoutes.rewrites,
fallback: []
};
} else {
rewrites = customRoutes.rewrites;
}
return Object.assign(customRoutes, {
rewrites
});
}
getPrerenderManifest() {
if (this._cachedPreviewManifest) {
return this._cachedPreviewManifest;
}
const manifest = require((0, _path).join(this.distDir, _constants.PRERENDER_MANIFEST));
return this._cachedPreviewManifest = manifest;
}
getPreviewProps() {
return this.getPrerenderManifest().preview;
}
getMiddleware() {
var ref;
const middleware = ((ref = this.middlewareManifest) === null || ref === void 0 ? void 0 : ref.middleware) || {
};
return Object.keys(middleware).map((page)=>({
match: (0, _utils).getRouteMatcher((0, _utils).getMiddlewareRegex(page, _constants1.MIDDLEWARE_ROUTE.test(middleware[page].name))),
page
})
);
}
async hasMiddleware(pathname, _isSSR) {
try {
return (0, _require).getMiddlewareInfo({
dev: this.renderOpts.dev,
distDir: this.distDir,
page: pathname,
serverless: this._isLikeServerless
}).paths.length > 0;
} catch (_) {
}
return false;
}
async ensureMiddleware(_pathname, _isSSR) {
}
async runMiddleware(params) {
this.middlewareBetaWarning();
const page = {
};
if (await this.hasPage(params.parsedUrl.pathname)) {
page.name = params.parsedUrl.pathname;
} else if (this.dynamicRoutes) {
for (const dynamicRoute of this.dynamicRoutes){
const matchParams = dynamicRoute.match(params.parsedUrl.pathname);
if (matchParams) {
page.name = dynamicRoute.page;
page.params = matchParams;
break;
}
}
}
const subreq = params.request.headers[`x-middleware-subrequest`];
const subrequests = typeof subreq === 'string' ? subreq.split(':') : [];
const allHeaders = new Headers();
let result = null;
for (const middleware of this.middleware || []){
if (middleware.match(params.parsedUrl.pathname)) {
if (!await this.hasMiddleware(middleware.page, middleware.ssr)) {
console.warn(`The Edge Function for ${middleware.page} was not found`);
continue;
}
await this.ensureMiddleware(middleware.page, middleware.ssr);
const middlewareInfo = (0, _require).getMiddlewareInfo({
dev: this.renderOpts.dev,
distDir: this.distDir,
page: middleware.page,
serverless: this._isLikeServerless
});
if (subrequests.includes(middlewareInfo.name)) {
result = {
response: _response.NextResponse.next(),
waitUntil: Promise.resolve()
};
continue;
}
result = await (0, _sandbox).run({
name: middlewareInfo.name,
paths: middlewareInfo.paths,
request: {
headers: params.request.headers,
method: params.request.method || 'GET',
nextConfig: {
basePath: this.nextConfig.basePath,
i18n: this.nextConfig.i18n,
trailingSlash: this.nextConfig.trailingSlash
},
url: params.request.__NEXT_INIT_URL,
page: page
},
ssr: !!this.nextConfig.experimental.concurrentFeatures
});
for (let [key, value] of result.response.headers){
allHeaders.append(key, value);
}
if (!this.renderOpts.dev) {
result.waitUntil.catch((error)=>{
console.error(`Uncaught: middleware waitUntil errored`, error);
});
}
if (!result.response.headers.has('x-middleware-next')) {
break;
}
}
}
if (!result) {
this.render404(params.request, params.response, params.parsed);
} else {
for (let [key, value] of allHeaders){
result.response.headers.set(key, value);
}
}
return result;
}
generateRoutes() {
var ref;
const server = this;
const publicRoutes = _fs.default.existsSync(this.publicDir) ? this.generatePublicRoutes() : [];
const staticFilesRoute = this.hasStaticDir ? [
{
// 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/vercel/next.js/issues/2617
match: (0, _router).route('/static/:path*'),
name: 'static catchall',
fn: async (req, res, params, parsedUrl)=>{
const p = (0, _path).join(this.dir, 'static', ...params.path);
await this.serveStatic(req, res, p, parsedUrl);
return {
finished: true
};
}
},
] : [];
const fsRoutes = [
{
match: (0, _router).route('/_next/static/:path*'),
type: 'route',
name: '_next/static catchall',
fn: async (req, res, params, parsedUrl)=>{
// make sure to 404 for /_next/static itself
if (!params.path) {
await this.render404(req, res, parsedUrl);
return {
finished: true
};
}
if (params.path[0] === _constants.CLIENT_STATIC_FILES_RUNTIME || params.path[0] === 'chunks' || params.path[0] === 'css' || params.path[0] === 'image' || params.path[0] === 'media' || params.path[0] === this.buildId || params.path[0] === 'pages' || params.path[1] === 'pages') {
this.setImmutableAssetCacheControl(res);
}
const p = (0, _path).join(this.distDir, _constants.CLIENT_STATIC_FILES_PATH, ...params.path || []);
await this.serveStatic(req, res, p, parsedUrl);
return {
finished: true
};
}
},
{
match: (0, _router).route('/_next/data/:path*'),
type: 'route',
name: '_next/data catchall',
fn: async (req, res, params, _parsedUrl)=>{
// 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) {
await this.render404(req, res, _parsedUrl);
return {
finished: true
};
}
// remove buildId from URL
params.path.shift();
const lastParam = params.path[params.path.length - 1];
// show 404 if it doesn't end with .json
if (typeof lastParam !== 'string' || !lastParam.endsWith('.json')) {
await this.render404(req, res, _parsedUrl);
return {
finished: true
};
}
// re-create page's pathname
let pathname = `/${params.path.join('/')}`;
pathname = (0, _getRouteFromAssetPath).default(pathname, '.json');
const { i18n } = this.nextConfig;
if (i18n) {
const { host } = (req === null || req === void 0 ? void 0 : req.headers) || {
};
// remove port from host and remove port if present
const hostname = host === null || host === void 0 ? void 0 : host.split(':')[0].toLowerCase();
const localePathResult = (0, _normalizeLocalePath).normalizeLocalePath(pathname, i18n.locales);
const { defaultLocale } = (0, _detectDomainLocale).detectDomainLocale(i18n.domains, hostname) || {
};
let detectedLocale = '';
if (localePathResult.detectedLocale) {
pathname = localePathResult.pathname;
detectedLocale = localePathResult.detectedLocale;
}
_parsedUrl.query.__nextLocale = detectedLocale;
_parsedUrl.query.__nextDefaultLocale = defaultLocale || i18n.defaultLocale;
if (!detectedLocale) {
_parsedUrl.query.__nextLocale = _parsedUrl.query.__nextDefaultLocale;
await this.render404(req, res, _parsedUrl);
return {
finished: true
};
}
}
const parsedUrl = (0, _url).parse(pathname, true);
await this.render(req, res, pathname, {
..._parsedUrl.query,
_nextDataReq: '1'
}, parsedUrl);
return {
finished: true
};
}
},
{
match: (0, _router).route('/_next/image'),
type: 'route',
name: '_next/image catchall',
fn: (req, res, _params, parsedUrl)=>{
if (this.minimalMode) {
res.statusCode = 400;
res.end('Bad Request');
return {
finished: true
};
}
const { imageOptimizer } = require('./image-optimizer');
return imageOptimizer(server, req, res, parsedUrl, server.nextConfig, server.distDir, this.renderOpts.dev);
}
},
{
match: (0, _router).route('/_next/:path*'),
type: 'route',
name: '_next catchall',
// This path is needed because `render()` does a check for `/_next` and the calls the routing again
fn: async (req, res, _params, parsedUrl)=>{
await this.render404(req, res, parsedUrl);
return {
finished: true
};
}
},
...publicRoutes,
...staticFilesRoute,
];
const restrictedRedirectPaths = [
'/_next'
].map((p)=>this.nextConfig.basePath ? `${this.nextConfig.basePath}${p}` : p
);
const getCustomRoute = (r, type)=>{
const match = getCustomRouteMatcher(r.source, !r.internal ? (regex)=>(0, _loadCustomRoutes).modifyRouteRegex(regex, type === 'redirect' ? restrictedRedirectPaths : undefined)
: undefined);
return {
...r,
type,
match,
name: type,
fn: async (_req, _res, _params, _parsedUrl)=>({
finished: false
})
};
};
// Headers come very first
const headers = this.minimalMode ? [] : this.customRoutes.headers.map((r)=>{
const headerRoute = getCustomRoute(r, 'header');
return {
match: headerRoute.match,
has: headerRoute.has,
type: headerRoute.type,
name: `${headerRoute.type} ${headerRoute.source} header route`,
fn: async (_req, res, params, _parsedUrl)=>{
const hasParams = Object.keys(params).length > 0;
for (const header of headerRoute.headers){
let { key , value } = header;
if (hasParams) {
key = (0, _prepareDestination).compileNonPath(key, params);
value = (0, _prepareDestination).compileNonPath(value, params);
}
res.setHeader(key, value);
}
return {
finished: false
};
}
};
});
// since initial query values are decoded by querystring.parse
// we need to re-encode them here but still allow passing through
// values from rewrites/redirects
const stringifyQuery = (req, query)=>{
const initialQueryValues = Object.values(req.__NEXT_INIT_QUERY);
return (0, _querystring).stringify(query, undefined, undefined, {
encodeURIComponent (value) {
if (initialQueryValues.some((val)=>val === value
)) {
return encodeURIComponent(value);
}
return value;
}
});
};
const proxyRequest = async (req, res, parsedUrl)=>{
const { query } = parsedUrl;
delete parsedUrl.query;
parsedUrl.search = stringifyQuery(req, query);
const target = (0, _url).format(parsedUrl);
const proxy = new _httpProxy.default({
target,
changeOrigin: true,
ignorePath: true,
xfwd: true,
proxyTimeout: 30000
});
await new Promise((proxyResolve, proxyReject)=>{
let finished = false;
proxy.on('proxyReq', (proxyReq)=>{
proxyReq.on('close', ()=>{
if (!finished) {
finished = true;
proxyResolve(true);
}
});
});
proxy.on('error', (err)=>{
if (!finished) {
finished = true;
proxyReject(err);
}
});
proxy.web(req, res);
});
return {
finished: true
};
};
const redirects = this.minimalMode ? [] : this.customRoutes.redirects.map((redirect)=>{
const redirectRoute = getCustomRoute(redirect, 'redirect');
return {
internal: redirectRoute.internal,
type: redirectRoute.type,
match: redirectRoute.match,
has: redirectRoute.has,
statusCode: redirectRoute.statusCode,
name: `Redirect route ${redirectRoute.source}`,
fn: async (req, res, params, parsedUrl)=>{
const { parsedDestination } = (0, _prepareDestination).default(redirectRoute.destination, params, parsedUrl.query, false);
const { query } = parsedDestination;
delete parsedDestination.query;
parsedDestination.search = stringifyQuery(req, query);
let updatedDestination = (0, _url).format(parsedDestination);
if (updatedDestination.startsWith('/')) {
updatedDestination = (0, _utils1).normalizeRepeatedSlashes(updatedDestination);
}
res.setHeader('Location', updatedDestination);
res.statusCode = (0, _loadCustomRoutes).getRedirectStatus(redirectRoute);
// Since IE11 doesn't support the 308 header add backwards
// compatibility using refresh header
if (res.statusCode === 308) {
res.setHeader('Refresh', `0;url=${updatedDestination}`);
}
res.end(updatedDestination);
return {
finished: true
};
}
};
});
const buildRewrite = (rewrite, check = true)=>{
const rewriteRoute = getCustomRoute(rewrite, 'rewrite');
return {
...rewriteRoute,
check,
type: rewriteRoute.type,
name: `Rewrite route ${rewriteRoute.source}`,
match: rewriteRoute.match,
fn: async (req, res, params, parsedUrl)=>{
const { newUrl , parsedDestination } = (0, _prepareDestination).default(rewriteRoute.destination, params, parsedUrl.query, true);
// external rewrite, proxy it
if (parsedDestination.protocol) {
return proxyRequest(req, res, parsedDestination);
}
req._nextRewroteUrl = newUrl;
req._nextDidRewrite = req._nextRewroteUrl !== req.url;
return {
finished: false,
pathname: newUrl,
query: parsedDestination.query
};
}
};
};
let beforeFiles = [];
let afterFiles = [];
let fallback = [];
if (!this.minimalMode) {
if (Array.isArray(this.customRoutes.rewrites)) {
afterFiles = this.customRoutes.rewrites.map((r)=>buildRewrite(r)
);
} else {
beforeFiles = this.customRoutes.rewrites.beforeFiles.map((r)=>buildRewrite(r, false)
);
afterFiles = this.customRoutes.rewrites.afterFiles.map((r)=>buildRewrite(r)
);
fallback = this.customRoutes.rewrites.fallback.map((r)=>buildRewrite(r)
);
}
}
let catchAllMiddleware;
if (!this.minimalMode) {
catchAllMiddleware = {
match: (0, _router).route('/:path*'),
type: 'route',
name: 'middleware catchall',
fn: async (req, res, _params, parsed)=>{
var ref;
const fullUrl = req.__NEXT_INIT_URL;
const parsedUrl = (0, _parseNextUrl).parseNextUrl({
url: fullUrl,
headers: req.headers,
nextConfig: {
basePath: this.nextConfig.basePath,
i18n: this.nextConfig.i18n,
trailingSlash: this.nextConfig.trailingSlash
}
});
if (!((ref = this.middleware) === null || ref === void 0 ? void 0 : ref.some((m)=>m.match(parsedUrl.pathname)
))) {
return {
finished: false
};
}
let result = null;
try {
result = await this.runMiddleware({
request: req,
response: res,
parsedUrl: parsedUrl,
parsed: parsed
});
} catch (err) {
if ((0, _isError).default(err) && err.code === 'ENOENT') {
await this.render404(req, res, parsed);
return {
finished: true
};
}
const error = (0, _isError).default(err) ? err : new Error(err + '');
console.error(error);
res.statusCode = 500;
this.renderError(error, req, res, parsed.pathname || '');
return {
finished: true
};
}
if (result === null) {
return {
finished: true
};
}
if (!result.response.headers.has('x-middleware-rewrite') && !result.response.headers.has('x-middleware-next') && !result.response.headers.has('Location')) {
result.response.headers.set('x-middleware-refresh', '1');
}
result.response.headers.delete('x-middleware-next');
for (const [key, value] of Object.entries((0, _utils4).toNodeHeaders(result.response.headers))){
if (key !== 'content-encoding' && value !== undefined) {
res.setHeader(key, value);
}
}
const preflight = req.method === 'HEAD' && req.headers['x-middleware-preflight'];
if (preflight) {
res.writeHead(200);
res.end();
return {
finished: true
};
}
res.statusCode = result.response.status;
res.statusMessage = result.response.statusText;
const location = result.response.headers.get('Location');
if (location) {
res.statusCode = result.response.status;
if (res.statusCode === 308) {
res.setHeader('Refresh', `0;url=${location}`);
}
res.end();
return {
finished: true
};
}
if (result.response.headers.has('x-middleware-rewrite')) {
const rewrite = result.response.headers.get('x-middleware-rewrite');
const rewriteParsed = (0, _parseUrl).parseUrl(rewrite);
if (rewriteParsed.protocol) {
return proxyRequest(req, res, rewriteParsed);
}
req._nextRewroteUrl = rewrite;
req._nextDidRewrite = req._nextRewroteUrl !== req.url;
return {
finished: false,
pathname: rewriteParsed.pathname,
query: {
...parsedUrl.query,
...rewriteParsed.query
}
};
}
if (result.response.headers.has('x-middleware-refresh')) {
res.writeHead(result.response.status);
for await (const chunk of result.response.body || []){
res.write(chunk);
}
res.end();
return {
finished: true
};
}
return {
finished: false
};
}
};
}
const catchAllRoute = {
match: (0, _router).route('/:path*'),
type: 'route',
name: 'Catchall render',
fn: async (req, res, _params, parsedUrl)=>{
let { pathname , query } = parsedUrl;
if (!pathname) {
throw new Error('pathname is undefined');
}
// next.js core assumes page path without trailing slash
pathname = (0, _normalizeTrailingSlash).removePathTrailingSlash(pathname);
if (this.nextConfig.i18n) {
var ref;
const localePathResult = (0, _normalizeLocalePath).normalizeLocalePath(pathname, (ref = this.nextConfig.i18n) === null || ref === void 0 ? void 0 : ref.locales);
if (localePathResult.detectedLocale) {
pathname = localePathResult.pathname;
parsedUrl.query.__nextLocale = localePathResult.detectedLocale;
}
}
const bubbleNoFallback = !!query._nextBubbleNoFallback;
if (pathname.match(_constants1.MIDDLEWARE_ROUTE)) {
await this.render404(req, res, parsedUrl);
return {
finished: true
};
}
if (pathname === '/api' || pathname.startsWith('/api/')) {
delete query._nextBubbleNoFallback;
const handled = await this.handleApiRequest(req, res, pathname, query);
if (handled) {
return {
finished: true
};
}
}
try {
await this.render(req, res, pathname, query, parsedUrl);
return {
finished: true
};
} catch (err) {
if (err instanceof NoFallbackError && bubbleNoFallback) {
return {
finished: false
};
}
throw err;
}
}
};
const { useFileSystemPublicRoutes } = this.nextConfig;
if (useFileSystemPublicRoutes) {
this.dynamicRoutes = this.getDynamicRoutes();
if (!this.minimalMode) {
this.middleware = this.getMiddleware();
}
}
return {
headers,
fsRoutes,
rewrites: {
beforeFiles,
afterFiles,
fallback
},
redirects,
catchAllRoute,
catchAllMiddleware,
useFileSystemPublicRoutes,
dynamicRoutes: this.dynamicRoutes,
basePath: this.nextConfig.basePath,
pageChecker: this.hasPage.bind(this),
locales: ((ref = this.nextConfig.i18n) === null || ref === void 0 ? void 0 : ref.locales) || []
};
}
async getPagePath(pathname, locales) {
return (0, _require).getPagePath(pathname, this.distDir, this._isLikeServerless, this.renderOpts.dev, locales);
}
async hasPage(pathname) {
let found = false;
try {
var ref;
found = !!await this.getPagePath(pathname, (ref = this.nextConfig.i18n) === null || ref === void 0 ? void 0 : ref.locales);
} catch (_) {
}
return found;
}
async _beforeCatchAllRender(_req, _res, _params, _parsedUrl) {
return false;
}
// Used to build API page in development
async ensureApiPage(_pathname) {
}
/**
* Resolves `API` request, in development builds on demand
* @param req http request
* @param res http response
* @param pathname path of request
*/ async handleApiRequest(req, res, pathname, query) {
let page = pathname;
let params = false;
let pageFound = await this.hasPage(page);
if (!pageFound && this.dynamicRoutes) {
for (const dynamicRoute of this.dynamicRoutes){
params = dynamicRoute.match(pathname);
if (dynamicRoute.page.startsWith('/api') && params) {
page = dynamicRoute.page;
pageFound = true;
break;
}
}
}
if (!pageFound) {
return false;
}
// Make sure the page is built before getting the path
// or else it won't be in the manifest yet
await this.ensureApiPage(page);
let builtPagePath;
try {
builtPagePath = await this.getPagePath(page);
} catch (err) {
if ((0, _isError).default(err) && err.code === 'ENOENT') {
return false;
}
throw err;
}
const pageModule = await require(builtPagePath);
query = {
...query,
...params
};
delete query.__nextLocale;
delete query.__nextDefaultLocale;
if (!this.renderOpts.dev && this._isLikeServerless) {
if (typeof pageModule.default === 'function') {
prepareServerlessUrl(req, query);
await pageModule.default(req, res);
return true;
}
}
await (0, _apiUtils).apiResolver(req, res, query, pageModule, this.renderOpts.previewProps, this.minimalMode, this.renderOpts.dev, page);
return true;
}
generatePublicRoutes() {
const publicFiles = new Set((0, _recursiveReaddirSync).recursiveReadDirSync(this.publicDir).map((p)=>encodeURI(p.replace(/\\/g, '/'))
));
return [
{
match: (0, _router).route('/:path*'),
name: 'public folder catchall',
fn: async (req, res, params, parsedUrl)=>{
const pathParts = 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, idx)=>{
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, (0, _path).join(this.publicDir, ...pathParts), parsedUrl);
return {
finished: true
};
}
return {
finished: false
};
}
},
];
}
getDynamicRoutes() {
const addedPages = new Set();
return (0, _utils).getSortedRoutes(Object.keys(this.pagesManifest).map((page)=>{
var ref;
return (0, _normalizeLocalePath).normalizeLocalePath(page, (ref = this.nextConfig.i18n) === null || ref === void 0 ? void 0 : ref.locales).pathname;
})).map((page)=>{
if (addedPages.has(page) || !(0, _utils).isDynamicRoute(page)) return null;
addedPages.add(page);
return {
page,
match: (0, _utils).getRouteMatcher((0, _utils).getRouteRegex(page))
};
}).filter((item)=>Boolean(item)
);
}
handleCompression(req, res) {
if (this.compression) {
this.compression(req, res, ()=>{
});
}
}
async run(req, res, parsedUrl) {
this.handleCompression(req, r