@netlify/plugin-nextjs 4.37.4 → 4.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/constants.js CHANGED
@@ -3,15 +3,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.DIVIDER = exports.LAMBDA_MAX_SIZE = exports.LAMBDA_WARNING_SIZE = exports.MINIMUM_REVALIDATE_SECONDS = exports.DYNAMIC_PARAMETER_REGEX = exports.OPTIONAL_CATCH_ALL_REGEX = exports.CATCH_ALL_REGEX = exports.DEFAULT_FUNCTIONS_SRC = exports.HANDLER_FUNCTION_PATH = exports.ODB_FUNCTION_PATH = exports.HIDDEN_PATHS = exports.IMAGE_FUNCTION_TITLE = exports.ODB_FUNCTION_TITLE = exports.HANDLER_FUNCTION_TITLE = exports.NEXT_PLUGIN = exports.NEXT_PLUGIN_NAME = exports.IMAGE_FUNCTION_NAME = exports.ODB_FUNCTION_NAME = exports.HANDLER_FUNCTION_NAME = void 0;
6
+ exports.DIVIDER = exports.LAMBDA_MAX_SIZE = exports.LAMBDA_WARNING_SIZE = exports.MINIMUM_REVALIDATE_SECONDS = exports.DYNAMIC_PARAMETER_REGEX = exports.OPTIONAL_CATCH_ALL_REGEX = exports.CATCH_ALL_REGEX = exports.DEFAULT_FUNCTIONS_SRC = exports.HANDLER_FUNCTION_PATH = exports.ODB_FUNCTION_PATH = exports.HIDDEN_PATHS = exports.IMAGE_FUNCTION_TITLE = exports.API_FUNCTION_TITLE = exports.ODB_FUNCTION_TITLE = exports.HANDLER_FUNCTION_TITLE = exports.NEXT_PLUGIN = exports.NEXT_PLUGIN_NAME = exports.IMAGE_FUNCTION_NAME = exports.API_FUNCTION_NAME = exports.ODB_FUNCTION_NAME = exports.HANDLER_FUNCTION_NAME = void 0;
7
7
  const destr_1 = __importDefault(require("destr"));
8
8
  exports.HANDLER_FUNCTION_NAME = '___netlify-handler';
9
9
  exports.ODB_FUNCTION_NAME = '___netlify-odb-handler';
10
+ exports.API_FUNCTION_NAME = '___netlify-api-handler';
10
11
  exports.IMAGE_FUNCTION_NAME = '_ipx';
11
12
  exports.NEXT_PLUGIN_NAME = '@netlify/next-runtime';
12
13
  exports.NEXT_PLUGIN = '@netlify/plugin-nextjs';
13
14
  exports.HANDLER_FUNCTION_TITLE = 'Next.js SSR handler';
14
15
  exports.ODB_FUNCTION_TITLE = 'Next.js ISR handler';
16
+ exports.API_FUNCTION_TITLE = 'Next.js API handler';
15
17
  exports.IMAGE_FUNCTION_TITLE = 'next/image handler';
16
18
  // These are paths in .next that shouldn't be publicly accessible
17
19
  exports.HIDDEN_PATHS = (0, destr_1.default)(process.env.NEXT_KEEP_METADATA_FILES)
@@ -64,6 +64,11 @@ const hasManuallyAddedModule = ({ netlifyConfig, moduleName, }) =>
64
64
  Object.values(netlifyConfig.functions).some(({ included_files = [] }) => included_files.some((inc) => inc.includes(`node_modules/${moduleName}`)));
65
65
  exports.hasManuallyAddedModule = hasManuallyAddedModule;
66
66
  /* eslint-enable camelcase */
