@netlify/plugin-nextjs 4.14.2 → 4.17.1-api-routes.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/analysis.js +63 -0
- package/lib/helpers/cache.js +1 -1
- package/lib/helpers/config.js +1 -1
- package/lib/helpers/dev.js +37 -0
- package/lib/helpers/edge.js +24 -8
- package/lib/helpers/files.js +37 -8
- package/lib/helpers/functions.js +47 -11
- package/lib/helpers/redirects.js +2 -2
- package/lib/helpers/utils.js +50 -14
- package/lib/helpers/verification.js +3 -3
- package/lib/index.js +54 -20
- package/lib/templates/getApiHandler.js +115 -0
- package/lib/templates/getHandler.js +10 -2
- package/lib/templates/getPageResolver.js +22 -1
- package/lib/templates/ipx.js +1 -0
- package/manifest.yml +1 -1
- package/package.json +13 -24
- package/src/templates/edge/next-dev.js +89 -0
- package/src/templates/edge/runtime.ts +4 -0
- package/src/templates/edge/utils.ts +22 -1
- package/lib/middleware/html-rewriter.js +0 -4
- package/lib/middleware/index.js +0 -19
- package/lib/middleware/request.js +0 -64
- package/lib/middleware/response.js +0 -90
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.extractConfigFromFile = exports.validateConfigValue = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const extract_const_value_1 = require("next/dist/build/analysis/extract-const-value");
|
|
9
|
+
const parse_module_1 = require("next/dist/build/analysis/parse-module");
|
|
10
|
+
const pathe_1 = require("pathe");
|
|
11
|
+
const validateConfigValue = (config, apiFilePath) => {
|
|
12
|
+
if (config.type === 'experimental-scheduled') {
|
|
13
|
+
if (!config.schedule) {
|
|
14
|
+
console.error(`Invalid config value in ${(0, pathe_1.relative)(process.cwd(), apiFilePath)}: schedule is required when type is "experimental-scheduled"`);
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
if (config.runtime === 'experimental-edge') {
|
|
18
|
+
console.error(`Invalid config value in ${(0, pathe_1.relative)(process.cwd(), apiFilePath)}: edge runtime is not supported for scheduled functions`);
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
if (!config.type || config.type === 'experimental-background') {
|
|
24
|
+
if (config.schedule) {
|
|
25
|
+
console.error(`Invalid config value in ${(0, pathe_1.relative)(process.cwd(), apiFilePath)}: schedule is not allowed unless type is "experimental-scheduled"`);
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
if (config.type && config.runtime === 'experimental-edge') {
|
|
29
|
+
console.error(`Invalid config value in ${(0, pathe_1.relative)(process.cwd(), apiFilePath)}: edge runtime is not supported for background functions`);
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
console.error(`Invalid config value in ${(0, pathe_1.relative)(process.cwd(), apiFilePath)}: type ${config.type} is not supported`);
|
|
35
|
+
return false;
|
|
36
|
+
};
|
|
37
|
+
exports.validateConfigValue = validateConfigValue;
|
|
38
|
+
/**
|
|
39
|
+
* Uses Next's swc static analysis to extract the config values from a file.
|
|
40
|
+
*/
|
|
41
|
+
const extractConfigFromFile = async (apiFilePath) => {
|
|
42
|
+
const fileContent = await fs_1.default.promises.readFile(apiFilePath, 'utf8');
|
|
43
|
+
// No need to parse if there's no "config"
|
|
44
|
+
if (!fileContent.includes('config')) {
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
const ast = await (0, parse_module_1.parseModule)(apiFilePath, fileContent);
|
|
48
|
+
let config;
|
|
49
|
+
try {
|
|
50
|
+
config = (0, extract_const_value_1.extractExportedConstValue)(ast, 'config');
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (error instanceof extract_const_value_1.UnsupportedValueError) {
|
|
54
|
+
console.warn(`Unsupported config value in ${(0, pathe_1.relative)(process.cwd(), apiFilePath)}`);
|
|
55
|
+
}
|
|
56
|
+
return {};
|
|
57
|
+
}
|
|
58
|
+
if ((0, exports.validateConfigValue)(config, apiFilePath)) {
|
|
59
|
+
return config;
|
|
60
|
+
}
|
|
61
|
+
throw new Error(`Unsupported config value in ${(0, pathe_1.relative)(process.cwd(), apiFilePath)}`);
|
|
62
|
+
};
|
|
63
|
+
exports.extractConfigFromFile = extractConfigFromFile;
|
package/lib/helpers/cache.js
CHANGED
|
@@ -9,7 +9,7 @@ const findDistDir = (publish) => {
|
|
|
9
9
|
if (!(0, utils_1.shouldSkip)()) {
|
|
10
10
|
return publish;
|
|
11
11
|
}
|
|
12
|
-
// In this situation, the user has disabled the
|
|
12
|
+
// In this situation, the user has disabled the next-runtime, which means that they might be using next export,
|
|
13
13
|
// so we'll look in a few places to find the site root. This allows us to find the .next directory.
|
|
14
14
|
for (const root of [(0, path_1.resolve)(publish, '..'), (0, path_1.resolve)(publish, '..', '..')]) {
|
|
15
15
|
if ((0, fs_1.existsSync)((0, path_1.join)(root, 'next.config.js'))) {
|
package/lib/helpers/config.js
CHANGED
|
@@ -64,7 +64,7 @@ const configureHandlerFunctions = async ({ netlifyConfig, publish, ignore = [] }
|
|
|
64
64
|
/* eslint-disable no-underscore-dangle */
|
|
65
65
|
(_a = netlifyConfig.functions)._ipx || (_a._ipx = {});
|
|
66
66
|
netlifyConfig.functions._ipx.node_bundler = 'nft';
|
|
67
|
-
[constants_1.HANDLER_FUNCTION_NAME, constants_1.ODB_FUNCTION_NAME].forEach((functionName) => {
|
|
67
|
+
[constants_1.HANDLER_FUNCTION_NAME, constants_1.ODB_FUNCTION_NAME, '_api_*'].forEach((functionName) => {
|
|
68
68
|
var _a, _b;
|
|
69
69
|
(_a = netlifyConfig.functions)[functionName] || (_a[functionName] = { included_files: [], external_node_modules: [] });
|
|
70
70
|
netlifyConfig.functions[functionName].node_bundler = 'nft';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.onPreDev = void 0;
|
|
7
|
+
const path_1 = require("path");
|
|
8
|
+
const execa_1 = __importDefault(require("execa"));
|
|
9
|
+
const fs_extra_1 = require("fs-extra");
|
|
10
|
+
const edge_1 = require("./edge");
|
|
11
|
+
const files_1 = require("./files");
|
|
12
|
+
// The types haven't been updated yet
|
|
13
|
+
const onPreDev = async ({ constants, netlifyConfig }) => {
|
|
14
|
+
var _a;
|
|
15
|
+
const base = (_a = netlifyConfig.build.base) !== null && _a !== void 0 ? _a : process.cwd();
|
|
16
|
+
// Need to patch the files, because build might not have been run
|
|
17
|
+
await (0, files_1.patchNextFiles)(base);
|
|
18
|
+
// Clean up old functions
|
|
19
|
+
await (0, fs_extra_1.unlink)((0, path_1.resolve)('.netlify', 'middleware.js')).catch(() => {
|
|
20
|
+
// Ignore if it doesn't exist
|
|
21
|
+
});
|
|
22
|
+
await (0, edge_1.writeDevEdgeFunction)(constants);
|
|
23
|
+
if (!(0, fs_extra_1.existsSync)((0, path_1.resolve)(base, 'middleware.ts')) && !(0, fs_extra_1.existsSync)((0, path_1.resolve)(base, 'middleware.js'))) {
|
|
24
|
+
console.log("No middleware found. Create a 'middleware.ts' or 'middleware.js' file in your project root to add custom middleware.");
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
console.log('Watching for changes in Next.js middleware...');
|
|
28
|
+
}
|
|
29
|
+
// Eventually we might want to do this via esbuild's API, but for now the CLI works fine
|
|
30
|
+
const common = [`--bundle`, `--outdir=${(0, path_1.resolve)('.netlify')}`, `--format=esm`, `--target=esnext`, '--watch'];
|
|
31
|
+
// TypeScript
|
|
32
|
+
(0, execa_1.default)(`esbuild`, [...common, (0, path_1.resolve)(base, 'middleware.ts')], { all: true }).all.pipe(process.stdout);
|
|
33
|
+
// JavaScript
|
|
34
|
+
(0, execa_1.default)(`esbuild`, [...common, (0, path_1.resolve)(base, 'middleware.js')], { all: true }).all.pipe(process.stdout);
|
|
35
|
+
// Don't return the promise because we don't want to wait for the child process to finish
|
|
36
|
+
};
|
|
37
|
+
exports.onPreDev = onPreDev;
|
package/lib/helpers/edge.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.enableEdgeInNextConfig = exports.writeEdgeFunctions = exports.writeDevEdgeFunction = exports.loadMiddlewareManifest = void 0;
|
|
4
4
|
/* eslint-disable max-lines */
|
|
5
5
|
const fs_1 = require("fs");
|
|
6
6
|
const path_1 = require("path");
|
|
@@ -67,6 +67,25 @@ const writeEdgeFunction = async ({ edgeFunctionDefinition, edgeFunctionRoot, net
|
|
|
67
67
|
pattern: stripLookahead(edgeFunctionDefinition.regexp),
|
|
68
68
|
};
|
|
69
69
|
};
|
|
70
|
+
const writeDevEdgeFunction = async ({ INTERNAL_EDGE_FUNCTIONS_SRC = '.netlify/edge-functions', }) => {
|
|
71
|
+
const manifest = {
|
|
72
|
+
functions: [
|
|
73
|
+
{
|
|
74
|
+
function: 'next-dev',
|
|
75
|
+
path: '/*',
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
version: 1,
|
|
79
|
+
};
|
|
80
|
+
const edgeFunctionRoot = (0, path_1.resolve)(INTERNAL_EDGE_FUNCTIONS_SRC);
|
|
81
|
+
await (0, fs_extra_1.emptyDir)(edgeFunctionRoot);
|
|
82
|
+
await (0, fs_extra_1.writeJson)((0, path_1.join)(edgeFunctionRoot, 'manifest.json'), manifest);
|
|
83
|
+
const edgeFunctionDir = (0, path_1.join)(edgeFunctionRoot, 'next-dev');
|
|
84
|
+
await (0, fs_extra_1.ensureDir)(edgeFunctionDir);
|
|
85
|
+
await copyEdgeSourceFile({ edgeFunctionDir, file: 'next-dev.js', target: 'index.js' });
|
|
86
|
+
await copyEdgeSourceFile({ edgeFunctionDir, file: 'utils.ts' });
|
|
87
|
+
};
|
|
88
|
+
exports.writeDevEdgeFunction = writeDevEdgeFunction;
|
|
70
89
|
/**
|
|
71
90
|
* Writes Edge Functions for the Next middleware
|
|
72
91
|
*/
|
|
@@ -78,9 +97,7 @@ const writeEdgeFunctions = async (netlifyConfig) => {
|
|
|
78
97
|
const edgeFunctionRoot = (0, path_1.resolve)('.netlify', 'edge-functions');
|
|
79
98
|
await (0, fs_extra_1.emptyDir)(edgeFunctionRoot);
|
|
80
99
|
if (!process.env.NEXT_DISABLE_EDGE_IMAGES) {
|
|
81
|
-
|
|
82
|
-
console.log('Using Netlify Edge Functions for image format detection. Set env var "NEXT_DISABLE_EDGE_IMAGES=true" to disable.');
|
|
83
|
-
}
|
|
100
|
+
console.log('Using Netlify Edge Functions for image format detection. Set env var "NEXT_DISABLE_EDGE_IMAGES=true" to disable.');
|
|
84
101
|
const edgeFunctionDir = (0, path_1.join)(edgeFunctionRoot, 'ipx');
|
|
85
102
|
await (0, fs_extra_1.ensureDir)(edgeFunctionDir);
|
|
86
103
|
await copyEdgeSourceFile({ edgeFunctionDir, file: 'ipx.ts', target: 'index.ts' });
|
|
@@ -90,7 +107,7 @@ const writeEdgeFunctions = async (netlifyConfig) => {
|
|
|
90
107
|
path: '/_next/image*',
|
|
91
108
|
});
|
|
92
109
|
}
|
|
93
|
-
if (process.env.
|
|
110
|
+
if (!process.env.NEXT_DISABLE_NETLIFY_EDGE) {
|
|
94
111
|
const middlewareManifest = await (0, exports.loadMiddlewareManifest)(netlifyConfig);
|
|
95
112
|
if (!middlewareManifest) {
|
|
96
113
|
console.error("Couldn't find the middleware manifest");
|
|
@@ -121,11 +138,10 @@ const writeEdgeFunctions = async (netlifyConfig) => {
|
|
|
121
138
|
await (0, fs_extra_1.writeJson)((0, path_1.join)(edgeFunctionRoot, 'manifest.json'), manifest);
|
|
122
139
|
};
|
|
123
140
|
exports.writeEdgeFunctions = writeEdgeFunctions;
|
|
124
|
-
const
|
|
141
|
+
const enableEdgeInNextConfig = async (publish) => {
|
|
125
142
|
const configFile = (0, path_1.join)(publish, 'required-server-files.json');
|
|
126
143
|
const config = await (0, fs_extra_1.readJSON)(configFile);
|
|
127
|
-
config.config.env.NEXT_USE_NETLIFY_EDGE = 'true';
|
|
128
144
|
await (0, fs_extra_1.writeJSON)(configFile, config);
|
|
129
145
|
};
|
|
130
|
-
exports.
|
|
146
|
+
exports.enableEdgeInNextConfig = enableEdgeInNextConfig;
|
|
131
147
|
/* eslint-enable max-lines */
|
package/lib/helpers/files.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.movePublicFiles = exports.unpatchNextFiles = exports.unpatchFile = exports.patchNextFiles = exports.moveStaticPages = exports.getMiddleware = exports.matchesRewrite = exports.matchesRedirect = exports.matchMiddleware = exports.stripLocale = exports.isDynamicRoute = void 0;
|
|
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
7
|
/* eslint-disable max-lines */
|
|
8
8
|
const os_1 = require("os");
|
|
9
9
|
const chalk_1 = require("chalk");
|
|
@@ -54,7 +54,7 @@ const matchesRewrite = (file, rewrites) => {
|
|
|
54
54
|
exports.matchesRewrite = matchesRewrite;
|
|
55
55
|
const getMiddleware = async (publish) => {
|
|
56
56
|
var _a;
|
|
57
|
-
if (process.env.
|
|
57
|
+
if (!process.env.NEXT_DISABLE_NETLIFY_EDGE) {
|
|
58
58
|
return [];
|
|
59
59
|
}
|
|
60
60
|
const manifestPath = (0, pathe_1.join)(publish, 'server', 'middleware-manifest.json');
|
|
@@ -229,6 +229,7 @@ const moveStaticPages = async ({ netlifyConfig, target, i18n, basePath, }) => {
|
|
|
229
229
|
}
|
|
230
230
|
};
|
|
231
231
|
exports.moveStaticPages = moveStaticPages;
|
|
232
|
+
const PATCH_WARNING = `/* File patched by Netlify */`;
|
|
232
233
|
/**
|
|
233
234
|
* Attempt to patch a source file, preserving a backup
|
|
234
235
|
*/
|
|
@@ -237,7 +238,11 @@ const patchFile = async ({ file, replacements, }) => {
|
|
|
237
238
|
console.warn('File was not found');
|
|
238
239
|
return false;
|
|
239
240
|
}
|
|
240
|
-
|
|
241
|
+
let content = await (0, fs_extra_1.readFile)(file, 'utf8');
|
|
242
|
+
// If the file has already been patched, patch the backed-up original instead
|
|
243
|
+
if (content.includes(PATCH_WARNING) && (0, fs_extra_1.existsSync)(`${file}.orig`)) {
|
|
244
|
+
content = await (0, fs_extra_1.readFile)(`${file}.orig`, 'utf8');
|
|
245
|
+
}
|
|
241
246
|
const newContent = replacements.reduce((acc, [from, to]) => {
|
|
242
247
|
if (acc.includes(to)) {
|
|
243
248
|
console.log('Already patched. Skipping.');
|
|
@@ -250,7 +255,7 @@ const patchFile = async ({ file, replacements, }) => {
|
|
|
250
255
|
return false;
|
|
251
256
|
}
|
|
252
257
|
await (0, fs_extra_1.writeFile)(`${file}.orig`, content);
|
|
253
|
-
await (0, fs_extra_1.writeFile)(file, newContent);
|
|
258
|
+
await (0, fs_extra_1.writeFile)(file, `${newContent}\n${PATCH_WARNING}`);
|
|
254
259
|
console.log('Done');
|
|
255
260
|
return true;
|
|
256
261
|
};
|
|
@@ -265,25 +270,49 @@ const getServerFile = (root, includeBase = true) => {
|
|
|
265
270
|
}
|
|
266
271
|
return (0, utils_1.findModuleFromBase)({ candidates, paths: [root] });
|
|
267
272
|
};
|
|
273
|
+
/**
|
|
274
|
+
* Find the source file for a given page route
|
|
275
|
+
*/
|
|
276
|
+
const getSourceFileForPage = (page, root) => {
|
|
277
|
+
for (const extension of ['ts', 'js']) {
|
|
278
|
+
const file = (0, pathe_1.join)(root, `${page}.${extension}`);
|
|
279
|
+
if ((0, fs_extra_1.existsSync)(file)) {
|
|
280
|
+
return file;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
exports.getSourceFileForPage = getSourceFileForPage;
|
|
285
|
+
/**
|
|
286
|
+
* Reads the node file trace file for a given file, and resolves the dependencies
|
|
287
|
+
*/
|
|
288
|
+
const getDependenciesOfFile = async (file) => {
|
|
289
|
+
const nft = `${file}.nft.json`;
|
|
290
|
+
if (!(0, fs_extra_1.existsSync)(nft)) {
|
|
291
|
+
return [];
|
|
292
|
+
}
|
|
293
|
+
const dependencies = await (0, fs_extra_1.readJson)(nft, 'utf8');
|
|
294
|
+
return dependencies.files.map((dep) => (0, pathe_1.resolve)(file, dep));
|
|
295
|
+
};
|
|
296
|
+
exports.getDependenciesOfFile = getDependenciesOfFile;
|
|
268
297
|
const baseServerReplacements = [
|
|
269
298
|
[`let ssgCacheKey = `, `let ssgCacheKey = process.env._BYPASS_SSG || `],
|
|
270
299
|
];
|
|
271
300
|
const nextServerReplacements = [
|
|
272
301
|
[
|
|
273
302
|
`getMiddlewareManifest() {\n if (this.minimalMode) return null;`,
|
|
274
|
-
`getMiddlewareManifest() {\n if (this.minimalMode || process.env.
|
|
303
|
+
`getMiddlewareManifest() {\n if (this.minimalMode || !process.env.NEXT_DISABLE_NETLIFY_EDGE) return null;`,
|
|
275
304
|
],
|
|
276
305
|
[
|
|
277
306
|
`generateCatchAllMiddlewareRoute(devReady) {\n if (this.minimalMode) return []`,
|
|
278
|
-
`generateCatchAllMiddlewareRoute(devReady) {\n if (this.minimalMode || process.env.
|
|
307
|
+
`generateCatchAllMiddlewareRoute(devReady) {\n if (this.minimalMode || !process.env.NEXT_DISABLE_NETLIFY_EDGE) return [];`,
|
|
279
308
|
],
|
|
280
309
|
[
|
|
281
310
|
`generateCatchAllMiddlewareRoute() {\n if (this.minimalMode) return undefined;`,
|
|
282
|
-
`generateCatchAllMiddlewareRoute() {\n if (this.minimalMode || process.env.
|
|
311
|
+
`generateCatchAllMiddlewareRoute() {\n if (this.minimalMode || !process.env.NEXT_DISABLE_NETLIFY_EDGE) return undefined;`,
|
|
283
312
|
],
|
|
284
313
|
[
|
|
285
314
|
`getMiddlewareManifest() {\n if (this.minimalMode) {`,
|
|
286
|
-
`getMiddlewareManifest() {\n if (!this.minimalMode &&
|
|
315
|
+
`getMiddlewareManifest() {\n if (!this.minimalMode && process.env.NEXT_DISABLE_NETLIFY_EDGE) {`,
|
|
287
316
|
],
|
|
288
317
|
];
|
|
289
318
|
const patchNextFiles = async (root) => {
|
package/lib/helpers/functions.js
CHANGED
|
@@ -3,23 +3,45 @@ 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.setupImageFunction = exports.generatePagesResolver = exports.generateFunctions = void 0;
|
|
6
|
+
exports.getApiRouteConfigs = exports.setupImageFunction = exports.generatePagesResolver = exports.generateFunctions = void 0;
|
|
7
7
|
const node_bridge_1 = __importDefault(require("@vercel/node-bridge"));
|
|
8
8
|
const fs_extra_1 = require("fs-extra");
|
|
9
9
|
const pathe_1 = require("pathe");
|
|
10
10
|
const constants_1 = require("../constants");
|
|
11
|
+
const getApiHandler_1 = require("../templates/getApiHandler");
|
|
11
12
|
const getHandler_1 = require("../templates/getHandler");
|
|
12
13
|
const getPageResolver_1 = require("../templates/getPageResolver");
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
14
|
+
const analysis_1 = require("./analysis");
|
|
15
|
+
const files_1 = require("./files");
|
|
16
|
+
const utils_1 = require("./utils");
|
|
17
|
+
const generateFunctions = async ({ FUNCTIONS_SRC = constants_1.DEFAULT_FUNCTIONS_SRC, INTERNAL_FUNCTIONS_SRC, PUBLISH_DIR }, appDir, apiRoutes) => {
|
|
18
|
+
const publish = (0, pathe_1.resolve)(PUBLISH_DIR);
|
|
19
|
+
const functionsDir = (0, pathe_1.resolve)(INTERNAL_FUNCTIONS_SRC || FUNCTIONS_SRC);
|
|
20
|
+
console.log({ functionsDir });
|
|
21
|
+
const functionDir = (0, pathe_1.join)(functionsDir, constants_1.HANDLER_FUNCTION_NAME);
|
|
22
|
+
const publishDir = (0, pathe_1.relative)(functionDir, publish);
|
|
23
|
+
for (const { route, config, compiled } of apiRoutes) {
|
|
24
|
+
const apiHandlerSource = await (0, getApiHandler_1.getApiHandler)({
|
|
25
|
+
page: route,
|
|
26
|
+
config,
|
|
27
|
+
});
|
|
28
|
+
const functionName = (0, utils_1.getFunctionNameForPage)(route, config.type === 'experimental-background');
|
|
29
|
+
await (0, fs_extra_1.ensureDir)((0, pathe_1.join)(functionsDir, functionName));
|
|
30
|
+
await (0, fs_extra_1.writeFile)((0, pathe_1.join)(functionsDir, functionName, `${functionName}.js`), apiHandlerSource);
|
|
31
|
+
await (0, fs_extra_1.copyFile)(node_bridge_1.default, (0, pathe_1.join)(functionsDir, functionName, 'bridge.js'));
|
|
32
|
+
await (0, fs_extra_1.copyFile)((0, pathe_1.join)(__dirname, '..', '..', 'lib', 'templates', 'handlerUtils.js'), (0, pathe_1.join)(functionsDir, functionName, 'handlerUtils.js'));
|
|
33
|
+
const resolverSource = await (0, getPageResolver_1.getSinglePageResolver)({
|
|
34
|
+
functionsDir,
|
|
35
|
+
sourceFile: (0, pathe_1.join)(publish, 'server', compiled),
|
|
36
|
+
});
|
|
37
|
+
await (0, fs_extra_1.writeFile)((0, pathe_1.join)(functionsDir, functionName, 'pages.js'), resolverSource);
|
|
38
|
+
}
|
|
39
|
+
const writeHandler = async (functionName, isODB) => {
|
|
18
40
|
const handlerSource = await (0, getHandler_1.getHandler)({ isODB, publishDir, appDir: (0, pathe_1.relative)(functionDir, appDir) });
|
|
19
|
-
await (0, fs_extra_1.ensureDir)((0, pathe_1.join)(functionsDir,
|
|
20
|
-
await (0, fs_extra_1.writeFile)((0, pathe_1.join)(functionsDir,
|
|
21
|
-
await (0, fs_extra_1.copyFile)(node_bridge_1.default, (0, pathe_1.join)(functionsDir,
|
|
22
|
-
await (0, fs_extra_1.copyFile)((0, pathe_1.join)(__dirname, '..', '..', 'lib', 'templates', 'handlerUtils.js'), (0, pathe_1.join)(functionsDir,
|
|
41
|
+
await (0, fs_extra_1.ensureDir)((0, pathe_1.join)(functionsDir, functionName));
|
|
42
|
+
await (0, fs_extra_1.writeFile)((0, pathe_1.join)(functionsDir, functionName, `${functionName}.js`), handlerSource);
|
|
43
|
+
await (0, fs_extra_1.copyFile)(node_bridge_1.default, (0, pathe_1.join)(functionsDir, functionName, 'bridge.js'));
|
|
44
|
+
await (0, fs_extra_1.copyFile)((0, pathe_1.join)(__dirname, '..', '..', 'lib', 'templates', 'handlerUtils.js'), (0, pathe_1.join)(functionsDir, functionName, 'handlerUtils.js'));
|
|
23
45
|
};
|
|
24
46
|
await writeHandler(constants_1.HANDLER_FUNCTION_NAME, false);
|
|
25
47
|
await writeHandler(constants_1.ODB_FUNCTION_NAME, true);
|
|
@@ -40,7 +62,7 @@ const generatePagesResolver = async ({ constants: { INTERNAL_FUNCTIONS_SRC, FUNC
|
|
|
40
62
|
};
|
|
41
63
|
exports.generatePagesResolver = generatePagesResolver;
|
|
42
64
|
// Move our next/image function into the correct functions directory
|
|
43
|
-
const setupImageFunction = async ({ constants: { INTERNAL_FUNCTIONS_SRC, FUNCTIONS_SRC = constants_1.DEFAULT_FUNCTIONS_SRC }, imageconfig = {}, netlifyConfig, basePath, remotePatterns, }) => {
|
|
65
|
+
const setupImageFunction = async ({ constants: { INTERNAL_FUNCTIONS_SRC, FUNCTIONS_SRC = constants_1.DEFAULT_FUNCTIONS_SRC }, imageconfig = {}, netlifyConfig, basePath, remotePatterns, responseHeaders, }) => {
|
|
44
66
|
const functionsPath = INTERNAL_FUNCTIONS_SRC || FUNCTIONS_SRC;
|
|
45
67
|
const functionName = `${constants_1.IMAGE_FUNCTION_NAME}.js`;
|
|
46
68
|
const functionDirectory = (0, pathe_1.join)(functionsPath, constants_1.IMAGE_FUNCTION_NAME);
|
|
@@ -49,6 +71,7 @@ const setupImageFunction = async ({ constants: { INTERNAL_FUNCTIONS_SRC, FUNCTIO
|
|
|
49
71
|
...imageconfig,
|
|
50
72
|
basePath: [basePath, constants_1.IMAGE_FUNCTION_NAME].join('/'),
|
|
51
73
|
remotePatterns,
|
|
74
|
+
responseHeaders,
|
|
52
75
|
});
|
|
53
76
|
await (0, fs_extra_1.copyFile)((0, pathe_1.join)(__dirname, '..', '..', 'lib', 'templates', 'ipx.js'), (0, pathe_1.join)(functionDirectory, functionName));
|
|
54
77
|
const imagePath = imageconfig.path || '/_next/image';
|
|
@@ -75,3 +98,16 @@ const setupImageFunction = async ({ constants: { INTERNAL_FUNCTIONS_SRC, FUNCTIO
|
|
|
75
98
|
}
|
|
76
99
|
};
|
|
77
100
|
exports.setupImageFunction = setupImageFunction;
|
|
101
|
+
/**
|
|
102
|
+
* Look for API routes, and extract the config from the source file.
|
|
103
|
+
*/
|
|
104
|
+
const getApiRouteConfigs = async (publish, baseDir) => {
|
|
105
|
+
const pages = await (0, fs_extra_1.readJSON)((0, pathe_1.join)(publish, 'server', 'pages-manifest.json'));
|
|
106
|
+
const apiRoutes = Object.keys(pages).filter((page) => page.startsWith('/api/'));
|
|
107
|
+
const pagesDir = (0, pathe_1.join)(baseDir, 'pages');
|
|
108
|
+
return Promise.all(apiRoutes.map(async (apiRoute) => {
|
|
109
|
+
const filePath = (0, files_1.getSourceFileForPage)(apiRoute, pagesDir);
|
|
110
|
+
return { route: apiRoute, config: await (0, analysis_1.extractConfigFromFile)(filePath), compiled: pages[apiRoute] };
|
|
111
|
+
}));
|
|
112
|
+
};
|
|
113
|
+
exports.getApiRouteConfigs = getApiRouteConfigs;
|
package/lib/helpers/redirects.js
CHANGED
|
@@ -146,7 +146,7 @@ const generateDynamicRewrites = ({ dynamicRoutes, prerenderedDynamicRoutes, midd
|
|
|
146
146
|
dynamicRewrites,
|
|
147
147
|
};
|
|
148
148
|
};
|
|
149
|
-
const generateRedirects = async ({ netlifyConfig, nextConfig: { i18n, basePath, trailingSlash, appDir }, buildId, }) => {
|
|
149
|
+
const generateRedirects = async ({ netlifyConfig, nextConfig: { i18n, basePath, trailingSlash, appDir }, buildId, apiRoutes, }) => {
|
|
150
150
|
const { dynamicRoutes: prerenderedDynamicRoutes, routes: prerenderedStaticRoutes } = await (0, fs_extra_1.readJSON)((0, pathe_1.join)(netlifyConfig.build.publish, 'prerender-manifest.json'));
|
|
151
151
|
const { dynamicRoutes, staticRoutes } = await (0, fs_extra_1.readJSON)((0, pathe_1.join)(netlifyConfig.build.publish, 'routes-manifest.json'));
|
|
152
152
|
netlifyConfig.redirects.push(...constants_1.HIDDEN_PATHS.map((path) => ({
|
|
@@ -161,7 +161,7 @@ const generateRedirects = async ({ netlifyConfig, nextConfig: { i18n, basePath,
|
|
|
161
161
|
// This is only used in prod, so dev uses `next dev` directly
|
|
162
162
|
netlifyConfig.redirects.push(
|
|
163
163
|
// API routes always need to be served from the regular function
|
|
164
|
-
...(0, utils_1.getApiRewrites)(basePath),
|
|
164
|
+
...(0, utils_1.getApiRewrites)(basePath, apiRoutes),
|
|
165
165
|
// Preview mode gets forced to the function, to bypass pre-rendered pages, but static files need to be skipped
|
|
166
166
|
...(await (0, utils_1.getPreviewRewrites)({ basePath, appDir })));
|
|
167
167
|
const middleware = await (0, files_1.getMiddleware)(netlifyConfig.build.publish);
|
package/lib/helpers/utils.js
CHANGED
|
@@ -3,10 +3,22 @@ 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.isNextAuthInstalled = exports.findModuleFromBase = exports.shouldSkip = exports.getPreviewRewrites = exports.getApiRewrites = exports.redirectsForNextRouteWithData = exports.redirectsForNextRoute = exports.isApiRoute = exports.routeToDataRoute = exports.netlifyRoutesForNextRouteWithData = exports.toNetlifyRoute = void 0;
|
|
6
|
+
exports.getCustomImageResponseHeaders = exports.isNextAuthInstalled = exports.findModuleFromBase = exports.shouldSkip = exports.getPreviewRewrites = exports.getApiRewrites = exports.redirectsForNextRouteWithData = exports.redirectsForNextRoute = 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");
|
|
10
|
+
const RESERVED_FILENAME = /[^\w_-]/g;
|
|
11
|
+
/**
|
|
12
|
+
* Given a Next route, generates a valid Netlify function name.
|
|
13
|
+
* If "background" is true then the function name will have `-background`
|
|
14
|
+
* appended to it, meaning that it is executed as a background function.
|
|
15
|
+
*/
|
|
16
|
+
const getFunctionNameForPage = (page, background = false) => `${page
|
|
17
|
+
.replace(constants_1.CATCH_ALL_REGEX, '_$1-SPLAT')
|
|
18
|
+
.replace(constants_1.OPTIONAL_CATCH_ALL_REGEX, '-SPLAT')
|
|
19
|
+
.replace(constants_1.DYNAMIC_PARAMETER_REGEX, '_$1-PARAM')
|
|
20
|
+
.replace(RESERVED_FILENAME, '_')}-${background ? 'background' : 'handler'}`;
|
|
21
|
+
exports.getFunctionNameForPage = getFunctionNameForPage;
|
|
10
22
|
const toNetlifyRoute = (nextRoute) => {
|
|
11
23
|
const netlifyRoutes = [nextRoute];
|
|
12
24
|
// If the route is an optional catch-all route, we need to add a second
|
|
@@ -77,18 +89,33 @@ const redirectsForNextRouteWithData = ({ route, dataRoute, basePath, to, status
|
|
|
77
89
|
force,
|
|
78
90
|
}));
|
|
79
91
|
exports.redirectsForNextRouteWithData = redirectsForNextRouteWithData;
|
|
80
|
-
const getApiRewrites = (basePath) =>
|
|
81
|
-
{
|
|
82
|
-
from
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
+
const getApiRewrites = (basePath, apiRoutes) => {
|
|
93
|
+
const apiRewrites = apiRoutes.map((apiRoute) => {
|
|
94
|
+
const [from] = (0, exports.toNetlifyRoute)(`${basePath}${apiRoute.route}`);
|
|
95
|
+
// Scheduled functions can't be invoked directly, so we 404 them.
|
|
96
|
+
if (apiRoute.config.type === 'experimental-scheduled') {
|
|
97
|
+
return { from, to: '/404.html', status: 404 };
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
from,
|
|
101
|
+
to: `/.netlify/functions/${(0, exports.getFunctionNameForPage)(apiRoute.route, apiRoute.config.type === 'experimental-background')}`,
|
|
102
|
+
status: 200,
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
return [
|
|
106
|
+
...apiRewrites,
|
|
107
|
+
{
|
|
108
|
+
from: `${basePath}/api`,
|
|
109
|
+
to: constants_1.HANDLER_FUNCTION_PATH,
|
|
110
|
+
status: 200,
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
from: `${basePath}/api/*`,
|
|
114
|
+
to: constants_1.HANDLER_FUNCTION_PATH,
|
|
115
|
+
status: 200,
|
|
116
|
+
},
|
|
117
|
+
];
|
|
118
|
+
};
|
|
92
119
|
exports.getApiRewrites = getApiRewrites;
|
|
93
120
|
const getPreviewRewrites = async ({ basePath, appDir }) => {
|
|
94
121
|
const publicFiles = await (0, globby_1.default)('**/*', { cwd: (0, pathe_1.join)(appDir, 'public') });
|
|
@@ -136,7 +163,7 @@ const findModuleFromBase = ({ paths, candidates }) => {
|
|
|
136
163
|
exports.findModuleFromBase = findModuleFromBase;
|
|
137
164
|
const isNextAuthInstalled = () => {
|
|
138
165
|
try {
|
|
139
|
-
// eslint-disable-next-line import/no-unassigned-import, import/no-
|
|
166
|
+
// eslint-disable-next-line import/no-unassigned-import, import/no-extraneous-dependencies, n/no-extraneous-require
|
|
140
167
|
require('next-auth');
|
|
141
168
|
return true;
|
|
142
169
|
}
|
|
@@ -146,3 +173,12 @@ const isNextAuthInstalled = () => {
|
|
|
146
173
|
}
|
|
147
174
|
};
|
|
148
175
|
exports.isNextAuthInstalled = isNextAuthInstalled;
|
|
176
|
+
const getCustomImageResponseHeaders = (headers) => {
|
|
177
|
+
const customImageResponseHeaders = headers.find((header) => { var _a; return (_a = header.for) === null || _a === void 0 ? void 0 : _a.startsWith('/_next/image/'); });
|
|
178
|
+
if (customImageResponseHeaders) {
|
|
179
|
+
return customImageResponseHeaders === null || customImageResponseHeaders === void 0 ? void 0 : customImageResponseHeaders.values;
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
};
|
|
183
|
+
exports.getCustomImageResponseHeaders = getCustomImageResponseHeaders;
|
|
184
|
+
/* eslint-enable max-lines */
|
|
@@ -42,7 +42,7 @@ const verifyNetlifyBuildVersion = ({ IS_LOCAL, NETLIFY_BUILD_VERSION, failBuild,
|
|
|
42
42
|
// We check for build version because that's what's available to us, but prompt about the cli because that's what they can upgrade
|
|
43
43
|
if (IS_LOCAL && !(0, semver_1.satisfies)(NETLIFY_BUILD_VERSION, REQUIRED_BUILD_VERSION, { includePrerelease: true })) {
|
|
44
44
|
return failBuild((0, outdent_1.outdent) `
|
|
45
|
-
This version of the
|
|
45
|
+
This version of the Next Runtime requires netlify-cli@6.12.4 or higher. Please upgrade and try again.
|
|
46
46
|
You can do this by running: "npm install -g netlify-cli@latest" or "yarn global add netlify-cli@latest"
|
|
47
47
|
`);
|
|
48
48
|
}
|
|
@@ -136,7 +136,7 @@ exports.checkZipSize = checkZipSize;
|
|
|
136
136
|
const getProblematicUserRewrites = ({ redirects, basePath, }) => {
|
|
137
137
|
const userRewrites = [];
|
|
138
138
|
for (const redirect of redirects) {
|
|
139
|
-
// This is the first of the
|
|
139
|
+
// This is the first of the runtime-generated redirects so we can stop checking
|
|
140
140
|
if (redirect.from === `${basePath}/_next/static/*` && redirect.to === `/static/:splat` && redirect.status === 200) {
|
|
141
141
|
break;
|
|
142
142
|
}
|
|
@@ -159,7 +159,7 @@ const warnForProblematicUserRewrites = ({ redirects, basePath, }) => {
|
|
|
159
159
|
return;
|
|
160
160
|
}
|
|
161
161
|
console.log((0, chalk_1.yellowBright)((0, outdent_1.outdent) `
|
|
162
|
-
You have the following Netlify rewrite${userRewrites.length === 1 ? '' : 's'} that might cause conflicts with the Next.js
|
|
162
|
+
You have the following Netlify rewrite${userRewrites.length === 1 ? '' : 's'} that might cause conflicts with the Next.js Runtime:
|
|
163
163
|
|
|
164
164
|
${(0, chalk_1.reset)(userRewrites.map(({ from, to, status }) => `- ${from} ${to} ${status}`).join('\n'))}
|
|
165
165
|
|
package/lib/index.js
CHANGED
|
@@ -8,6 +8,7 @@ const outdent_1 = require("outdent");
|
|
|
8
8
|
const constants_1 = require("./constants");
|
|
9
9
|
const cache_1 = require("./helpers/cache");
|
|
10
10
|
const config_1 = require("./helpers/config");
|
|
11
|
+
const dev_1 = require("./helpers/dev");
|
|
11
12
|
const edge_1 = require("./helpers/edge");
|
|
12
13
|
const files_1 = require("./helpers/files");
|
|
13
14
|
const functions_1 = require("./helpers/functions");
|
|
@@ -20,7 +21,7 @@ const plugin = {
|
|
|
20
21
|
const { publish } = netlifyConfig.build;
|
|
21
22
|
if ((0, utils_1.shouldSkip)()) {
|
|
22
23
|
await (0, cache_1.restoreCache)({ cache, publish });
|
|
23
|
-
console.log('Not running
|
|
24
|
+
console.log('Not running Next Runtime');
|
|
24
25
|
if ((0, fs_extra_1.existsSync)((0, path_1.join)(constants.INTERNAL_FUNCTIONS_SRC, constants_1.HANDLER_FUNCTION_NAME))) {
|
|
25
26
|
console.log(`Please ensure you remove any generated functions from ${constants.INTERNAL_FUNCTIONS_SRC}`);
|
|
26
27
|
}
|
|
@@ -34,7 +35,6 @@ const plugin = {
|
|
|
34
35
|
netlifyConfig.build.environment.NEXT_PRIVATE_TARGET = 'server';
|
|
35
36
|
},
|
|
36
37
|
async onBuild({ constants, netlifyConfig, utils: { build: { failBuild }, }, }) {
|
|
37
|
-
var _a;
|
|
38
38
|
if ((0, utils_1.shouldSkip)()) {
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
@@ -45,6 +45,28 @@ const plugin = {
|
|
|
45
45
|
publish,
|
|
46
46
|
failBuild,
|
|
47
47
|
});
|
|
48
|
+
const middlewareManifest = await (0, edge_1.loadMiddlewareManifest)(netlifyConfig);
|
|
49
|
+
let usingEdge = false;
|
|
50
|
+
if ((middlewareManifest === null || middlewareManifest === void 0 ? void 0 : middlewareManifest.functions) && Object.keys(middlewareManifest.functions).length !== 0) {
|
|
51
|
+
usingEdge = true;
|
|
52
|
+
if (process.env.NEXT_DISABLE_NETLIFY_EDGE) {
|
|
53
|
+
failBuild((0, outdent_1.outdent) `
|
|
54
|
+
You are using Next.js experimental edge runtime, but have set NEXT_DISABLE_NETLIFY_EDGE to true. This is not supported.
|
|
55
|
+
To use edge runtime, remove the env var ${(0, chalk_1.bold) `NEXT_DISABLE_NETLIFY_EDGE`}.
|
|
56
|
+
`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if ((middlewareManifest === null || middlewareManifest === void 0 ? void 0 : middlewareManifest.middleware) && Object.keys(middlewareManifest.middleware).length !== 0) {
|
|
60
|
+
usingEdge = true;
|
|
61
|
+
if (process.env.NEXT_DISABLE_NETLIFY_EDGE) {
|
|
62
|
+
console.log((0, chalk_1.redBright)((0, outdent_1.outdent) `
|
|
63
|
+
You are using Next.js Middleware without Netlify Edge Functions.
|
|
64
|
+
This is deprecated because it negatively affects performance and will disable ISR and static rendering.
|
|
65
|
+
It also disables advanced middleware features from @netlify/next
|
|
66
|
+
To get the best performance and use Netlify Edge Functions, remove the env var ${(0, chalk_1.bold) `NEXT_DISABLE_NETLIFY_EDGE`}.
|
|
67
|
+
`));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
48
70
|
if (experimental.images) {
|
|
49
71
|
experimentalRemotePatterns = experimental.images.remotePatterns || [];
|
|
50
72
|
}
|
|
@@ -63,10 +85,11 @@ const plugin = {
|
|
|
63
85
|
}
|
|
64
86
|
const buildId = (0, fs_extra_1.readFileSync)((0, path_1.join)(publish, 'BUILD_ID'), 'utf8').trim();
|
|
65
87
|
await (0, config_1.configureHandlerFunctions)({ netlifyConfig, ignore, publish: (0, path_1.relative)(process.cwd(), publish) });
|
|
66
|
-
await (0, functions_1.
|
|
88
|
+
const apiRoutes = await (0, functions_1.getApiRouteConfigs)(publish, appDir);
|
|
89
|
+
await (0, functions_1.generateFunctions)(constants, appDir, apiRoutes);
|
|
67
90
|
await (0, functions_1.generatePagesResolver)({ target, constants });
|
|
68
91
|
await (0, files_1.movePublicFiles)({ appDir, outdir, publish });
|
|
69
|
-
await (0, files_1.patchNextFiles)(
|
|
92
|
+
await (0, files_1.patchNextFiles)(appDir);
|
|
70
93
|
if (!process.env.SERVE_STATIC_FILES_FROM_ORIGIN) {
|
|
71
94
|
await (0, files_1.moveStaticPages)({ target, netlifyConfig, i18n, basePath });
|
|
72
95
|
}
|
|
@@ -80,35 +103,28 @@ const plugin = {
|
|
|
80
103
|
netlifyConfig,
|
|
81
104
|
basePath,
|
|
82
105
|
remotePatterns: experimentalRemotePatterns,
|
|
106
|
+
responseHeaders: (0, utils_1.getCustomImageResponseHeaders)(netlifyConfig.headers),
|
|
83
107
|
});
|
|
84
108
|
await (0, redirects_1.generateRedirects)({
|
|
85
109
|
netlifyConfig,
|
|
86
110
|
nextConfig: { basePath, i18n, trailingSlash, appDir },
|
|
87
111
|
buildId,
|
|
112
|
+
apiRoutes,
|
|
88
113
|
});
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
114
|
+
if (usingEdge) {
|
|
115
|
+
await (0, edge_1.writeEdgeFunctions)(netlifyConfig);
|
|
116
|
+
await (0, edge_1.enableEdgeInNextConfig)(publish);
|
|
92
117
|
console.log((0, outdent_1.outdent) `
|
|
93
|
-
✨ Deploying to ${(0, chalk_1.greenBright) `Netlify Edge Functions`} ✨
|
|
118
|
+
✨ Deploying middleware and functions to ${(0, chalk_1.greenBright) `Netlify Edge Functions`} ✨
|
|
94
119
|
This feature is in beta. Please share your feedback here: https://ntl.fyi/next-netlify-edge
|
|
95
120
|
`);
|
|
96
|
-
await (0, edge_1.updateConfig)(publish);
|
|
97
|
-
}
|
|
98
|
-
const middlewareManifest = await (0, edge_1.loadMiddlewareManifest)(netlifyConfig);
|
|
99
|
-
if (!process.env.NEXT_USE_NETLIFY_EDGE && ((_a = middlewareManifest === null || middlewareManifest === void 0 ? void 0 : middlewareManifest.sortedMiddleware) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
100
|
-
console.log((0, chalk_1.yellowBright)((0, outdent_1.outdent) `
|
|
101
|
-
You are using Next.js Middleware without Netlify Edge Functions.
|
|
102
|
-
This will soon be deprecated because it negatively affects performance and will disable ISR and static rendering.
|
|
103
|
-
To get the best performance and use Netlify Edge Functions, set the env var ${(0, chalk_1.bold) `NEXT_USE_NETLIFY_EDGE=true`}.
|
|
104
|
-
`));
|
|
105
121
|
}
|
|
106
122
|
},
|
|
107
123
|
async onPostBuild({ netlifyConfig: { build: { publish }, redirects, headers, }, utils: { status, cache, functions, build: { failBuild }, }, constants: { FUNCTIONS_DIST }, }) {
|
|
108
124
|
await (0, cache_1.saveCache)({ cache, publish });
|
|
109
125
|
if ((0, utils_1.shouldSkip)()) {
|
|
110
126
|
status.show({
|
|
111
|
-
title: '
|
|
127
|
+
title: 'Next Runtime did not run',
|
|
112
128
|
summary: `Next cache was stored, but all other functions were skipped because ${process.env.NETLIFY_NEXT_PLUGIN_SKIP
|
|
113
129
|
? `NETLIFY_NEXT_PLUGIN_SKIP is set`
|
|
114
130
|
: `NEXT_PLUGIN_FORCE_RUN is set to ${process.env.NEXT_PLUGIN_FORCE_RUN}`}`,
|
|
@@ -122,8 +138,26 @@ const plugin = {
|
|
|
122
138
|
(0, config_1.generateCustomHeaders)(nextConfig, headers);
|
|
123
139
|
(0, verification_1.warnForProblematicUserRewrites)({ basePath, redirects });
|
|
124
140
|
(0, verification_1.warnForRootRedirects)({ appDir });
|
|
125
|
-
await (0, files_1.unpatchNextFiles)(basePath);
|
|
126
141
|
},
|
|
127
142
|
};
|
|
128
|
-
|
|
143
|
+
// The types haven't been updated yet
|
|
144
|
+
const nextRuntime = (_inputs, meta = {}) => {
|
|
145
|
+
var _a;
|
|
146
|
+
if (!((_a = meta === null || meta === void 0 ? void 0 : meta.events) === null || _a === void 0 ? void 0 : _a.has('onPreDev'))) {
|
|
147
|
+
return {
|
|
148
|
+
...plugin,
|
|
149
|
+
onEnd: ({ utils }) => {
|
|
150
|
+
utils.status.show({
|
|
151
|
+
title: 'Please upgrade to the latest version of the Netlify CLI',
|
|
152
|
+
summary: 'To support for the latest Next.js features, please upgrade to the latest version of the Netlify CLI',
|
|
153
|
+
});
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
...plugin,
|
|
159
|
+
onPreDev: dev_1.onPreDev,
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
module.exports = nextRuntime;
|
|
129
163
|
/* eslint-enable max-lines */
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getApiHandler = void 0;
|
|
4
|
+
// Aliasing like this means the editor may be able to syntax-highlight the string
|
|
5
|
+
const outdent_1 = require("outdent");
|
|
6
|
+
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
7
|
+
const { Server } = require('http');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
// eslint-disable-next-line n/prefer-global/url, n/prefer-global/url-search-params
|
|
10
|
+
const { URLSearchParams, URL } = require('url');
|
|
11
|
+
const { Bridge } = require('@vercel/node-bridge/bridge');
|
|
12
|
+
const { getMultiValueHeaders, getNextServer } = require('./handlerUtils');
|
|
13
|
+
// We return a function and then call `toString()` on it to serialise it as the launcher function
|
|
14
|
+
const makeHandler = (conf, app, pageRoot, page) => {
|
|
15
|
+
var _a;
|
|
16
|
+
// Change working directory into the site root, unless using Nx, which moves the
|
|
17
|
+
// dist directory and handles this itself
|
|
18
|
+
const dir = path.resolve(__dirname, app);
|
|
19
|
+
if (pageRoot.startsWith(dir)) {
|
|
20
|
+
process.chdir(dir);
|
|
21
|
+
}
|
|
22
|
+
// This is just so nft knows about the page entrypoints. It's not actually used
|
|
23
|
+
try {
|
|
24
|
+
// eslint-disable-next-line n/no-missing-require
|
|
25
|
+
require.resolve('./pages.js');
|
|
26
|
+
}
|
|
27
|
+
catch { }
|
|
28
|
+
// React assumes you want development mode if NODE_ENV is unset.
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-extra-semi
|
|
30
|
+
;
|
|
31
|
+
(_a = process.env).NODE_ENV || (_a.NODE_ENV = 'production');
|
|
32
|
+
// We don't want to write ISR files to disk in the lambda environment
|
|
33
|
+
conf.experimental.isrFlushToDisk = false;
|
|
34
|
+
// This is our flag that we use when patching the source
|
|
35
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
36
|
+
process.env._BYPASS_SSG = 'true';
|
|
37
|
+
for (const [key, value] of Object.entries(conf.env)) {
|
|
38
|
+
process.env[key] = String(value);
|
|
39
|
+
}
|
|
40
|
+
// We memoize this because it can be shared between requests, but don't instantiate it until
|
|
41
|
+
// the first request because we need the host and port.
|
|
42
|
+
let bridge;
|
|
43
|
+
const getBridge = (event) => {
|
|
44
|
+
if (bridge) {
|
|
45
|
+
return bridge;
|
|
46
|
+
}
|
|
47
|
+
// Scheduled functions don't have a URL, but we need to give one so Next knows the route to serve
|
|
48
|
+
const url = event.rawUrl ? new URL(event.rawUrl) : new URL(path, process.env.URL || 'http://n');
|
|
49
|
+
const port = Number.parseInt(url.port) || 80;
|
|
50
|
+
const NextServer = getNextServer();
|
|
51
|
+
const nextServer = new NextServer({
|
|
52
|
+
// We know we're just an API route, so can enable minimal mode
|
|
53
|
+
minimalMode: true,
|
|
54
|
+
conf,
|
|
55
|
+
dir,
|
|
56
|
+
customServer: false,
|
|
57
|
+
hostname: url.hostname,
|
|
58
|
+
port,
|
|
59
|
+
});
|
|
60
|
+
const requestHandler = nextServer.getRequestHandler();
|
|
61
|
+
const server = new Server(async (req, res) => {
|
|
62
|
+
try {
|
|
63
|
+
await requestHandler(req, res);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
console.error(error);
|
|
67
|
+
throw new Error('Error handling request. See function logs for details.');
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
bridge = new Bridge(server);
|
|
71
|
+
bridge.listen();
|
|
72
|
+
return bridge;
|
|
73
|
+
};
|
|
74
|
+
return async function handler(event, context) {
|
|
75
|
+
// Ensure that paths are encoded - but don't double-encode them
|
|
76
|
+
event.path = event.rawUrl ? new URL(event.rawUrl).pathname : page;
|
|
77
|
+
// Next expects to be able to parse the query from the URL
|
|
78
|
+
const query = new URLSearchParams(event.queryStringParameters).toString();
|
|
79
|
+
event.path = query ? `${event.path}?${query}` : event.path;
|
|
80
|
+
// We know the page
|
|
81
|
+
event.headers['x-matched-path'] = page;
|
|
82
|
+
const { headers, ...result } = await getBridge(event).launcher(event, context);
|
|
83
|
+
// Convert all headers to multiValueHeaders
|
|
84
|
+
const multiValueHeaders = getMultiValueHeaders(headers);
|
|
85
|
+
multiValueHeaders['cache-control'] = ['public, max-age=0, must-revalidate'];
|
|
86
|
+
console.log(`[${event.httpMethod}] ${event.path} (API)`);
|
|
87
|
+
return {
|
|
88
|
+
...result,
|
|
89
|
+
multiValueHeaders,
|
|
90
|
+
isBase64Encoded: result.encoding === 'base64',
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Handlers for API routes are simpler than page routes, but they each have a separate one
|
|
96
|
+
*/
|
|
97
|
+
const getApiHandler = ({ page, config, publishDir = '../../../.next', appDir = '../../..', }) =>
|
|
98
|
+
// This is a string, but if you have the right editor plugin it should format as js
|
|
99
|
+
(0, outdent_1.outdent /* javascript */) `
|
|
100
|
+
const { Server } = require("http");
|
|
101
|
+
// We copy the file here rather than requiring from the node module
|
|
102
|
+
const { Bridge } = require("./bridge");
|
|
103
|
+
const { getMaxAge, getMultiValueHeaders, getNextServer } = require('./handlerUtils')
|
|
104
|
+
|
|
105
|
+
${config.type === 'experimental-scheduled' ? `const { schedule } = require("@netlify/functions")` : ''}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
const { config } = require("${publishDir}/required-server-files.json")
|
|
109
|
+
let staticManifest
|
|
110
|
+
const path = require("path");
|
|
111
|
+
const pageRoot = path.resolve(path.join(__dirname, "${publishDir}", "serverless", "pages"));
|
|
112
|
+
const handler = (${makeHandler.toString()})(config, "${appDir}", pageRoot, ${JSON.stringify(page)})
|
|
113
|
+
exports.handler = ${config.type === 'experimental-scheduled' ? `schedule(${JSON.stringify(config.schedule)}, handler);` : 'handler'}
|
|
114
|
+
`;
|
|
115
|
+
exports.getApiHandler = getApiHandler;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getHandler = void 0;
|
|
4
|
+
// Aliasing like this means the editor may be able to syntax-highlight the string
|
|
4
5
|
const outdent_1 = require("outdent");
|
|
5
6
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
6
7
|
const { promises } = require('fs');
|
|
@@ -81,6 +82,13 @@ const makeHandler = (conf, app, pageRoot, staticManifest = [], mode = 'ssr') =>
|
|
|
81
82
|
// Next expects to be able to parse the query from the URL
|
|
82
83
|
const query = new URLSearchParams(event.queryStringParameters).toString();
|
|
83
84
|
event.path = query ? `${event.path}?${query}` : event.path;
|
|
85
|
+
const graphToken = event.netlifyGraphToken;
|
|
86
|
+
if (graphToken && requestMode !== 'ssr') {
|
|
87
|
+
// Prefix with underscore to help us determine the origin of the token
|
|
88
|
+
// allows us to write better error messages
|
|
89
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
90
|
+
process.env._NETLIFY_GRAPH_TOKEN = graphToken;
|
|
91
|
+
}
|
|
84
92
|
const { headers, ...result } = await getBridge(event).launcher(event, context);
|
|
85
93
|
// Convert all headers to multiValueHeaders
|
|
86
94
|
const multiValueHeaders = getMultiValueHeaders(headers);
|
|
@@ -112,14 +120,14 @@ const makeHandler = (conf, app, pageRoot, staticManifest = [], mode = 'ssr') =>
|
|
|
112
120
|
};
|
|
113
121
|
const getHandler = ({ isODB = false, publishDir = '../../../.next', appDir = '../../..' }) =>
|
|
114
122
|
// This is a string, but if you have the right editor plugin it should format as js
|
|
115
|
-
(0, outdent_1.outdent) `
|
|
123
|
+
(0, outdent_1.outdent /* javascript */) `
|
|
116
124
|
const { Server } = require("http");
|
|
117
125
|
const { promises } = require("fs");
|
|
118
126
|
// We copy the file here rather than requiring from the node module
|
|
119
127
|
const { Bridge } = require("./bridge");
|
|
120
128
|
const { augmentFsModule, getMaxAge, getMultiValueHeaders, getNextServer } = require('./handlerUtils')
|
|
121
129
|
|
|
122
|
-
const { builder } = require("@netlify/functions")
|
|
130
|
+
${isODB ? `const { builder } = require("@netlify/functions")` : ''}
|
|
123
131
|
const { config } = require("${publishDir}/required-server-files.json")
|
|
124
132
|
let staticManifest
|
|
125
133
|
try {
|
|
@@ -3,12 +3,14 @@ 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.getPageResolver = void 0;
|
|
6
|
+
exports.getSinglePageResolver = exports.getPageResolver = void 0;
|
|
7
7
|
const path_1 = require("path");
|
|
8
8
|
const outdent_1 = require("outdent");
|
|
9
|
+
const pathe_1 = require("pathe");
|
|
9
10
|
const slash_1 = __importDefault(require("slash"));
|
|
10
11
|
const tiny_glob_1 = __importDefault(require("tiny-glob"));
|
|
11
12
|
const constants_1 = require("../constants");
|
|
13
|
+
const files_1 = require("../helpers/files");
|
|
12
14
|
// Generate a file full of require.resolve() calls for all the pages in the
|
|
13
15
|
// build. This is used by the nft bundler to find all the pages.
|
|
14
16
|
const getPageResolver = async ({ publish, target }) => {
|
|
@@ -31,3 +33,22 @@ const getPageResolver = async ({ publish, target }) => {
|
|
|
31
33
|
`;
|
|
32
34
|
};
|
|
33
35
|
exports.getPageResolver = getPageResolver;
|
|
36
|
+
/**
|
|
37
|
+
* API routes only need the dependencies for a single entrypoint, so we use the
|
|
38
|
+
* NFT trace file to get the dependencies.
|
|
39
|
+
*/
|
|
40
|
+
const getSinglePageResolver = async ({ functionsDir, sourceFile, }) => {
|
|
41
|
+
const dependencies = await (0, files_1.getDependenciesOfFile)(sourceFile);
|
|
42
|
+
// We don't need the actual name, just the relative path.
|
|
43
|
+
const functionDir = (0, pathe_1.resolve)(functionsDir, 'functionName');
|
|
44
|
+
const pageFiles = [sourceFile, ...dependencies]
|
|
45
|
+
.map((file) => `require.resolve('${(0, pathe_1.relative)(functionDir, file)}')`)
|
|
46
|
+
.sort();
|
|
47
|
+
return (0, outdent_1.outdent /* javascript */) `
|
|
48
|
+
// This file is purely to allow nft to know about these pages.
|
|
49
|
+
try {
|
|
50
|
+
${pageFiles.join('\n ')}
|
|
51
|
+
} catch {}
|
|
52
|
+
`;
|
|
53
|
+
};
|
|
54
|
+
exports.getSinglePageResolver = getSinglePageResolver;
|
package/lib/templates/ipx.js
CHANGED
|
@@ -8,5 +8,6 @@ exports.handler = (0, ipx_1.createIPXHandler)({
|
|
|
8
8
|
basePath: imageconfig_json_1.basePath,
|
|
9
9
|
domains: imageconfig_json_1.domains,
|
|
10
10
|
remotePatterns: imageconfig_json_1.remotePatterns,
|
|
11
|
+
responseHeaders: imageconfig_json_1.responseHeaders,
|
|
11
12
|
});
|
|
12
13
|
/* eslint-enable n/no-missing-import, import/no-unresolved, @typescript-eslint/ban-ts-comment */
|
package/manifest.yml
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
name: netlify-
|
|
1
|
+
name: netlify-next-runtime-experimental
|
package/package.json
CHANGED
|
@@ -1,30 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/plugin-nextjs",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.17.1-api-routes.0",
|
|
4
4
|
"description": "Run Next.js seamlessly on Netlify",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"files": [
|
|
7
7
|
"lib/**/*",
|
|
8
8
|
"src/templates/edge/*",
|
|
9
|
-
"manifest.yml"
|
|
10
|
-
"middleware.js"
|
|
9
|
+
"manifest.yml"
|
|
11
10
|
],
|
|
12
|
-
"typesVersions": {
|
|
13
|
-
"*": {
|
|
14
|
-
"middleware": [
|
|
15
|
-
"dist-types/middleware"
|
|
16
|
-
]
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
"exports": {
|
|
20
|
-
".": "./lib/index.js",
|
|
21
|
-
"./middleware": "./lib/middleware/index.js"
|
|
22
|
-
},
|
|
23
11
|
"dependencies": {
|
|
24
|
-
"@netlify/
|
|
25
|
-
"@netlify/
|
|
12
|
+
"@netlify/esbuild": "0.14.25",
|
|
13
|
+
"@netlify/functions": "^1.2.0",
|
|
14
|
+
"@netlify/ipx": "^1.2.2",
|
|
26
15
|
"@vercel/node-bridge": "^2.1.0",
|
|
27
16
|
"chalk": "^4.1.2",
|
|
17
|
+
"execa": "^5.1.1",
|
|
28
18
|
"fs-extra": "^10.0.0",
|
|
29
19
|
"globby": "^11.0.4",
|
|
30
20
|
"moize": "^6.1.0",
|
|
@@ -34,13 +24,13 @@
|
|
|
34
24
|
"p-limit": "^3.1.0",
|
|
35
25
|
"pathe": "^0.2.0",
|
|
36
26
|
"pretty-bytes": "^5.6.0",
|
|
37
|
-
"slash": "^3.0.0",
|
|
38
27
|
"semver": "^7.3.5",
|
|
28
|
+
"slash": "^3.0.0",
|
|
39
29
|
"tiny-glob": "^0.2.9"
|
|
40
30
|
},
|
|
41
31
|
"devDependencies": {
|
|
42
32
|
"@delucis/if-env": "^1.1.2",
|
|
43
|
-
"@netlify/build": "^27.
|
|
33
|
+
"@netlify/build": "^27.15.6",
|
|
44
34
|
"@types/fs-extra": "^9.0.13",
|
|
45
35
|
"@types/jest": "^27.4.1",
|
|
46
36
|
"@types/node": "^17.0.25",
|
|
@@ -63,15 +53,14 @@
|
|
|
63
53
|
},
|
|
64
54
|
"repository": {
|
|
65
55
|
"type": "git",
|
|
66
|
-
"url": "git+https://github.com/netlify/
|
|
56
|
+
"url": "git+https://github.com/netlify/next-runtime.git"
|
|
67
57
|
},
|
|
68
|
-
"
|
|
69
|
-
"license": "ISC",
|
|
58
|
+
"license": "MIT",
|
|
70
59
|
"bugs": {
|
|
71
|
-
"url": "https://github.com/netlify/
|
|
60
|
+
"url": "https://github.com/netlify/next-runtime/issues"
|
|
72
61
|
},
|
|
73
|
-
"homepage": "https://github.com/netlify/
|
|
62
|
+
"homepage": "https://github.com/netlify/next-runtime#readme",
|
|
74
63
|
"engines": {
|
|
75
64
|
"node": ">=12.0.0"
|
|
76
65
|
}
|
|
77
|
-
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { NextRequest } from 'https://esm.sh/v91/next@12.2.5/deno/dist/server/web/spec-extension/request.js'
|
|
2
|
+
import { NextResponse } from 'https://esm.sh/v91/next@12.2.5/deno/dist/server/web/spec-extension/response.js'
|
|
3
|
+
import { fromFileUrl } from 'https://deno.land/std@0.151.0/path/mod.ts'
|
|
4
|
+
import { buildResponse } from './utils.ts'
|
|
5
|
+
|
|
6
|
+
globalThis.NFRequestContextMap ||= new Map()
|
|
7
|
+
globalThis.__dirname = fromFileUrl(new URL('./', import.meta.url)).slice(0, -1)
|
|
8
|
+
|
|
9
|
+
// Check if a file exists, given a relative path
|
|
10
|
+
const exists = async (relativePath) => {
|
|
11
|
+
const path = fromFileUrl(new URL(relativePath, import.meta.url))
|
|
12
|
+
try {
|
|
13
|
+
await Deno.stat(path)
|
|
14
|
+
return true
|
|
15
|
+
} catch (error) {
|
|
16
|
+
if (error instanceof Deno.errors.NotFound) {
|
|
17
|
+
return false
|
|
18
|
+
}
|
|
19
|
+
throw error
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
let idx = 0
|
|
23
|
+
|
|
24
|
+
const handler = async (req, context) => {
|
|
25
|
+
// Uncomment when CLI update lands
|
|
26
|
+
// if (!Deno.env.get('NETLIFY_DEV')) {
|
|
27
|
+
// // Only run in dev
|
|
28
|
+
// return
|
|
29
|
+
// }
|
|
30
|
+
|
|
31
|
+
let middleware
|
|
32
|
+
// Dynamic imports and FS operations aren't allowed when deployed,
|
|
33
|
+
// but that's fine because this is only ever used locally.
|
|
34
|
+
// We don't want to just try importing and use that to test,
|
|
35
|
+
// because that would also throw if there's an error in the middleware,
|
|
36
|
+
// which we would want to surface not ignore.
|
|
37
|
+
if (await exists('../../middleware.js')) {
|
|
38
|
+
// We need to cache-bust the import because otherwise it will claim it
|
|
39
|
+
// doesn't exist if the user creates it after the server starts
|
|
40
|
+
const nextMiddleware = await import(`../../middleware.js#${idx++}`)
|
|
41
|
+
middleware = nextMiddleware.middleware
|
|
42
|
+
} else {
|
|
43
|
+
// No middleware, so we silently return
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// This is the format expected by Next.js
|
|
48
|
+
const geo = {
|
|
49
|
+
country: context.geo.country?.code,
|
|
50
|
+
region: context.geo.subdivision?.code,
|
|
51
|
+
city: context.geo.city,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// A default request id is fine locally
|
|
55
|
+
const requestId = req.headers.get('x-nf-request-id') || 'request-id'
|
|
56
|
+
|
|
57
|
+
globalThis.NFRequestContextMap.set(requestId, {
|
|
58
|
+
request: req,
|
|
59
|
+
context,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const request = {
|
|
63
|
+
headers: Object.fromEntries(req.headers.entries()),
|
|
64
|
+
geo,
|
|
65
|
+
method: req.method,
|
|
66
|
+
ip: context.ip,
|
|
67
|
+
body: req.body || undefined,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const nextRequest = new NextRequest(req, request)
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const response = await middleware(nextRequest)
|
|
74
|
+
return buildResponse({
|
|
75
|
+
result: { response: response || NextResponse.next(), waitUntil: Promise.resolve() },
|
|
76
|
+
request: req,
|
|
77
|
+
context,
|
|
78
|
+
})
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error(error)
|
|
81
|
+
return new Response(error.message, { status: 500 })
|
|
82
|
+
} finally {
|
|
83
|
+
if (requestId) {
|
|
84
|
+
globalThis.NFRequestContextMap.delete(requestId)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export default handler
|
|
@@ -45,6 +45,10 @@ declare global {
|
|
|
45
45
|
globalThis.NFRequestContextMap ||= new Map()
|
|
46
46
|
|
|
47
47
|
const handler = async (req: Request, context: Context) => {
|
|
48
|
+
if (Deno.env.get('NETLIFY_DEV')) {
|
|
49
|
+
// Don't run in dev
|
|
50
|
+
return
|
|
51
|
+
}
|
|
48
52
|
const url = new URL(req.url)
|
|
49
53
|
|
|
50
54
|
const geo = {
|
|
@@ -123,17 +123,38 @@ export const buildResponse = async ({
|
|
|
123
123
|
request.headers.set('x-nf-next-middleware', 'skip')
|
|
124
124
|
|
|
125
125
|
const rewrite = res.headers.get('x-middleware-rewrite')
|
|
126
|
+
|
|
127
|
+
// Data requests (i.e. requests for /_next/data ) need special handling
|
|
128
|
+
const isDataReq = request.headers.get('x-nextjs-data')
|
|
129
|
+
|
|
126
130
|
if (rewrite) {
|
|
127
131
|
const rewriteUrl = new URL(rewrite, request.url)
|
|
128
132
|
const baseUrl = new URL(request.url)
|
|
133
|
+
const relativeUrl = relativizeURL(rewrite, request.url)
|
|
134
|
+
|
|
135
|
+
// Data requests might be rewritten to an external URL
|
|
136
|
+
// This header tells the client router the redirect target, and if it's external then it will do a full navigation
|
|
137
|
+
if (isDataReq) {
|
|
138
|
+
res.headers.set('x-nextjs-rewrite', relativeUrl)
|
|
139
|
+
}
|
|
129
140
|
if (rewriteUrl.hostname !== baseUrl.hostname) {
|
|
130
141
|
// Netlify Edge Functions don't support proxying to external domains, but Next middleware does
|
|
131
142
|
const proxied = fetch(new Request(rewriteUrl.toString(), request))
|
|
132
143
|
return addMiddlewareHeaders(proxied, res)
|
|
133
144
|
}
|
|
134
|
-
res.headers.set('x-middleware-rewrite',
|
|
145
|
+
res.headers.set('x-middleware-rewrite', relativeUrl)
|
|
146
|
+
|
|
135
147
|
return addMiddlewareHeaders(context.rewrite(rewrite), res)
|
|
136
148
|
}
|
|
149
|
+
|
|
150
|
+
const redirect = res.headers.get('Location')
|
|
151
|
+
|
|
152
|
+
// Data requests shouldn;t automatically redirect in the browser (they might be HTML pages): they're handled by the router
|
|
153
|
+
if (redirect && isDataReq) {
|
|
154
|
+
res.headers.delete('location')
|
|
155
|
+
res.headers.set('x-nextjs-redirect', relativizeURL(redirect, request.url))
|
|
156
|
+
}
|
|
157
|
+
|
|
137
158
|
if (res.headers.get('x-middleware-next') === '1') {
|
|
138
159
|
return addMiddlewareHeaders(context.next(), res)
|
|
139
160
|
}
|
package/lib/middleware/index.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
-
};
|
|
16
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
__exportStar(require("./response"), exports);
|
|
18
|
-
__exportStar(require("./request"), exports);
|
|
19
|
-
__exportStar(require("./html-rewriter"), exports);
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MiddlewareRequest = void 0;
|
|
4
|
-
const server_1 = require("next/server");
|
|
5
|
-
const response_1 = require("./response");
|
|
6
|
-
/**
|
|
7
|
-
* Supercharge your Next middleware with Netlify Edge Functions
|
|
8
|
-
*/
|
|
9
|
-
class MiddlewareRequest extends Request {
|
|
10
|
-
constructor(nextRequest) {
|
|
11
|
-
super(nextRequest);
|
|
12
|
-
this.nextRequest = nextRequest;
|
|
13
|
-
if (!('Deno' in globalThis)) {
|
|
14
|
-
throw new Error('MiddlewareRequest only works in a Netlify Edge Function environment');
|
|
15
|
-
}
|
|
16
|
-
const requestId = nextRequest.headers.get('x-nf-request-id');
|
|
17
|
-
if (!requestId) {
|
|
18
|
-
throw new Error('Missing x-nf-request-id header');
|
|
19
|
-
}
|
|
20
|
-
const requestContext = globalThis.NFRequestContextMap.get(requestId);
|
|
21
|
-
if (!requestContext) {
|
|
22
|
-
throw new Error(`Could not find request context for request id ${requestId}`);
|
|
23
|
-
}
|
|
24
|
-
this.context = requestContext.context;
|
|
25
|
-
this.originalRequest = requestContext.request;
|
|
26
|
-
}
|
|
27
|
-
// Add the headers to the original request, which will be passed to the origin
|
|
28
|
-
applyHeaders() {
|
|
29
|
-
this.headers.forEach((value, name) => {
|
|
30
|
-
this.originalRequest.headers.set(name, value);
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
async next() {
|
|
34
|
-
this.applyHeaders();
|
|
35
|
-
const response = await this.context.next();
|
|
36
|
-
return new response_1.MiddlewareResponse(response);
|
|
37
|
-
}
|
|
38
|
-
rewrite(destination, init) {
|
|
39
|
-
if (typeof destination === 'string' && destination.startsWith('/')) {
|
|
40
|
-
destination = new URL(destination, this.url);
|
|
41
|
-
}
|
|
42
|
-
this.applyHeaders();
|
|
43
|
-
return server_1.NextResponse.rewrite(destination, init);
|
|
44
|
-
}
|
|
45
|
-
get headers() {
|
|
46
|
-
return this.nextRequest.headers;
|
|
47
|
-
}
|
|
48
|
-
get cookies() {
|
|
49
|
-
return this.nextRequest.cookies;
|
|
50
|
-
}
|
|
51
|
-
get geo() {
|
|
52
|
-
return this.nextRequest.geo;
|
|
53
|
-
}
|
|
54
|
-
get ip() {
|
|
55
|
-
return this.nextRequest.ip;
|
|
56
|
-
}
|
|
57
|
-
get nextUrl() {
|
|
58
|
-
return this.nextRequest.url;
|
|
59
|
-
}
|
|
60
|
-
get url() {
|
|
61
|
-
return this.nextRequest.url.toString();
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
exports.MiddlewareRequest = MiddlewareRequest;
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MiddlewareResponse = void 0;
|
|
4
|
-
const server_1 = require("next/server");
|
|
5
|
-
// A NextResponse that wraps the Netlify origin response
|
|
6
|
-
// We can't pass it through directly, because Next disallows returning a response body
|
|
7
|
-
class MiddlewareResponse extends server_1.NextResponse {
|
|
8
|
-
constructor(originResponse) {
|
|
9
|
-
super();
|
|
10
|
-
this.originResponse = originResponse;
|
|
11
|
-
// These are private in Node when compiling, but we access them in Deno at runtime
|
|
12
|
-
Object.defineProperty(this, 'dataTransforms', {
|
|
13
|
-
value: [],
|
|
14
|
-
enumerable: false,
|
|
15
|
-
writable: false,
|
|
16
|
-
});
|
|
17
|
-
Object.defineProperty(this, 'elementHandlers', {
|
|
18
|
-
value: [],
|
|
19
|
-
enumerable: false,
|
|
20
|
-
writable: false,
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Transform the page props before they are passed to the client.
|
|
25
|
-
* This works for both HTML pages and JSON data
|
|
26
|
-
*/
|
|
27
|
-
transformData(transform) {
|
|
28
|
-
// The transforms are evaluated after the middleware is returned
|
|
29
|
-
this.dataTransforms.push(transform);
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Rewrite the response HTML with the given selector and handlers
|
|
33
|
-
*/
|
|
34
|
-
rewriteHTML(selector, handlers) {
|
|
35
|
-
this.elementHandlers.push([selector, handlers]);
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Sets the value of a page prop.
|
|
39
|
-
* @see transformData if you need more control
|
|
40
|
-
*/
|
|
41
|
-
setPageProp(key, value) {
|
|
42
|
-
this.transformData((props) => {
|
|
43
|
-
props.pageProps || (props.pageProps = {});
|
|
44
|
-
props.pageProps[key] = value;
|
|
45
|
-
return props;
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Replace the text of the given element. Takes either a string or a function
|
|
50
|
-
* that is passed the original string and returns new new string.
|
|
51
|
-
* @see rewriteHTML for more control
|
|
52
|
-
*/
|
|
53
|
-
replaceText(selector, valueOrReplacer) {
|
|
54
|
-
// If it's a string then our job is simpler, because we don't need to collect the current text
|
|
55
|
-
if (typeof valueOrReplacer === 'string') {
|
|
56
|
-
this.rewriteHTML(selector, {
|
|
57
|
-
text(textChunk) {
|
|
58
|
-
if (textChunk.lastInTextNode) {
|
|
59
|
-
textChunk.replace(valueOrReplacer);
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
textChunk.remove();
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
let text = '';
|
|
69
|
-
this.rewriteHTML(selector, {
|
|
70
|
-
text(textChunk) {
|
|
71
|
-
text += textChunk.text;
|
|
72
|
-
// We're finished, so we can replace the text
|
|
73
|
-
if (textChunk.lastInTextNode) {
|
|
74
|
-
textChunk.replace(valueOrReplacer(text));
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
// Remove the chunk, because we'll be adding it back later
|
|
78
|
-
textChunk.remove();
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
get headers() {
|
|
85
|
-
var _a;
|
|
86
|
-
// If we have the origin response, we should use its headers
|
|
87
|
-
return ((_a = this.originResponse) === null || _a === void 0 ? void 0 : _a.headers) || super.headers;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
exports.MiddlewareResponse = MiddlewareResponse;
|