@netlify/plugin-nextjs 4.29.3 → 4.29.4

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.
@@ -154,4 +154,3 @@ const generateCustomHeaders = (nextConfig, netlifyHeaders = []) => {
154
154
  }
155
155
  };
156
156
  exports.generateCustomHeaders = generateCustomHeaders;
157
- /* eslint-enable max-lines */
@@ -3,8 +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.writeDevEdgeFunction = exports.cleanupEdgeFunctions = exports.loadMiddlewareManifest = void 0;
7
- /* eslint-disable max-lines */
6
+ exports.writeEdgeFunctions = exports.writeDevEdgeFunction = exports.cleanupEdgeFunctions = exports.loadAppPathRoutesManifest = exports.loadMiddlewareManifest = void 0;
8
7
  const fs_1 = require("fs");
9
8
  const path_1 = require("path");
10
9
  const chalk_1 = require("chalk");
@@ -13,14 +12,15 @@ const fs_extra_1 = require("fs-extra");
13
12
  const outdent_1 = require("outdent");
14
13
  const config_1 = require("./config");
15
14
  const matchers_1 = require("./matchers");
16
- const loadMiddlewareManifest = (netlifyConfig) => {
17
- const middlewarePath = (0, path_1.resolve)(netlifyConfig.build.publish, 'server', 'middleware-manifest.json');
18
- if (!(0, fs_1.existsSync)(middlewarePath)) {
19
- return null;
15
+ const maybeLoadJson = (path) => {
16
+ if ((0, fs_1.existsSync)(path)) {
17
+ return (0, fs_extra_1.readJson)(path);
20
18
  }
21
- return (0, fs_extra_1.readJson)(middlewarePath);
22
19
  };
20
+ const loadMiddlewareManifest = (netlifyConfig) => maybeLoadJson((0, path_1.resolve)(netlifyConfig.build.publish, 'server', 'middleware-manifest.json'));
23
21
  exports.loadMiddlewareManifest = loadMiddlewareManifest;
22
+ const loadAppPathRoutesManifest = (netlifyConfig) => maybeLoadJson((0, path_1.resolve)(netlifyConfig.build.publish, 'app-path-routes-manifest.json'));
23
+ exports.loadAppPathRoutesManifest = loadAppPathRoutesManifest;
24
24
  /**
25
25
  * Convert the Next middleware name into a valid Edge Function name
26
26
  */
@@ -35,6 +35,7 @@ import {
35
35
  // Deno defines "window", but naughty libraries think this means it's a browser
36
36
  delete globalThis.window
37
37
  globalThis.process = { env: {...Deno.env.toObject(), NEXT_RUNTIME: 'edge', 'NEXT_PRIVATE_MINIMAL_MODE': '1' } }
38
+ globalThis.EdgeRuntime = "netlify-edge"
38
39
  let _ENTRIES = {}
39
40
 
40
41
  // Next.js uses this extension to the Headers API implemented by Cloudflare workerd
@@ -109,7 +110,7 @@ const getMiddlewareBundle = async ({ edgeFunctionDefinition, netlifyConfig, }) =
109
110
  };
110
111
  const getEdgeTemplatePath = (file) => (0, path_1.join)(__dirname, '..', '..', 'src', 'templates', 'edge', file);
111
112
  const copyEdgeSourceFile = ({ file, target, edgeFunctionDir, }) => fs_1.promises.copyFile(getEdgeTemplatePath(file), (0, path_1.join)(edgeFunctionDir, target !== null && target !== void 0 ? target : file));
112
- const writeEdgeFunction = async ({ edgeFunctionDefinition, edgeFunctionRoot, netlifyConfig, nextConfig, }) => {
113
+ const writeEdgeFunction = async ({ edgeFunctionDefinition, edgeFunctionRoot, netlifyConfig, pageRegexMap, appPathRoutesManifest = {}, nextConfig, cache, }) => {
113
114
  const name = sanitizeName(edgeFunctionDefinition.name);
114
115
  const edgeFunctionDir = (0, path_1.join)(edgeFunctionRoot, name);
115
116
  const bundle = await getMiddlewareBundle({
@@ -137,11 +138,19 @@ const writeEdgeFunction = async ({ edgeFunctionDefinition, edgeFunctionRoot, net
137
138
  else {
138
139
  matchers.push(...edgeFunctionDefinition.matchers);
139
140
  }
141
+ // If the EF matches a page, it's an app dir page so needs a matcher too
142
+ // The object will be empty if appDir isn't enabled in the Next config
143
+ if (pageRegexMap && edgeFunctionDefinition.page in appPathRoutesManifest) {
144
+ const regexp = pageRegexMap.get(appPathRoutesManifest[edgeFunctionDefinition.page]);
145
+ if (regexp) {
146
+ matchers.push({ regexp });
147
+ }
148
+ }
140
149
  await (0, fs_extra_1.writeJson)((0, path_1.join)(edgeFunctionDir, 'matchers.json'), matchers);
141
150
  // We add a defintion for each matching path
142
151
  return matchers.map((matcher) => {
143
152
  const pattern = (0, matchers_1.stripLookahead)(matcher.regexp);
144
- return { function: name, pattern, name: edgeFunctionDefinition.name };
153
+ return { function: name, pattern, name: edgeFunctionDefinition.name, cache };
145
154
  });
146
155
  };
147
156
  const cleanupEdgeFunctions = ({ INTERNAL_EDGE_FUNCTIONS_SRC = '.netlify/edge-functions', }) => (0, fs_extra_1.emptyDir)(INTERNAL_EDGE_FUNCTIONS_SRC);
@@ -169,7 +178,8 @@ exports.writeDevEdgeFunction = writeDevEdgeFunction;
169
178
  /**
170
179
  * Writes Edge Functions for the Next middleware
171
180
  */
172
- const writeEdgeFunctions = async (netlifyConfig) => {
181
+ const writeEdgeFunctions = async ({ netlifyConfig, routesManifest, }) => {
182
+ var _a;
173
183
  const manifest = {
174
184
  functions: [],
175
185
  version: 1,
@@ -179,6 +189,7 @@ const writeEdgeFunctions = async (netlifyConfig) => {
179
189
  const { publish } = netlifyConfig.build;
180
190
  const nextConfigFile = await (0, config_1.getRequiredServerFiles)(publish);
181
191
  const nextConfig = nextConfigFile.config;
192
+ const usesAppDir = (_a = nextConfig.experimental) === null || _a === void 0 ? void 0 : _a.appDir;
182
193
  await (0, fs_extra_1.copy)(getEdgeTemplatePath('../edge-shared'), (0, path_1.join)(edgeFunctionRoot, 'edge-shared'));
183
194
  await (0, fs_extra_1.writeJSON)((0, path_1.join)(edgeFunctionRoot, 'edge-shared', 'nextConfig.json'), nextConfig);
184
195
  if (!(0, destr_1.default)(process.env.NEXT_DISABLE_EDGE_IMAGES) &&
@@ -216,13 +227,23 @@ const writeEdgeFunctions = async (netlifyConfig) => {
216
227
  // Older versions of the manifest format don't have the functions field
217
228
  // No, the version field was not incremented
218
229
  if (typeof middlewareManifest.functions === 'object') {
230
+ // When using the app dir, we also need to check if the EF matches a page
231
+ const appPathRoutesManifest = await (0, exports.loadAppPathRoutesManifest)(netlifyConfig);
232
+ const pageRegexMap = new Map([...(routesManifest.dynamicRoutes || []), ...(routesManifest.staticRoutes || [])].map((route) => [
233
+ route.page,
234
+ route.regex,
235
+ ]));
219
236
  for (const edgeFunctionDefinition of Object.values(middlewareManifest.functions)) {
220
237
  usesEdge = true;
221
238
  const functionDefinitions = await writeEdgeFunction({
222
239
  edgeFunctionDefinition,
223
240
  edgeFunctionRoot,
224
241
  netlifyConfig,
242
+ pageRegexMap,
243
+ appPathRoutesManifest,
225
244
  nextConfig,
245
+ // cache: "manual" is currently experimental, so we restrict it to sites that use experimental appDir
246
+ cache: usesAppDir ? 'manual' : undefined,
226
247
  });
227
248
  manifest.functions.push(...functionDefinitions);
228
249
  }
@@ -237,4 +258,3 @@ const writeEdgeFunctions = async (netlifyConfig) => {
237
258
  await (0, fs_extra_1.writeJson)((0, path_1.join)(edgeFunctionRoot, 'manifest.json'), manifest);
238
259
  };
239
260
  exports.writeEdgeFunctions = writeEdgeFunctions;
240
- /* eslint-enable max-lines */
@@ -4,7 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.movePublicFiles = exports.unpatchNextFiles = exports.unpatchFile = exports.patchNextFiles = exports.getDependenciesOfFile = exports.getSourceFileForPage = exports.moveStaticPages = exports.getMiddleware = exports.matchesRewrite = exports.matchesRedirect = exports.matchMiddleware = exports.stripLocale = exports.isDynamicRoute = void 0;
7
- /* eslint-disable max-lines */
8
7
  const os_1 = require("os");
9
8
  const chalk_1 = require("chalk");
10
9
  const fs_extra_1 = require("fs-extra");
@@ -70,7 +69,6 @@ exports.getMiddleware = getMiddleware;
70
69
  const moveStaticPages = async ({ netlifyConfig, target, i18n, basePath, }) => {
71
70
  console.log('Moving static page files to serve from CDN...');
72
71
  const outputDir = (0, pathe_1.join)(netlifyConfig.build.publish, target === 'server' ? 'server' : 'serverless');
73
- const root = (0, pathe_1.join)(outputDir, 'pages');
74
72
  const buildId = (0, fs_extra_1.readFileSync)((0, pathe_1.join)(netlifyConfig.build.publish, 'BUILD_ID'), 'utf8').trim();
75
73
  const dataDir = (0, pathe_1.join)('_next', 'data', buildId);
76
74
  await (0, fs_extra_1.ensureDir)((0, pathe_1.join)(netlifyConfig.build.publish, dataDir));
@@ -92,14 +90,17 @@ const moveStaticPages = async ({ netlifyConfig, target, i18n, basePath, }) => {
92
90
  }
93
91
  }
94
92
  });
95
- const files = [];
93
+ let fileCount = 0;
96
94
  const filesManifest = {};
97
95
  const moveFile = async (file) => {
96
+ // Strip the initial 'app' or 'pages' directory from the output path
97
+ const pathname = file.split('/').slice(1).join('/');
98
+ // .rsc data files go next to the html file
98
99
  const isData = file.endsWith('.json');
99
- const source = (0, pathe_1.join)(root, file);
100
- const targetFile = isData ? (0, pathe_1.join)(dataDir, file) : file;
100
+ const source = (0, pathe_1.join)(outputDir, file);
101
+ const targetFile = isData ? (0, pathe_1.join)(dataDir, pathname) : pathname;
101
102
  const targetPath = basePath ? (0, pathe_1.join)(basePath, targetFile) : targetFile;
102
- files.push(file);
103
+ fileCount += 1;
103
104
  filesManifest[file] = targetPath;
104
105
  const dest = (0, pathe_1.join)(netlifyConfig.build.publish, targetPath);
105
106
  try {
@@ -110,8 +111,8 @@ const moveStaticPages = async ({ netlifyConfig, target, i18n, basePath, }) => {
110
111
  }
111
112
  };
112
113
  // Move all static files, except error documents and nft manifests
113
- const pages = await (0, globby_1.default)(['**/*.{html,json}', '!**/(500|404|*.js.nft).{html,json}'], {
114
- cwd: root,
114
+ const pages = await (0, globby_1.default)(['{app,pages}/**/*.{html,json,rsc}', '!**/(500|404|*.js.nft).{html,json}'], {
115
+ cwd: outputDir,
115
116
  dot: true,
116
117
  });
117
118
  const matchingMiddleware = new Set();
@@ -121,35 +122,38 @@ const moveStaticPages = async ({ netlifyConfig, target, i18n, basePath, }) => {
121
122
  // Limit concurrent file moves to number of cpus or 2 if there is only 1
122
123
  const limit = (0, p_limit_1.default)(Math.max(2, (0, os_1.cpus)().length));
123
124
  const promises = pages.map((rawPath) => {
125
+ // Convert to POSIX path
124
126
  const filePath = (0, slash_1.default)(rawPath);
127
+ // Remove the initial 'app' or 'pages' directory from the output path
128
+ const pagePath = filePath.split('/').slice(1).join('/');
125
129
  // Don't move ISR files, as they're used for the first request
126
- if (isrFiles.has(filePath)) {
130
+ if (isrFiles.has(pagePath)) {
127
131
  return;
128
132
  }
129
- if ((0, exports.isDynamicRoute)(filePath)) {
133
+ if ((0, exports.isDynamicRoute)(pagePath)) {
130
134
  return;
131
135
  }
132
- if ((0, exports.matchesRedirect)(filePath, redirects)) {
133
- matchedRedirects.add(filePath);
136
+ if ((0, exports.matchesRedirect)(pagePath, redirects)) {
137
+ matchedRedirects.add(pagePath);
134
138
  return;
135
139
  }
136
- if ((0, exports.matchesRewrite)(filePath, rewrites)) {
137
- matchedRewrites.add(filePath);
140
+ if ((0, exports.matchesRewrite)(pagePath, rewrites)) {
141
+ matchedRewrites.add(pagePath);
138
142
  return;
139
143
  }
140
144
  // Middleware matches against the unlocalised path
141
- const unlocalizedPath = (0, exports.stripLocale)(rawPath, i18n === null || i18n === void 0 ? void 0 : i18n.locales);
145
+ const unlocalizedPath = (0, exports.stripLocale)(pagePath, i18n === null || i18n === void 0 ? void 0 : i18n.locales);
142
146
  const middlewarePath = (0, exports.matchMiddleware)(middleware, unlocalizedPath);
143
147
  // If a file matches middleware it can't be offloaded to the CDN, and needs to stay at the origin to be served by next/server
144
148
  if (middlewarePath) {
145
149
  matchingMiddleware.add(middlewarePath);
146
- matchedPages.add(rawPath);
150
+ matchedPages.add(filePath);
147
151
  return;
148
152
  }
149
153
  return limit(moveFile, filePath);
150
154
  });
151
155
  await Promise.all(promises);
152
- console.log(`Moved ${files.length} files`);
156
+ console.log(`Moved ${fileCount} files`);
153
157
  if (matchedPages.size !== 0) {
154
158
  console.log((0, chalk_1.yellowBright)((0, outdent_1.outdent) `
155
159
  Skipped moving ${matchedPages.size} ${matchedPages.size === 1 ? 'file because it matches' : 'files because they match'} middleware, so cannot be deployed to the CDN and will be served from the origin instead.
@@ -373,4 +377,3 @@ const movePublicFiles = async ({ appDir, outdir, publish, }) => {
373
377
  }
374
378
  };
375
379
  exports.movePublicFiles = movePublicFiles;
376
- /* eslint-enable max-lines */
@@ -20,10 +20,13 @@ const utils_1 = require("./utils");
20
20
  const generateFunctions = async ({ FUNCTIONS_SRC = constants_1.DEFAULT_FUNCTIONS_SRC, INTERNAL_FUNCTIONS_SRC, PUBLISH_DIR }, appDir, apiRoutes) => {
21
21
  const publish = (0, pathe_1.resolve)(PUBLISH_DIR);
22
22
  const functionsDir = (0, pathe_1.resolve)(INTERNAL_FUNCTIONS_SRC || FUNCTIONS_SRC);
23
- console.log({ functionsDir });
24
23
  const functionDir = (0, pathe_1.join)(functionsDir, constants_1.HANDLER_FUNCTION_NAME);
25
24
  const publishDir = (0, pathe_1.relative)(functionDir, publish);
26
25
  for (const { route, config, compiled } of apiRoutes) {
26
+ // Don't write a lambda if the runtime is edge
27
+ if (config.runtime === 'experimental-edge') {
28
+ continue;
29
+ }
27
30
  const apiHandlerSource = await (0, getApiHandler_1.getApiHandler)({
28
31
  page: route,
29
32
  config,
@@ -167,4 +170,3 @@ const warnOnApiRoutes = async ({ FUNCTIONS_DIST, }) => {
167
170
  }
168
171
  };
169
172
  exports.warnOnApiRoutes = warnOnApiRoutes;
170
- /* eslint-enable max-lines */
@@ -127,7 +127,7 @@ const generateStaticIsrRewrites = ({ staticRouteEntries, basePath, i18n, buildId
127
127
  /**
128
128
  * Generate rewrites for all dynamic routes
129
129
  */
130
- const generateDynamicRewrites = ({ dynamicRoutes, prerenderedDynamicRoutes, middleware, basePath, buildId, i18n, }) => {
130
+ const generateDynamicRewrites = ({ dynamicRoutes, prerenderedDynamicRoutes, middleware, basePath, buildId, i18n, is404Isr, }) => {
131
131
  const dynamicRewrites = [];
132
132
  const dynamicRoutesThatMatchMiddleware = [];
133
133
  dynamicRoutes.forEach((route) => {
@@ -138,8 +138,11 @@ const generateDynamicRewrites = ({ dynamicRoutes, prerenderedDynamicRoutes, midd
138
138
  if (matchesMiddleware(middleware, route.page)) {
139
139
  dynamicRoutesThatMatchMiddleware.push(route.page);
140
140
  }
141
+ else if (prerenderedDynamicRoutes[route.page].fallback === false && !is404Isr) {
142
+ dynamicRewrites.push(...(0, utils_1.redirectsForNext404Route)({ route: route.page, buildId, basePath, i18n }));
143
+ }
141
144
  else {
142
- dynamicRewrites.push(...(0, utils_1.redirectsForNextRoute)({ buildId, route: route.page, basePath, to: constants_1.ODB_FUNCTION_PATH, status: 200, i18n }));
145
+ dynamicRewrites.push(...(0, utils_1.redirectsForNextRoute)({ route: route.page, buildId, basePath, to: constants_1.ODB_FUNCTION_PATH, i18n }));
143
146
  }
144
147
  }
145
148
  else {
@@ -168,6 +171,7 @@ const generateRedirects = async ({ netlifyConfig, nextConfig: { i18n, basePath,
168
171
  const middleware = await (0, files_1.getMiddleware)(netlifyConfig.build.publish);
169
172
  netlifyConfig.redirects.push(...generateMiddlewareRewrites({ basePath, i18n, middleware, buildId }));
170
173
  const staticRouteEntries = Object.entries(prerenderedStaticRoutes);
174
+ const is404Isr = staticRouteEntries.some(([route, { initialRevalidateSeconds }]) => (0, utils_1.is404Route)(route, i18n) && initialRevalidateSeconds !== false);
171
175
  const routesThatMatchMiddleware = [];
172
176
  const { staticRoutePaths, staticIsrRewrites, staticIsrRoutesThatMatchMiddleware } = generateStaticIsrRewrites({
173
177
  staticRouteEntries,
@@ -194,6 +198,7 @@ const generateRedirects = async ({ netlifyConfig, nextConfig: { i18n, basePath,
194
198
  basePath,
195
199
  buildId,
196
200
  i18n,
201
+ is404Isr,
197
202
  });
198
203
  netlifyConfig.redirects.push(...dynamicRewrites);
199
204
  routesThatMatchMiddleware.push(...dynamicRoutesThatMatchMiddleware);
@@ -214,4 +219,3 @@ const generateRedirects = async ({ netlifyConfig, nextConfig: { i18n, basePath,
214
219
  }
215
220
  };
216
221
  exports.generateRedirects = generateRedirects;
217
- /* eslint-enable max-lines */
@@ -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.getRemotePatterns = exports.isBundleSizeCheckDisabled = exports.getCustomImageResponseHeaders = exports.isNextAuthInstalled = exports.findModuleFromBase = exports.shouldSkip = exports.getPreviewRewrites = exports.getApiRewrites = exports.redirectsForNextRouteWithData = exports.redirectsForNextRoute = exports.is404Route = exports.isApiRoute = exports.routeToDataRoute = exports.netlifyRoutesForNextRouteWithData = exports.toNetlifyRoute = exports.getFunctionNameForPage = void 0;
6
+ exports.getRemotePatterns = exports.isBundleSizeCheckDisabled = exports.getCustomImageResponseHeaders = exports.isNextAuthInstalled = exports.findModuleFromBase = exports.shouldSkip = exports.getPreviewRewrites = exports.getApiRewrites = exports.redirectsForNextRouteWithData = exports.redirectsForNext404Route = exports.redirectsForNextRoute = exports.is404Route = exports.isApiRoute = exports.routeToDataRoute = exports.netlifyRoutesForNextRouteWithData = exports.toNetlifyRoute = exports.getFunctionNameForPage = void 0;
7
7
  const globby_1 = __importDefault(require("globby"));
8
8
  const pathe_1 = require("pathe");
9
9
  const constants_1 = require("../constants");
@@ -57,7 +57,10 @@ exports.routeToDataRoute = routeToDataRoute;
57
57
  const netlifyRoutesForNextRoute = (route, buildId, i18n) => {
58
58
  var _a;
59
59
  if (!((_a = i18n === null || i18n === void 0 ? void 0 : i18n.locales) === null || _a === void 0 ? void 0 : _a.length)) {
60
- return (0, exports.netlifyRoutesForNextRouteWithData)({ route, dataRoute: (0, exports.routeToDataRoute)(route, buildId) });
60
+ return (0, exports.netlifyRoutesForNextRouteWithData)({ route, dataRoute: (0, exports.routeToDataRoute)(route, buildId) }).map((redirect) => ({
61
+ redirect,
62
+ locale: false,
63
+ }));
61
64
  }
62
65
  const { locales, defaultLocale } = i18n;
63
66
  const routes = [];
@@ -69,7 +72,10 @@ const netlifyRoutesForNextRoute = (route, buildId, i18n) => {
69
72
  ...(0, exports.netlifyRoutesForNextRouteWithData)({
70
73
  route: locale === defaultLocale ? route : `/${locale}${route}`,
71
74
  dataRoute,
72
- }));
75
+ }).map((redirect) => ({
76
+ redirect,
77
+ locale,
78
+ })));
73
79
  });
74
80
  return routes;
75
81
  };
@@ -77,13 +83,20 @@ const isApiRoute = (route) => route.startsWith('/api/') || route === '/api';
77
83
  exports.isApiRoute = isApiRoute;
78
84
  const is404Route = (route, i18n) => i18n ? i18n.locales.some((locale) => route === `/${locale}/404`) : route === '/404';
79
85
  exports.is404Route = is404Route;
80
- const redirectsForNextRoute = ({ route, buildId, basePath, to, i18n, status = 200, force = false, }) => netlifyRoutesForNextRoute(route, buildId, i18n).map((redirect) => ({
86
+ const redirectsForNextRoute = ({ route, buildId, basePath, to, i18n, status = 200, force = false, }) => netlifyRoutesForNextRoute(route, buildId, i18n).map(({ redirect }) => ({
81
87
  from: `${basePath}${redirect}`,
82
88
  to,
83
89
  status,
84
90
  force,
85
91
  }));
86
92
  exports.redirectsForNextRoute = redirectsForNextRoute;
93
+ const redirectsForNext404Route = ({ route, buildId, basePath, i18n, force = false, }) => netlifyRoutesForNextRoute(route, buildId, i18n).map(({ redirect, locale }) => ({
94
+ from: `${basePath}${redirect}`,
95
+ to: locale ? `${basePath}/server/pages/${locale}/404.html` : `${basePath}/server/pages/404.html`,
96
+ status: 404,
97
+ force,
98
+ }));
99
+ exports.redirectsForNext404Route = redirectsForNext404Route;
87
100
  const redirectsForNextRouteWithData = ({ route, dataRoute, basePath, to, status = 200, force = false, }) => (0, exports.netlifyRoutesForNextRouteWithData)({ route, dataRoute }).map((redirect) => ({
88
101
  from: `${basePath}${redirect}`,
89
102
  to,
@@ -193,4 +206,3 @@ const getRemotePatterns = (experimental, images) => {
193
206
  return [];
194
207
  };
195
208
  exports.getRemotePatterns = getRemotePatterns;
196
- /* eslint-enable max-lines */
@@ -27,7 +27,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.warnForRootRedirects = exports.warnForProblematicUserRewrites = exports.getProblematicUserRewrites = exports.checkZipSize = exports.checkForRootPublish = exports.checkNextSiteHasBuilt = exports.checkForOldFunctions = exports.verifyNetlifyBuildVersion = void 0;
30
- /* eslint-disable max-lines */
31
30
  const fs_1 = require("fs");
32
31
  const path_1 = __importStar(require("path"));
33
32
  const chalk_1 = require("chalk");
@@ -180,4 +179,3 @@ const warnForRootRedirects = ({ appDir }) => {
180
179
  }
181
180
  };
182
181
  exports.warnForRootRedirects = warnForRootRedirects;
183
- /* eslint-enable max-lines */
package/lib/index.js CHANGED
@@ -3,7 +3,6 @@ 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
- /* eslint-disable max-lines */
7
6
  const path_1 = require("path");
8
7
  const chalk_1 = require("chalk");
9
8
  const destr_1 = __importDefault(require("destr"));
@@ -44,7 +43,7 @@ const plugin = {
44
43
  }
45
44
  const { publish } = netlifyConfig.build;
46
45
  (0, verification_1.checkNextSiteHasBuilt)({ publish, failBuild });
47
- const { appDir, basePath, i18n, images, target, ignore, trailingSlash, outdir, experimental } = await (0, config_1.getNextConfig)({
46
+ const { appDir, basePath, i18n, images, target, ignore, trailingSlash, outdir, experimental, routesManifest } = await (0, config_1.getNextConfig)({
48
47
  publish,
49
48
  failBuild,
50
49
  });
@@ -118,7 +117,7 @@ const plugin = {
118
117
  buildId,
119
118
  apiRoutes,
120
119
  });
121
- await (0, edge_1.writeEdgeFunctions)(netlifyConfig);
120
+ await (0, edge_1.writeEdgeFunctions)({ netlifyConfig, routesManifest });
122
121
  },
123
122
  async onPostBuild({ netlifyConfig: { build: { publish }, redirects, headers, }, utils: { status, cache, functions, build: { failBuild }, }, constants: { FUNCTIONS_DIST }, }) {
124
123
  await (0, cache_1.saveCache)({ cache, publish });
@@ -134,11 +133,14 @@ const plugin = {
134
133
  await (0, verification_1.checkForOldFunctions)({ functions });
135
134
  await (0, verification_1.checkZipSize)((0, path_1.join)(FUNCTIONS_DIST, `${constants_1.ODB_FUNCTION_NAME}.zip`));
136
135
  const nextConfig = await (0, config_1.getNextConfig)({ publish, failBuild });
137
- const { basePath, appDir } = nextConfig;
136
+ const { basePath, appDir, experimental } = nextConfig;
138
137
  (0, config_1.generateCustomHeaders)(nextConfig, headers);
139
138
  (0, verification_1.warnForProblematicUserRewrites)({ basePath, redirects });
140
139
  (0, verification_1.warnForRootRedirects)({ appDir });
141
140
  await (0, functions_1.warnOnApiRoutes)({ FUNCTIONS_DIST });
141
+ if (experimental === null || experimental === void 0 ? void 0 : experimental.appDir) {
142
+ console.log('🧪 Thank you for testing "appDir" support on Netlify. For known issues and to give feedback, visit https://ntl.fyi/next-13-feedback');
143
+ }
142
144
  },
143
145
  };
144
146
  // The types haven't been updated yet
@@ -161,4 +163,3 @@ const nextRuntime = (_inputs, meta = {}) => {
161
163
  };
162
164
  };
163
165
  module.exports = nextRuntime;
164
- /* eslint-enable max-lines */
@@ -105,7 +105,7 @@ const getApiHandler = ({ page, config, publishDir = '../../../.next', appDir = '
105
105
  const { config } = require("${publishDir}/required-server-files.json")
106
106
  let staticManifest
107
107
  const path = require("path");
108
- const pageRoot = path.resolve(path.join(__dirname, "${publishDir}", "serverless", "pages"));
108
+ const pageRoot = path.resolve(path.join(__dirname, "${publishDir}", "server"));
109
109
  const handler = (${makeHandler.toString()})(config, "${appDir}", pageRoot, ${JSON.stringify(page)})
110
110
  exports.handler = ${config.type === "experimental-scheduled" /* ApiRouteType.SCHEDULED */ ? `schedule(${JSON.stringify(config.schedule)}, handler);` : 'handler'}
111
111
  `;
@@ -10,7 +10,7 @@ const path = require('path');
10
10
  // eslint-disable-next-line n/prefer-global/url, n/prefer-global/url-search-params
11
11
  const { URLSearchParams, URL } = require('url');
12
12
  const { Bridge } = require('@vercel/node-bridge/bridge');
13
- const { augmentFsModule, getMaxAge, getMultiValueHeaders, getNextServer } = require('./handlerUtils');
13
+ const { augmentFsModule, getMaxAge, getMultiValueHeaders, getPrefetchResponse, getNextServer, } = require('./handlerUtils');
14
14
  // We return a function and then call `toString()` on it to serialise it as the launcher function
15
15
  // eslint-disable-next-line max-params
16
16
  const makeHandler = (conf, app, pageRoot, staticManifest = [], mode = 'ssr') => {
@@ -37,8 +37,8 @@ const makeHandler = (conf, app, pageRoot, staticManifest = [], mode = 'ssr') =>
37
37
  for (const [key, value] of Object.entries(conf.env)) {
38
38
  process.env[key] = String(value);
39
39
  }
40
- // Set during the request as it needs the host header. Hoisted so we can define the function once
41
- let base;
40
+ // Set during the request as it needs to get it from the request URL. Defaults to the URL env var
41
+ let base = process.env.URL;
42
42
  augmentFsModule({ promises, staticManifest, pageRoot, getBase: () => base });
43
43
  // We memoize this because it can be shared between requests, but don't instantiate it until
44
44
  // the first request because we need the host and port.
@@ -49,9 +49,7 @@ const makeHandler = (conf, app, pageRoot, staticManifest = [], mode = 'ssr') =>
49
49
  }
50
50
  const url = new URL(event.rawUrl);
51
51
  const port = Number.parseInt(url.port) || 80;
52
- const { host } = event.headers;
53
- const protocol = event.headers['x-forwarded-proto'] || 'http';
54
- base = `${protocol}://${host}`;
52
+ base = url.origin;
55
53
  const NextServer = getNextServer();
56
54
  const nextServer = new NextServer({
57
55
  conf,
@@ -77,6 +75,10 @@ const makeHandler = (conf, app, pageRoot, staticManifest = [], mode = 'ssr') =>
77
75
  return async function handler(event, context) {
78
76
  var _a, _b, _c;
79
77
  let requestMode = mode;
78
+ const prefetchResponse = getPrefetchResponse(event, mode);
79
+ if (prefetchResponse) {
80
+ return prefetchResponse;
81
+ }
80
82
  // Ensure that paths are encoded - but don't double-encode them
81
83
  event.path = new URL(event.rawUrl).pathname;
82
84
  // Next expects to be able to parse the query from the URL
@@ -140,7 +142,7 @@ const getHandler = ({ isODB = false, publishDir = '../../../.next', appDir = '..
140
142
  const { promises } = require("fs");
141
143
  // We copy the file here rather than requiring from the node module
142
144
  const { Bridge } = require("./bridge");
143
- const { augmentFsModule, getMaxAge, getMultiValueHeaders, getNextServer } = require('./handlerUtils')
145
+ const { augmentFsModule, getMaxAge, getMultiValueHeaders, getPrefetchResponse, getNextServer } = require('./handlerUtils')
144
146
 
145
147
  ${isODB ? `const { builder } = require("@netlify/functions")` : ''}
146
148
  const { config } = require("${publishDir}/required-server-files.json")
@@ -149,7 +151,7 @@ const getHandler = ({ isODB = false, publishDir = '../../../.next', appDir = '..
149
151
  staticManifest = require("${publishDir}/static-manifest.json")
150
152
  } catch {}
151
153
  const path = require("path");
152
- const pageRoot = path.resolve(path.join(__dirname, "${publishDir}", config.target === "server" ? "server" : "serverless", "pages"));
154
+ const pageRoot = path.resolve(path.join(__dirname, "${publishDir}", "server"));
153
155
  exports.handler = ${isODB
154
156
  ? `builder((${makeHandler.toString()})(config, "${appDir}", pageRoot, staticManifest, 'odb'));`
155
157
  : `(${makeHandler.toString()})(config, "${appDir}", pageRoot, staticManifest, 'ssr');`}
@@ -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.getNextServer = exports.augmentFsModule = exports.getMultiValueHeaders = exports.getMaxAge = exports.downloadFile = void 0;
6
+ exports.getPrefetchResponse = exports.getNextServer = 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"));
@@ -93,22 +93,22 @@ const augmentFsModule = ({ promises, staticManifest, pageRoot, getBase, }) => {
93
93
  const statsOrig = promises.stat;
94
94
  // ...then money-patch it to see if it's requesting a CDN file
95
95
  promises.readFile = (async (file, options) => {
96
- const base = getBase();
96
+ const baseUrl = getBase();
97
97
  // We only care about page files
98
98
  if (file.startsWith(pageRoot)) {
99
- // We only want the part after `pages/`
99
+ // We only want the part after `.next/server/`
100
100
  const filePath = file.slice(pageRoot.length + 1);
101
101
  // Is it in the CDN and not local?
102
102
  if (staticFiles.has(filePath) && !(0, fs_1.existsSync)(file)) {
103
103
  // This name is safe to use, because it's one that was already created by Next
104
104
  const cacheFile = path_1.default.join(cacheDir, filePath);
105
- const url = `${base}/${staticFiles.get(filePath)}`;
105
+ const url = `${baseUrl}/${staticFiles.get(filePath)}`;
106
106
  // If it's already downloading we can wait for it to finish
107
107
  if (downloadPromises.has(url)) {
108
108
  await downloadPromises.get(url);
109
109
  }
110
110
  // Have we already cached it? We download every time if running locally to avoid staleness
111
- if ((!(0, fs_1.existsSync)(cacheFile) || process.env.NETLIFY_DEV) && base) {
111
+ if ((!(0, fs_1.existsSync)(cacheFile) || process.env.NETLIFY_DEV) && baseUrl) {
112
112
  await promises.mkdir(path_1.default.dirname(cacheFile), { recursive: true });
113
113
  try {
114
114
  // Append the path to our host and we can load it like a regular page
@@ -129,7 +129,7 @@ const augmentFsModule = ({ promises, staticManifest, pageRoot, getBase, }) => {
129
129
  promises.stat = ((file, options) => {
130
130
  // We only care about page files
131
131
  if (file.startsWith(pageRoot)) {
132
- // We only want the part after `pages/`
132
+ // We only want the part after `.next/server`
133
133
  const cacheFile = path_1.default.join(cacheDir, file.slice(pageRoot.length + 1));
134
134
  if ((0, fs_1.existsSync)(cacheFile)) {
135
135
  return statsOrig(cacheFile, options);
@@ -172,3 +172,22 @@ const getNextServer = () => {
172
172
  return NextServer;
173
173
  };
174
174
  exports.getNextServer = getNextServer;
175
+ /**
176
+ * Prefetch requests are used to check for middleware redirects, and shouldn't trigger SSR.
177
+ */
178
+ const getPrefetchResponse = (event, mode) => {
179
+ if (event.headers['x-middleware-prefetch'] && mode === 'ssr') {
180
+ return {
181
+ statusCode: 200,
182
+ body: '{}',
183
+ headers: {
184
+ 'Content-Type': 'application/json',
185
+ 'x-middleware-skip': '1',
186
+ // https://github.com/vercel/next.js/pull/42936/files#r1027563953
187
+ vary: 'x-middleware-prefetch',
188
+ },
189
+ };
190
+ }
191
+ return false;
192
+ };
193
+ exports.getPrefetchResponse = getPrefetchResponse;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/plugin-nextjs",
3
- "version": "4.29.3",
3
+ "version": "4.29.4",
4
4
  "description": "Run Next.js seamlessly on Netlify",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -13,7 +13,7 @@
13
13
  "dependencies": {
14
14
  "@netlify/esbuild": "0.14.39",
15
15
  "@netlify/functions": "^1.3.0",
16
- "@netlify/ipx": "^1.3.1",
16
+ "@netlify/ipx": "^1.3.2",
17
17
  "@vercel/node-bridge": "^2.1.0",
18
18
  "chalk": "^4.1.2",
19
19
  "destr": "^1.1.1",
@@ -36,12 +36,12 @@
36
36
  },
37
37
  "devDependencies": {
38
38
  "@delucis/if-env": "^1.1.2",
39
- "@netlify/build": "^28.4.5",
39
+ "@netlify/build": "^29.1.3",
40
40
  "@types/fs-extra": "^9.0.13",
41
41
  "@types/jest": "^27.4.1",
42
42
  "@types/merge-stream": "^1.1.2",
43
43
  "@types/node": "^17.0.25",
44
- "next": "^13.0.3",
44
+ "next": "^13.0.6",
45
45
  "npm-run-all": "^4.1.5",
46
46
  "typescript": "^4.6.3"
47
47
  },
@@ -13,6 +13,12 @@ export interface FetchEventResult {
13
13
  waitUntil: Promise<any>
14
14
  }
15
15
 
16
+ export interface I18NConfig {
17
+ defaultLocale: string
18
+ localeDetection?: false
19
+ locales: string[]
20
+ }
21
+
16
22
  export interface RequestData {
17
23
  geo?: {
18
24
  city?: string
@@ -27,7 +33,7 @@ export interface RequestData {
27
33
  method: string
28
34
  nextConfig?: {
29
35
  basePath?: string
30
- i18n?: Record<string, unknown>
36
+ i18n?: I18NConfig | null
31
37
  trailingSlash?: boolean
32
38
  }
33
39
  page?: {