@netlify/plugin-nextjs 4.29.3 → 4.29.5-appdir.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/helpers/config.js +0 -1
- package/lib/helpers/edge.js +31 -11
- package/lib/helpers/files.js +23 -20
- package/lib/helpers/functions.js +4 -2
- package/lib/helpers/redirects.js +7 -3
- package/lib/helpers/utils.js +17 -5
- package/lib/helpers/verification.js +0 -2
- package/lib/index.js +7 -6
- package/lib/templates/getApiHandler.js +1 -1
- package/lib/templates/getHandler.js +10 -8
- package/lib/templates/handlerUtils.js +25 -6
- package/package.json +4 -4
- package/src/templates/edge/runtime.ts +7 -1
package/lib/helpers/config.js
CHANGED
package/lib/helpers/edge.js
CHANGED
|
@@ -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
|
|
17
|
-
|
|
18
|
-
|
|
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 */
|
package/lib/helpers/files.js
CHANGED
|
@@ -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
|
-
|
|
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)(
|
|
100
|
-
const targetFile = isData ? (0, pathe_1.join)(dataDir,
|
|
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
|
-
|
|
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)(['
|
|
114
|
-
cwd:
|
|
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(
|
|
130
|
+
if (isrFiles.has(pagePath)) {
|
|
127
131
|
return;
|
|
128
132
|
}
|
|
129
|
-
if ((0, exports.isDynamicRoute)(
|
|
133
|
+
if ((0, exports.isDynamicRoute)(pagePath)) {
|
|
130
134
|
return;
|
|
131
135
|
}
|
|
132
|
-
if ((0, exports.matchesRedirect)(
|
|
133
|
-
matchedRedirects.add(
|
|
136
|
+
if ((0, exports.matchesRedirect)(pagePath, redirects)) {
|
|
137
|
+
matchedRedirects.add(pagePath);
|
|
134
138
|
return;
|
|
135
139
|
}
|
|
136
|
-
if ((0, exports.matchesRewrite)(
|
|
137
|
-
matchedRewrites.add(
|
|
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)(
|
|
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(
|
|
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 ${
|
|
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.
|
|
@@ -360,7 +364,7 @@ const unpatchNextFiles = async (root) => {
|
|
|
360
364
|
}
|
|
361
365
|
};
|
|
362
366
|
exports.unpatchNextFiles = unpatchNextFiles;
|
|
363
|
-
const movePublicFiles = async ({ appDir, outdir, publish, }) => {
|
|
367
|
+
const movePublicFiles = async ({ appDir, outdir, publish, basePath, }) => {
|
|
364
368
|
// `outdir` is a config property added when using Next.js with Nx. It's typically
|
|
365
369
|
// a relative path outside of the appDir, e.g. '../../dist/apps/<app-name>', and
|
|
366
370
|
// the parent directory of the .next directory.
|
|
@@ -369,8 +373,7 @@ const movePublicFiles = async ({ appDir, outdir, publish, }) => {
|
|
|
369
373
|
// directory from the original app directory.
|
|
370
374
|
const publicDir = outdir ? (0, pathe_1.join)(appDir, outdir, 'public') : (0, pathe_1.join)(appDir, 'public');
|
|
371
375
|
if ((0, fs_extra_1.existsSync)(publicDir)) {
|
|
372
|
-
await (0, fs_extra_1.copy)(publicDir, `${publish}/`);
|
|
376
|
+
await (0, fs_extra_1.copy)(publicDir, `${publish}${basePath}/`);
|
|
373
377
|
}
|
|
374
378
|
};
|
|
375
379
|
exports.movePublicFiles = movePublicFiles;
|
|
376
|
-
/* eslint-enable max-lines */
|
package/lib/helpers/functions.js
CHANGED
|
@@ -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 */
|
package/lib/helpers/redirects.js
CHANGED
|
@@ -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)({
|
|
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 */
|
package/lib/helpers/utils.js
CHANGED
|
@@ -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
|
});
|
|
@@ -95,7 +94,7 @@ const plugin = {
|
|
|
95
94
|
const apiRoutes = await (0, functions_1.getExtendedApiRouteConfigs)(publish, appDir);
|
|
96
95
|
await (0, functions_1.generateFunctions)(constants, appDir, apiRoutes);
|
|
97
96
|
await (0, functions_1.generatePagesResolver)(constants);
|
|
98
|
-
await (0, files_1.movePublicFiles)({ appDir, outdir, publish });
|
|
97
|
+
await (0, files_1.movePublicFiles)({ appDir, outdir, publish, basePath });
|
|
99
98
|
await (0, files_1.patchNextFiles)(appDir);
|
|
100
99
|
if (!(0, destr_1.default)(process.env.SERVE_STATIC_FILES_FROM_ORIGIN)) {
|
|
101
100
|
await (0, files_1.moveStaticPages)({ target, netlifyConfig, i18n, basePath });
|
|
@@ -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}", "
|
|
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
|
|
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
|
-
|
|
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}",
|
|
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
|
|
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
|
|
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 = `${
|
|
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) &&
|
|
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 `
|
|
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
|
+
"version": "4.29.5-appdir.0",
|
|
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.
|
|
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": "^
|
|
39
|
+
"@netlify/build": "^29.3.0",
|
|
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.
|
|
44
|
+
"next": "^13.0.7",
|
|
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?:
|
|
36
|
+
i18n?: I18NConfig | null
|
|
31
37
|
trailingSlash?: boolean
|
|
32
38
|
}
|
|
33
39
|
page?: {
|