67
+ /**
68
+ * Transforms `/api/shows/[id].js` into `/api/shows/*id*.js`,
69
+ * so that the file `[id].js` is matched correctly.
70
+ */
71
+ const escapeGlob = (path) => path.replace(/\[/g, '*').replace(/]/g, '*');
67
72
  const configureHandlerFunctions = async ({ netlifyConfig, publish, ignore = [], apiLambdas, splitApiRoutes, }) => {
68
73
  var _a, _b, _c;
69
74
  const config = await (0, exports.getRequiredServerFiles)(publish);
@@ -100,7 +105,7 @@ const configureHandlerFunctions = async ({ netlifyConfig, publish, ignore = [],
100
105
  (_b = netlifyConfig.functions)[functionName] || (_b[functionName] = { included_files: [] });
101
106
  netlifyConfig.functions[functionName].node_bundler = 'none';
102
107
  (_c = netlifyConfig.functions[functionName]).included_files || (_c.included_files = []);
103
- netlifyConfig.functions[functionName].included_files.push(...includedFiles);
108
+ netlifyConfig.functions[functionName].included_files.push(...includedFiles.map(escapeGlob));
104
109
  }
105
110
  }
106
111
  else {
@@ -7,13 +7,10 @@ exports.onPreDev = void 0;
7
7
  const path_1 = require("path");
8
8
  const execa_1 = __importDefault(require("execa"));
9
9
  const edge_1 = require("./edge");
10
- const files_1 = require("./files");
11
10
  // The types haven't been updated yet
12
11
  const onPreDev = async ({ constants, netlifyConfig }) => {
13
12
  var _a;
14
13
  const base = (_a = netlifyConfig.build.base) !== null && _a !== void 0 ? _a : process.cwd();
15
- // Need to patch the files, because build might not have been run
16
- await (0, files_1.patchNextFiles)(base);
17
14
  await (0, edge_1.writeDevEdgeFunction)(constants);
18
15
  // Don't await this or it will never finish
19
16
  execa_1.default.node((0, path_1.resolve)(__dirname, '..', '..', 'lib', 'helpers', 'middlewareWatcher.js'), [base, process.env.NODE_ENV === 'test' ? '--once' : ''], {
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.writeEdgeFunctions = exports.getEdgeFunctionPatternForPage = exports.generateRscDataEdgeManifest = exports.writeDevEdgeFunction = exports.cleanupEdgeFunctions = exports.loadPrerenderManifest = exports.loadAppPathRoutesManifest = exports.loadMiddlewareManifest = exports.isAppDirRoute = void 0;
6
+ exports.writeEdgeFunctions = exports.getEdgeFunctionPatternForPage = exports.generateRscDataEdgeManifest = exports.writeDevEdgeFunction = exports.cleanupEdgeFunctions = exports.sanitizeEdgePath = exports.loadPrerenderManifest = exports.loadAppPathRoutesManifest = exports.loadMiddlewareManifest = exports.isAppDirRoute = void 0;
7
7
  const fs_1 = require("fs");
8
8
  const path_1 = require("path");
9
9
  const chalk_1 = require("chalk");
@@ -31,6 +31,11 @@ exports.loadPrerenderManifest = loadPrerenderManifest;
31
31
  * Convert the Next middleware name into a valid Edge Function name
32
32
  */
33
33
  const sanitizeName = (name) => `next_${name.replace(/\W/g, '_')}`;
34
+ /**
35
+ * Convert the images path to strip the origin (until domain-level Edge functions are supported)
36
+ */
37
+ const sanitizeEdgePath = (imagesPath) => new URL(imagesPath, process.env.URL || 'http://n').pathname;
38
+ exports.sanitizeEdgePath = sanitizeEdgePath;
34
39
  // Slightly different spacing in different versions!
35
40
  const IMPORT_UNSUPPORTED = [
36
41
  `Object.defineProperty(globalThis,"__import_unsupported"`,
@@ -301,7 +306,7 @@ const writeEdgeFunctions = async ({ netlifyConfig, routesManifest, }) => {
301
306
  manifest.functions.push({
302
307
  function: 'ipx',
303
308
  name: 'next/image handler',
304
- path: nextConfig.images.path || '/_next/image',
309
+ path: nextConfig.images.path ? (0, exports.sanitizeEdgePath)(nextConfig.images.path) : '/_next/image',
305
310
  generator,
306
311
  });
307
312
  manifest.layers.push({
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.removeMetadataFiles = exports.movePublicFiles = exports.unpatchNextFiles = exports.unpatchFile = exports.patchNextFiles = exports.getDependenciesOfFile = exports.getSourceFileForPage = exports.getServerFile = exports.moveStaticPages = exports.getMiddleware = exports.matchesRewrite = exports.matchesRedirect = exports.matchMiddleware = exports.stripLocale = exports.isDynamicRoute = void 0;
6
+ exports.removeMetadataFiles = exports.movePublicFiles = exports.getDependenciesOfFile = exports.getSourceFileForPage = exports.getServerFile = exports.moveStaticPages = exports.getMiddleware = exports.matchesRewrite = exports.matchesRedirect = exports.matchMiddleware = exports.stripLocale = exports.isDynamicRoute = void 0;
7
7
  const os_1 = require("os");
8
8
  const chalk_1 = require("chalk");
9
9
  const fs_extra_1 = require("fs-extra");
@@ -236,36 +236,6 @@ const moveStaticPages = async ({ netlifyConfig, target, i18n, basePath, }) => {
236
236
  }
237
237
  };
238
238
  exports.moveStaticPages = moveStaticPages;
239
- const PATCH_WARNING = `/* File patched by Netlify */`;
240
- /**
241
- * Attempt to patch a source file, preserving a backup
242
- */
243
- const patchFile = async ({ file, replacements, }) => {
244
- if (!(0, fs_extra_1.existsSync)(file)) {
245
- console.warn('File was not found');
246
- return false;
247
- }
248
- let content = await (0, fs_extra_1.readFile)(file, 'utf8');
249
- // If the file has already been patched, patch the backed-up original instead
250
- if (content.includes(PATCH_WARNING) && (0, fs_extra_1.existsSync)(`${file}.orig`)) {
251
- content = await (0, fs_extra_1.readFile)(`${file}.orig`, 'utf8');
252
- }
253
- const newContent = replacements.reduce((acc, [from, to]) => {
254
- if (acc.includes(to)) {
255
- console.log('Already patched. Skipping.');
256
- return acc;
257
- }
258
- return acc.replace(from, to);
259
- }, content);
260
- if (newContent === content) {
261
- console.warn('File was not changed');
262
- return false;
263
- }
264
- await (0, fs_extra_1.writeFile)(`${file}.orig`, content);
265
- await (0, fs_extra_1.writeFile)(file, `${newContent}\n${PATCH_WARNING}`);
266
- console.log('Done');
267
- return true;
268
- };
269
239
  /**
270
240
  * The file we need has moved around a bit over the past few versions,
271
241
  * so we iterate through the options until we find it
@@ -312,73 +282,6 @@ const getDependenciesOfFile = async (file) => {
312
282
  return dependencies.files.map((dep) => (0, pathe_1.resolve)((0, pathe_1.dirname)(file), dep));
313
283
  };
314
284
  exports.getDependenciesOfFile = getDependenciesOfFile;
315
- const baseServerReplacements = [
316
- // force manual revalidate during cache fetches
317
- [
318
- `checkIsManualRevalidate(req, this.renderOpts.previewProps)`,
319
- `checkIsManualRevalidate(process.env._REVALIDATE_SSG ? { headers: { 'x-prerender-revalidate': this.renderOpts.previewProps.previewModeId } } : req, this.renderOpts.previewProps)`,
320
- ],
321
- // In https://github.com/vercel/next.js/pull/47803 checkIsManualRevalidate was renamed to checkIsOnDemandRevalidate
322
- [
323
- `checkIsOnDemandRevalidate(req, this.renderOpts.previewProps)`,
324
- `checkIsOnDemandRevalidate(process.env._REVALIDATE_SSG ? { headers: { 'x-prerender-revalidate': this.renderOpts.previewProps.previewModeId } } : req, this.renderOpts.previewProps)`,
325
- ],
326
- // ensure ISR 404 pages send the correct SWR cache headers
327
- [`private: isPreviewMode || is404Page && cachedData`, `private: isPreviewMode && cachedData`],
328
- ];
329
- const nextServerReplacements = [
330
- [
331
- `getMiddlewareManifest() {\n if (this.minimalMode) return null;`,
332
- `getMiddlewareManifest() {\n if (this.minimalMode || (process.env.NEXT_DISABLE_NETLIFY_EDGE !== 'true' && process.env.NEXT_DISABLE_NETLIFY_EDGE !== '1')) return null;`,
333
- ],
334
- [
335
- `generateCatchAllMiddlewareRoute(devReady) {\n if (this.minimalMode) return []`,
336
- `generateCatchAllMiddlewareRoute(devReady) {\n if (this.minimalMode || (process.env.NEXT_DISABLE_NETLIFY_EDGE !== 'true' && process.env.NEXT_DISABLE_NETLIFY_EDGE !== '1')) return [];`,
337
- ],
338
- [
339
- `generateCatchAllMiddlewareRoute() {\n if (this.minimalMode) return undefined;`,
340
- `generateCatchAllMiddlewareRoute() {\n if (this.minimalMode || (process.env.NEXT_DISABLE_NETLIFY_EDGE !== 'true' && process.env.NEXT_DISABLE_NETLIFY_EDGE !== '1')) return undefined;`,
341
- ],
342
- [
343
- `getMiddlewareManifest() {\n if (this.minimalMode) {`,
344
- `getMiddlewareManifest() {\n if (!this.minimalMode && (process.env.NEXT_DISABLE_NETLIFY_EDGE === 'true' || process.env.NEXT_DISABLE_NETLIFY_EDGE === '1')) {`,
345
- ],
346
- ];
347
- const patchNextFiles = async (root) => {
348
- const baseServerFile = (0, exports.getServerFile)(root);
349
- console.log(`Patching ${baseServerFile}`);
350
- if (baseServerFile) {
351
- await patchFile({
352
- file: baseServerFile,
353
- replacements: baseServerReplacements,
354
- });
355
- }
356
- const nextServerFile = (0, exports.getServerFile)(root, false);
357
- console.log(`Patching ${nextServerFile}`);
358
- if (nextServerFile) {
359
- await patchFile({
360
- file: nextServerFile,
361
- replacements: nextServerReplacements,
362
- });
363
- }
364
- };
365
- exports.patchNextFiles = patchNextFiles;
366
- const unpatchFile = async (file) => {
367
- const origFile = `${file}.orig`;
368
- if ((0, fs_extra_1.existsSync)(origFile)) {
369
- await (0, fs_extra_1.move)(origFile, file, { overwrite: true });
370
- }
371
- };
372
- exports.unpatchFile = unpatchFile;
373
- const unpatchNextFiles = async (root) => {
374
- const baseServerFile = (0, exports.getServerFile)(root);
375
- await (0, exports.unpatchFile)(baseServerFile);
376
- const nextServerFile = (0, exports.getServerFile)(root, false);
377
- if (nextServerFile !== baseServerFile) {
378
- await (0, exports.unpatchFile)(nextServerFile);
379
- }
380
- };
381
- exports.unpatchNextFiles = unpatchNextFiles;
382
285
  const movePublicFiles = async ({ appDir, outdir, publish, basePath, }) => {
383
286
  // `outdir` is a config property added when using Next.js with Nx. It's typically
384
287
  // a relative path outside of the appDir, e.g. '../../dist/apps/<app-name>', and
@@ -31,7 +31,7 @@ const generateFunctions = async ({ FUNCTIONS_SRC = constants_1.DEFAULT_FUNCTIONS
31
31
  ? (0, pathe_1.relative)(functionDir, nextServerModuleAbsoluteLocation)
32
32
  : undefined;
33
33
  for (const apiLambda of apiLambdas) {
34
- const { functionName, routes, type, includedFiles } = apiLambda;
34
+ const { functionName, functionTitle, routes, type, includedFiles } = apiLambda;
35
35
  const apiHandlerSource = (0, getApiHandler_1.getApiHandler)({
36
36
  // most api lambdas serve multiple routes, but scheduled functions need to be in separate lambdas.
37
37
  // so routes[0] is safe to access.
@@ -46,6 +46,7 @@ const generateFunctions = async ({ FUNCTIONS_SRC = constants_1.DEFAULT_FUNCTIONS
46
46
  // copy handler dependencies (VercelNodeBridge, NetlifyNextServer, etc.)
47
47
  await (0, fs_extra_1.copyFile)(node_bridge_1.default, (0, pathe_1.join)(functionsDir, functionName, 'bridge.js'));
48
48
  await (0, fs_extra_1.copyFile)((0, pathe_1.join)(__dirname, '..', '..', 'lib', 'templates', 'server.js'), (0, pathe_1.join)(functionsDir, functionName, 'server.js'));
49
+ await (0, fs_extra_1.copyFile)((0, pathe_1.join)(__dirname, '..', '..', 'lib', 'templates', 'requireHooks.js'), (0, pathe_1.join)(functionsDir, functionName, 'requireHooks.js'));
49
50
  await (0, fs_extra_1.copyFile)((0, pathe_1.join)(__dirname, '..', '..', 'lib', 'templates', 'handlerUtils.js'), (0, pathe_1.join)(functionsDir, functionName, 'handlerUtils.js'));
50
51
  const resolveSourceFile = (file) => (0, pathe_1.join)(publish, 'server', file);
51
52
  // TODO: this should be unneeded once we use the `none` bundler everywhere
@@ -60,6 +61,7 @@ const generateFunctions = async ({ FUNCTIONS_SRC = constants_1.DEFAULT_FUNCTIONS
60
61
  ].map(resolveSourceFile),
61
62
  });
62
63
  await (0, fs_extra_1.writeFile)((0, pathe_1.join)(functionsDir, functionName, 'pages.js'), resolverSource);
64
+ await (0, functionsMetaData_1.writeFunctionConfiguration)({ functionName, functionTitle, functionsDir });
63
65
  const nfInternalFiles = await (0, tiny_glob_1.default)((0, pathe_1.join)(functionsDir, functionName, '**'));
64
66
  includedFiles.push(...nfInternalFiles);
65
67
  }
@@ -76,8 +78,9 @@ const generateFunctions = async ({ FUNCTIONS_SRC = constants_1.DEFAULT_FUNCTIONS
76
78
  // copy handler dependencies (VercelNodeBridge, NetlifyNextServer, etc.)
77
79
  await (0, fs_extra_1.copyFile)(node_bridge_1.default, (0, pathe_1.join)(functionsDir, functionName, 'bridge.js'));
78
80
  await (0, fs_extra_1.copyFile)((0, pathe_1.join)(__dirname, '..', '..', 'lib', 'templates', 'server.js'), (0, pathe_1.join)(functionsDir, functionName, 'server.js'));
81
+ await (0, fs_extra_1.copyFile)((0, pathe_1.join)(__dirname, '..', '..', 'lib', 'templates', 'requireHooks.js'), (0, pathe_1.join)(functionsDir, functionName, 'requireHooks.js'));
79
82
  await (0, fs_extra_1.copyFile)((0, pathe_1.join)(__dirname, '..', '..', 'lib', 'templates', 'handlerUtils.js'), (0, pathe_1.join)(functionsDir, functionName, 'handlerUtils.js'));
80
- (0, functionsMetaData_1.writeFunctionConfiguration)({ functionName, functionTitle, functionsDir });
83
+ await (0, functionsMetaData_1.writeFunctionConfiguration)({ functionName, functionTitle, functionsDir });
81
84
  };
82
85
  await writeHandler(constants_1.HANDLER_FUNCTION_NAME, constants_1.HANDLER_FUNCTION_TITLE, false);
83
86
  await writeHandler(constants_1.ODB_FUNCTION_NAME, constants_1.ODB_FUNCTION_TITLE, true);
@@ -221,20 +224,45 @@ const getBundleWeight = async (patterns) => {
221
224
  }));
222
225
  return sum(sizes.flat(1));
223
226
  };
224
- const MB = 1024 * 1024;
225
227
  const getAPILambdas = async (publish, baseDir, pageExtensions) => {
226
228
  const commonDependencies = await (0, exports.getAPIPRouteCommonDependencies)(publish);
227
- const threshold = 50 * MB - (await getBundleWeight(commonDependencies));
229
+ const threshold = constants_1.LAMBDA_WARNING_SIZE - (await getBundleWeight(commonDependencies));
228
230
  const apiRoutes = await (0, exports.getApiRouteConfigs)(publish, baseDir, pageExtensions);
229
231
  const packFunctions = async (routes, type) => {
230
232
  const weighedRoutes = await Promise.all(routes.map(async (route) => ({ value: route, weight: await getBundleWeight(route.includedFiles) })));
231
233
  const bins = (0, pack_1.pack)(weighedRoutes, threshold);
232
- return bins.map((bin, index) => ({
233
- functionName: bin.length === 1 ? bin[0].functionName : `api-${index}`,
234
- routes: bin,
235
- includedFiles: [...commonDependencies, ...routes.flatMap((route) => route.includedFiles)],
236
- type,
237
- }));
234
+ return bins.map((bin) => {
235
+ if (bin.length === 1) {
236
+ const [func] = bin;
237
+ const { functionName, functionTitle, config, includedFiles } = func;
238
+ return {
239
+ functionName,
240
+ functionTitle,
241
+ routes: [func],
242
+ includedFiles: [...commonDependencies, ...includedFiles],
243
+ type: config.type,
244
+ };
245
+ }
246
+ const includedFiles = [...commonDependencies, ...bin.flatMap((route) => route.includedFiles)];
247
+ const nonSingletonBins = bins.filter((b) => b.length > 1);
248
+ if (nonSingletonBins.length === 1) {
249
+ return {
250
+ functionName: constants_1.API_FUNCTION_NAME,
251
+ functionTitle: constants_1.API_FUNCTION_TITLE,
252
+ includedFiles,
253
+ routes: bin,
254
+ type,
255
+ };
256
+ }
257
+ const indexInNonSingletonBins = nonSingletonBins.indexOf(bin);
258
+ return {
259
+ functionName: `${constants_1.API_FUNCTION_NAME}-${indexInNonSingletonBins + 1}`,
260
+ functionTitle: `${constants_1.API_FUNCTION_TITLE} ${indexInNonSingletonBins + 1}/${nonSingletonBins.length}`,
261
+ includedFiles,
262
+ routes: bin,
263
+ type,
264
+ };
265
+ });
238
266
  };
239
267
  const standardFunctions = apiRoutes.filter((route) => !(0, analysis_1.isEdgeConfig)(route.config.runtime) &&
240
268
  route.config.type !== "experimental-background" /* ApiRouteType.BACKGROUND */ &&
@@ -263,12 +291,14 @@ const getApiRouteConfigs = async (publish, appDir, pageExtensions) => {
263
291
  const filePath = (0, files_1.getSourceFileForPage)(apiRoute, [pagesDir, srcPagesDir], pageExtensions);
264
292
  const config = await (0, analysis_1.extractConfigFromFile)(filePath, appDir);
265
293
  const functionName = (0, utils_1.getFunctionNameForPage)(apiRoute, config.type === "experimental-background" /* ApiRouteType.BACKGROUND */);
294
+ const functionTitle = `${constants_1.API_FUNCTION_TITLE} ${apiRoute}`;
266
295
  const compiled = pages[apiRoute];
267
296
  const compiledPath = (0, pathe_1.join)(publish, 'server', compiled);
268
297
  const routeDependencies = await (0, files_1.getDependenciesOfFile)(compiledPath);
269
298
  const includedFiles = [compiledPath, ...routeDependencies];
270
299
  return {
271
300
  functionName,
301
+ functionTitle,
272
302
  route: apiRoute,
273
303
  config,
274
304
  compiled,
@@ -288,6 +318,7 @@ const getExtendedApiRouteConfigs = async (publish, appDir, pageExtensions) => {
288
318
  exports.getExtendedApiRouteConfigs = getExtendedApiRouteConfigs;
289
319
  const packSingleFunction = (func) => ({
290
320
  functionName: func.functionName,
321
+ functionTitle: func.functionTitle,
291
322
  includedFiles: func.includedFiles,
292
323
  routes: [func],
293
324
  type: func.config.type,
package/lib/index.js CHANGED
@@ -51,6 +51,7 @@ const plugin = {
51
51
  });
52
52
  await (0, edge_1.cleanupEdgeFunctions)(constants);
53
53
  const middlewareManifest = await (0, edge_1.loadMiddlewareManifest)(netlifyConfig);
54
+ const config = await (0, config_1.getRequiredServerFiles)(publish);
54
55
  if ((middlewareManifest === null || middlewareManifest === void 0 ? void 0 : middlewareManifest.functions) &&
55
56
  Object.keys(middlewareManifest.functions).length !== 0 &&
56
57
  (0, destr_1.default)(process.env.NEXT_DISABLE_NETLIFY_EDGE)) {
@@ -70,7 +71,6 @@ const plugin = {
70
71
  `));
71
72
  }
72
73
  if ((0, utils_1.isNextAuthInstalled)()) {
73
- const config = await (0, config_1.getRequiredServerFiles)(publish);
74
74
  const userDefinedNextAuthUrl = config.config.env.NEXTAUTH_URL;
75
75
  if (userDefinedNextAuthUrl) {
76
76
  console.log(`NextAuth package detected, NEXTAUTH_URL environment variable set by user in next.config.js to ${userDefinedNextAuthUrl}`);
@@ -105,7 +105,6 @@ const plugin = {
105
105
  splitApiRoutes: (0, flags_1.splitApiRoutes)(featureFlags, publish),
106
106
  });
107
107
  await (0, files_1.movePublicFiles)({ appDir, outdir, publish, basePath });
108
- await (0, files_1.patchNextFiles)(appDir);
109
108
  if (!(0, destr_1.default)(process.env.SERVE_STATIC_FILES_FROM_ORIGIN)) {
110
109
  await (0, files_1.moveStaticPages)({ target, netlifyConfig, i18n, basePath });
111
110
  }
@@ -11,6 +11,7 @@ const path = require('path');
11
11
  const { URLSearchParams, URL } = require('url');
12
12
  const { Bridge } = require('@vercel/node-bridge/bridge');
13
13
  const { augmentFsModule, getMaxAge, getMultiValueHeaders, getPrefetchResponse, normalizePath, } = require('./handlerUtils');
14
+ const { overrideRequireHooks, applyRequireHooks } = require('./requireHooks');
14
15
  const { getNetlifyNextServer } = require('./server');
15
16
  // We return a function and then call `toString()` on it to serialise it as the launcher function
16
17
  // eslint-disable-next-line max-lines-per-function
@@ -28,14 +29,14 @@ const makeHandler = ({ conf, app, pageRoot, NextServer, staticManifest = [], mod
28
29
  require.resolve('./pages.js');
29
30
  }
30
31
  catch { }
32
+ // Next 13.4 conditionally uses different React versions and we need to make sure we use the same one
33
+ overrideRequireHooks(conf);
31
34
  const NetlifyNextServer = getNetlifyNextServer(NextServer);
35
+ applyRequireHooks();
32
36
  const ONE_YEAR_IN_SECONDS = 31536000;
33
37
  (_a = process.env).NODE_ENV || (_a.NODE_ENV = 'production');
34
38
  // We don't want to write ISR files to disk in the lambda environment
35
39
  conf.experimental.isrFlushToDisk = false;
36
- // This is our flag that we use when patching the source
37
- // eslint-disable-next-line no-underscore-dangle
38
- process.env._REVALIDATE_SSG = 'true';
39
40
  for (const [key, value] of Object.entries(conf.env)) {
40
41
  process.env[key] = String(value);
41
42
  }
@@ -152,9 +153,9 @@ const getHandler = ({ isODB = false, publishDir = '../../../.next', appDir = '..
152
153
  // We copy the file here rather than requiring from the node module
153
154
  const { Bridge } = require("./bridge");
154
155
  const { augmentFsModule, getMaxAge, getMultiValueHeaders, getPrefetchResponse, normalizePath } = require('./handlerUtils')
155
- const { getNetlifyNextServer } = require('./server')
156
+ const { overrideRequireHooks, applyRequireHooks } = require("./requireHooks")
157
+ const { getNetlifyNextServer } = require("./server")
156
158
  const NextServer = require(${JSON.stringify(nextServerModuleRelativeLocation)}).default
157
-
158
159
  ${isODB ? `const { builder } = require("@netlify/functions")` : ''}
159
160
  const { config } = require("${publishDir}/required-server-files.json")
160
161
  let staticManifest
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.localizeDataRoute = exports.localizeRoute = exports.unlocalizeRoute = exports.normalizeRoute = exports.netlifyApiFetch = exports.normalizePath = exports.getPrefetchResponse = exports.augmentFsModule = exports.getMultiValueHeaders = exports.getMaxAge = exports.downloadFile = void 0;
6
+ exports.localizeDataRoute = exports.localizeRoute = exports.unlocalizeRoute = exports.joinPaths = exports.normalizeRoute = exports.netlifyApiFetch = exports.normalizePath = exports.getPrefetchResponse = exports.augmentFsModule = exports.getMultiValueHeaders = exports.getMaxAge = exports.downloadFile = void 0;
7
7
  const fs_1 = require("fs");
8
8
  const os_1 = require("os");
9
9
  const path_1 = __importDefault(require("path"));
@@ -203,6 +203,9 @@ exports.netlifyApiFetch = netlifyApiFetch;
203
203
  // Remove trailing slash from a route (except for the root route)
204
204
  const normalizeRoute = (route) => (route.endsWith('/') ? route.slice(0, -1) || '/' : route);
205
205
  exports.normalizeRoute = normalizeRoute;
206
+ // Join multiple paths together, ensuring that there is only one slash between them
207
+ const joinPaths = (...paths) => paths.reduce((a, b) => (a.endsWith('/') ? `${a}${b}` : `${a}/${b}`));
208
+ exports.joinPaths = joinPaths;
206
209
  // Check if a route has a locale prefix (including the root route)
207
210
  const isLocalized = (route, i18n) => i18n.locales.some((locale) => route === `/${locale}` || route.startsWith(`/${locale}/`));
208
211
  // Remove the locale prefix from a route (if any)
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ /* eslint-disable n/no-extraneous-require, no-underscore-dangle, @typescript-eslint/no-explicit-any */
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.applyRequireHooks = exports.overrideRequireHooks = void 0;
8
+ // This is a modified version of the require hooks from Next.js
9
+ // https://github.com/vercel/next.js/blob/b04c70573ac199a9bb3ea42201e0865e610d5b67/packages/next/src/server/require-hook.ts
10
+ const module_1 = __importDefault(require("module"));
11
+ const resolveFilename = module_1.default._resolveFilename;
12
+ const requireHooks = new Map();
13
+ const overrideRequireHooks = (config) => {
14
+ // we may have changed the working directory in the handler
15
+ const opts = {
16
+ paths: [process.cwd()],
17
+ };
18
+ requireHooks.set('default', new Map([
19
+ ['react', require.resolve(`react`, opts)],
20
+ ['react/jsx-runtime', require.resolve(`react/jsx-runtime`, opts)],
21
+ ]));
22
+ if (config.experimental.appDir) {
23
+ requireHooks.set('next', new Map([
24
+ ['react', require.resolve(`next/dist/compiled/react`, opts)],
25
+ ['react/jsx-runtime', require.resolve(`next/dist/compiled/react/jsx-runtime`, opts)],
26
+ ['react/jsx-dev-runtime', require.resolve(`next/dist/compiled/react/jsx-dev-runtime`, opts)],
27
+ ['react-dom', require.resolve(`next/dist/compiled/react-dom/server-rendering-stub`, opts)],
28
+ ['react-dom/client', require.resolve(`next/dist/compiled/react-dom/client`, opts)],
29
+ ['react-dom/server', require.resolve(`next/dist/compiled/react-dom/server`, opts)],
30
+ ['react-dom/server.browser', require.resolve(`next/dist/compiled/react-dom/server.browser`, opts)],
31
+ ['react-dom/server.edge', require.resolve(`next/dist/compiled/react-dom/server.edge`, opts)],
32
+ [
33
+ 'react-server-dom-webpack/client',
34
+ require.resolve(`next/dist/compiled/react-server-dom-webpack/client`, opts),
35
+ ],
36
+ [
37
+ 'react-server-dom-webpack/client.edge',
38
+ require.resolve(`next/dist/compiled/react-server-dom-webpack/client.edge`, opts),
39
+ ],
40
+ [
41
+ 'react-server-dom-webpack/server.edge',
42
+ require.resolve(`next/dist/compiled/react-server-dom-webpack/server.edge`, opts),
43
+ ],
44
+ [
45
+ 'react-server-dom-webpack/server.node',
46
+ require.resolve(`next/dist/compiled/react-server-dom-webpack/server.node`, opts),
47
+ ],
48
+ ['styled-jsx', require.resolve('styled-jsx', opts)],
49
+ ['styled-jsx/style', require.resolve('styled-jsx/style', opts)],
50
+ ]));
51
+ }
52
+ if (config.experimental.serverActions) {
53
+ requireHooks.set('experimental', new Map([
54
+ ['react', require.resolve(`next/dist/compiled/react-experimental`, opts)],
55
+ ['react/jsx-runtime', require.resolve(`next/dist/compiled/react-experimental/jsx-runtime`, opts)],
56
+ ['react/jsx-dev-runtime', require.resolve(`next/dist/compiled/react-experimental/jsx-dev-runtime`, opts)],
57
+ ['react-dom', require.resolve(`next/dist/compiled/react-dom-experimental/server-rendering-stub`, opts)],
58
+ ['react-dom/client', require.resolve(`next/dist/compiled/react-dom-experimental/client`, opts)],
59
+ ['react-dom/server', require.resolve(`next/dist/compiled/react-dom-experimental/server`, opts)],
60
+ ['react-dom/server.browser', require.resolve(`next/dist/compiled/react-dom-experimental/server.browser`, opts)],
61
+ ['react-dom/server.edge', require.resolve(`next/dist/compiled/react-dom-experimental/server.edge`, opts)],
62
+ [
63
+ 'react-server-dom-webpack/client',
64
+ require.resolve(`next/dist/compiled/react-server-dom-webpack-experimental/client`, opts),
65
+ ],
66
+ [
67
+ 'react-server-dom-webpack/client.edge',
68
+ require.resolve(`next/dist/compiled/react-server-dom-webpack-experimental/client.edge`, opts),
69
+ ],
70
+ [
71
+ 'react-server-dom-webpack/server.edge',
72
+ require.resolve(`next/dist/compiled/react-server-dom-webpack-experimental/server.edge`, opts),
73
+ ],
74
+ [
75
+ 'react-server-dom-webpack/server.node',
76
+ require.resolve(`next/dist/compiled/react-server-dom-webpack-experimental/server.node`, opts),
77
+ ],
78
+ ['styled-jsx', require.resolve('styled-jsx', opts)],
79
+ ['styled-jsx/style', require.resolve('styled-jsx/style', opts)],
80
+ ]));
81
+ }
82
+ };
83
+ exports.overrideRequireHooks = overrideRequireHooks;
84
+ const applyRequireHooks = () => {
85
+ // eslint-disable-next-line max-params, func-names
86
+ ;
87
+ module_1.default._resolveFilename = function (originalResolveFilename, hooks, request, parent, isMain, options) {
88
+ var _a, _b, _c;
89
+ const reactMode = (_a = process.env.__NEXT_PRIVATE_PREBUNDLED_REACT) !== null && _a !== void 0 ? _a : 'default';
90
+ const resolvedRequest = (_c = (_b = hooks.get(reactMode)) === null || _b === void 0 ? void 0 : _b.get(request)) !== null && _c !== void 0 ? _c : request;
91
+ return originalResolveFilename.call(module_1.default, resolvedRequest, parent, isMain, options);
92
+ // We use `bind` here to avoid referencing outside variables to create potential memory leaks.
93
+ }.bind(null, resolveFilename, requireHooks);
94
+ };
95
+ exports.applyRequireHooks = applyRequireHooks;
96
+ /* eslint-enable n/no-extraneous-require, no-underscore-dangle, @typescript-eslint/no-explicit-any */
@@ -18,8 +18,12 @@ const getNetlifyNextServer = (NextServer) => {
18
18
  getRequestHandler() {
19
19
  const handler = super.getRequestHandler();
20
20
  return async (req, res, parsedUrl) => {
21
+ var _a;
21
22
  // preserve the URL before Next.js mutates it for i18n
22
23
  const { url, headers } = req;
24
+ // conditionally use the prebundled React module
25
+ this.netlifyPrebundleReact(url);
26
+ // intercept on-demand revalidation requests and handle with the Netlify API
23
27
  if (headers['x-prerender-revalidate'] && this.netlifyConfig.revalidateToken) {
24
28
  // handle on-demand revalidation by purging the ODB cache
25
29
  await this.netlifyRevalidate(url);
@@ -27,12 +31,40 @@ const getNetlifyNextServer = (NextServer) => {
27
31
  res.statusCode = 200;
28
32
  res.setHeader('x-nextjs-cache', 'REVALIDATED');
29
33
  res.send();
34
+ return;
30
35
  }
31
- else {
32
- return handler(req, res, parsedUrl);
36
+ // force Next to revalidate all requests so that we always have fresh content
37
+ // for our ODBs and middleware is disabled at the origin
38
+ // but ignore in preview mode (prerender_bypass is set to true in preview mode)
39
+ // because otherwise revalidate will override preview mode
40
+ if (!((_a = headers.cookie) === null || _a === void 0 ? void 0 : _a.includes('__prerender_bypass'))) {
41
+ // this header controls whether Next.js will revalidate the page
42
+ // and needs to be set to the preview mode id to enable it
43
+ headers['x-prerender-revalidate'] = this.renderOpts.previewProps.previewModeId;
33
44
  }
45
+ return handler(req, res, parsedUrl);
34
46
  };
35
47
  }
48
+ // doing what they do in https://github.com/vercel/vercel/blob/1663db7ca34d3dd99b57994f801fb30b72fbd2f3/packages/next/src/server-build.ts#L576-L580
49
+ netlifyPrebundleReact(path) {
50
+ var _a;
51
+ const routesManifest = this.getRoutesManifest();
52
+ const appPathsManifest = this.getAppPathsManifest();
53
+ const routes = [...routesManifest.staticRoutes, ...routesManifest.dynamicRoutes];
54
+ const matchedRoute = routes.find((route) => new RegExp(route.regex).test(path));
55
+ const isAppRoute = appPathsManifest && matchedRoute ? appPathsManifest[(0, handlerUtils_1.joinPaths)(matchedRoute.page, 'page')] : false;
56
+ if (isAppRoute) {
57
+ // app routes should use prebundled React
58
+ // eslint-disable-next-line no-underscore-dangle
59
+ process.env.__NEXT_PRIVATE_PREBUNDLED_REACT = ((_a = this.nextConfig.experimental) === null || _a === void 0 ? void 0 : _a.serverActions)
60
+ ? 'experimental'
61
+ : 'next';
62
+ return;
63
+ }
64
+ // pages routes should use use node_modules React
65
+ // eslint-disable-next-line no-underscore-dangle
66
+ process.env.__NEXT_PRIVATE_PREBUNDLED_REACT = '';
67
+ }
36
68
  async netlifyRevalidate(route) {
37
69
  try {
38
70
  // call netlify API to revalidate the path
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/plugin-nextjs",
3
- "version": "4.37.4",
3
+ "version": "4.38.0",
4
4
  "description": "Run Next.js seamlessly on Netlify",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -37,12 +37,12 @@
37
37
  },
38
38
  "devDependencies": {
39
39
  "@delucis/if-env": "^1.1.2",
40
- "@netlify/build": "^29.11.6",
40
+ "@netlify/build": "^29.12.7",
41
41
  "@types/fs-extra": "^9.0.13",
42
42
  "@types/jest": "^27.4.1",
43
43
  "@types/merge-stream": "^1.1.2",
44
44
  "@types/node": "^17.0.25",
45
- "next": "^13.3.0",
45
+ "next": "^13.4.1",
46
46
  "npm-run-all": "^4.1.5",
47
47
  "typescript": "^4.6.3"
48
48
  },