@netlify/plugin-nextjs 4.32.1 → 4.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/constants.js +6 -1
- package/lib/helpers/compiler.js +118 -0
- package/lib/helpers/config.js +6 -6
- package/lib/helpers/dev.js +3 -28
- package/lib/helpers/edge.js +2 -1
- package/lib/helpers/functions.js +16 -3
- package/lib/helpers/functionsMetaData.js +38 -0
- package/lib/helpers/middlewareWatcher.js +12 -0
- package/lib/helpers/utils.js +1 -1
- package/lib/templates/getHandler.js +10 -6
- package/lib/templates/getPageResolver.js +1 -1
- package/lib/templates/handlerUtils.js +49 -1
- package/lib/templates/server.js +81 -0
- package/package.json +3 -2
- package/src/templates/edge/next-dev.js +1 -2
- package/src/templates/edge-shared/utils.ts +25 -1
package/lib/constants.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DIVIDER = exports.LAMBDA_MAX_SIZE = exports.MINIMUM_REVALIDATE_SECONDS = exports.DYNAMIC_PARAMETER_REGEX = exports.OPTIONAL_CATCH_ALL_REGEX = exports.CATCH_ALL_REGEX = exports.DEFAULT_FUNCTIONS_SRC = exports.HANDLER_FUNCTION_PATH = exports.ODB_FUNCTION_PATH = exports.HIDDEN_PATHS = exports.IMAGE_FUNCTION_NAME = exports.ODB_FUNCTION_NAME = exports.HANDLER_FUNCTION_NAME = void 0;
|
|
3
|
+
exports.DIVIDER = exports.LAMBDA_MAX_SIZE = exports.MINIMUM_REVALIDATE_SECONDS = exports.DYNAMIC_PARAMETER_REGEX = exports.OPTIONAL_CATCH_ALL_REGEX = exports.CATCH_ALL_REGEX = exports.DEFAULT_FUNCTIONS_SRC = exports.HANDLER_FUNCTION_PATH = exports.ODB_FUNCTION_PATH = exports.HIDDEN_PATHS = exports.IMAGE_FUNCTION_TITLE = exports.ODB_FUNCTION_TITLE = exports.HANDLER_FUNCTION_TITLE = exports.NEXT_PLUGIN = exports.NEXT_PLUGIN_NAME = exports.IMAGE_FUNCTION_NAME = exports.ODB_FUNCTION_NAME = exports.HANDLER_FUNCTION_NAME = void 0;
|
|
4
4
|
exports.HANDLER_FUNCTION_NAME = '___netlify-handler';
|
|
5
5
|
exports.ODB_FUNCTION_NAME = '___netlify-odb-handler';
|
|
6
6
|
exports.IMAGE_FUNCTION_NAME = '_ipx';
|
|
7
|
+
exports.NEXT_PLUGIN_NAME = '@netlify/next-runtime';
|
|
8
|
+
exports.NEXT_PLUGIN = '@netlify/plugin-nextjs';
|
|
9
|
+
exports.HANDLER_FUNCTION_TITLE = 'Next.js SSR handler';
|
|
10
|
+
exports.ODB_FUNCTION_TITLE = 'Next.js ISR handler';
|
|
11
|
+
exports.IMAGE_FUNCTION_TITLE = 'next/image handler';
|
|
7
12
|
// These are paths in .next that shouldn't be publicly accessible
|
|
8
13
|
exports.HIDDEN_PATHS = [
|
|
9
14
|
'/cache/*',
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.watchForMiddlewareChanges = void 0;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const esbuild_1 = require("@netlify/esbuild");
|
|
7
|
+
const chokidar_1 = require("chokidar");
|
|
8
|
+
// For more information on Next.js middleware, see https://nextjs.org/docs/advanced-features/middleware
|
|
9
|
+
// These are the locations that a middleware file can exist in a Next.js application
|
|
10
|
+
// If other possible locations are added by Next.js, they should be added here.
|
|
11
|
+
const MIDDLEWARE_FILE_LOCATIONS = [
|
|
12
|
+
'middleware.js',
|
|
13
|
+
'middleware.ts',
|
|
14
|
+
'src/middleware.js',
|
|
15
|
+
'src/middleware.ts',
|
|
16
|
+
];
|
|
17
|
+
const toFileList = (watched) => Object.entries(watched).flatMap(([dir, files]) => files.map((file) => (0, path_1.join)(dir, file)));
|
|
18
|
+
/**
|
|
19
|
+
* Compile the middleware file using esbuild
|
|
20
|
+
*/
|
|
21
|
+
const buildMiddlewareFile = async (entryPoints, base) => {
|
|
22
|
+
try {
|
|
23
|
+
await (0, esbuild_1.build)({
|
|
24
|
+
entryPoints,
|
|
25
|
+
outfile: (0, path_1.join)(base, '.netlify', 'middleware.js'),
|
|
26
|
+
bundle: true,
|
|
27
|
+
format: 'esm',
|
|
28
|
+
target: 'esnext',
|
|
29
|
+
absWorkingDir: base,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
console.error(error.toString());
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* We only compile middleware if there's exactly one file. If there's more than one, we log a warning and don't compile.
|
|
38
|
+
*/
|
|
39
|
+
const shouldFilesBeCompiled = (watchedFiles, isFirstRun) => {
|
|
40
|
+
if (watchedFiles.length === 0) {
|
|
41
|
+
if (!isFirstRun) {
|
|
42
|
+
// Only log on subsequent builds, because having it on first build makes it seem like a warning, when it's a normal state
|
|
43
|
+
console.log('No middleware found');
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
if (watchedFiles.length > 1) {
|
|
48
|
+
console.log('Multiple middleware files found:');
|
|
49
|
+
console.log(watchedFiles.join('\n'));
|
|
50
|
+
console.log('This is not supported.');
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
return true;
|
|
54
|
+
};
|
|
55
|
+
const updateWatchedFiles = async (watcher, base, isFirstRun = false) => {
|
|
56
|
+
try {
|
|
57
|
+
// Start by deleting the old file. If we error out, we don't want to leave the old file around
|
|
58
|
+
await fs_1.promises.unlink((0, path_1.join)(base, '.netlify', 'middleware.js'));
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Ignore, because it's fine if it didn't exist
|
|
62
|
+
}
|
|
63
|
+
// The list of watched files is an object with the directory as the key and an array of files as the value.
|
|
64
|
+
// We need to flatten this into a list of files
|
|
65
|
+
const watchedFiles = toFileList(watcher.getWatched());
|
|
66
|
+
if (!shouldFilesBeCompiled(watchedFiles, isFirstRun)) {
|
|
67
|
+
watcher.emit('build');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
console.log(`${isFirstRun ? 'Building' : 'Rebuilding'} middleware ${watchedFiles[0]}...`);
|
|
71
|
+
await buildMiddlewareFile(watchedFiles, base);
|
|
72
|
+
console.log('...done');
|
|
73
|
+
watcher.emit('build');
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Watch for changes to the middleware file location. When a change is detected, recompile the middleware file.
|
|
77
|
+
*
|
|
78
|
+
* @param base The base directory to watch
|
|
79
|
+
* @returns a file watcher and a promise that resolves when the initial scan is complete.
|
|
80
|
+
*/
|
|
81
|
+
const watchForMiddlewareChanges = (base) => {
|
|
82
|
+
const watcher = (0, chokidar_1.watch)(MIDDLEWARE_FILE_LOCATIONS, {
|
|
83
|
+
// Try and ensure renames just emit one event
|
|
84
|
+
atomic: true,
|
|
85
|
+
// Don't emit for every watched file, just once after the scan is done
|
|
86
|
+
ignoreInitial: true,
|
|
87
|
+
cwd: base,
|
|
88
|
+
});
|
|
89
|
+
watcher
|
|
90
|
+
.on('change', (path) => {
|
|
91
|
+
console.log(`File ${path} has been changed`);
|
|
92
|
+
updateWatchedFiles(watcher, base);
|
|
93
|
+
})
|
|
94
|
+
.on('add', (path) => {
|
|
95
|
+
console.log(`File ${path} has been added`);
|
|
96
|
+
updateWatchedFiles(watcher, base);
|
|
97
|
+
})
|
|
98
|
+
.on('unlink', (path) => {
|
|
99
|
+
console.log(`File ${path} has been removed`);
|
|
100
|
+
updateWatchedFiles(watcher, base);
|
|
101
|
+
});
|
|
102
|
+
return {
|
|
103
|
+
watcher,
|
|
104
|
+
isReady: new Promise((resolve) => {
|
|
105
|
+
watcher.on('ready', async () => {
|
|
106
|
+
console.log('Initial scan for middleware file complete. Ready for changes.');
|
|
107
|
+
// This only happens on the first scan
|
|
108
|
+
await updateWatchedFiles(watcher, base, true);
|
|
109
|
+
console.log('Ready');
|
|
110
|
+
resolve();
|
|
111
|
+
});
|
|
112
|
+
}),
|
|
113
|
+
nextBuild: () => new Promise((resolve) => {
|
|
114
|
+
watcher.once('build', resolve);
|
|
115
|
+
}),
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
exports.watchForMiddlewareChanges = watchForMiddlewareChanges;
|
package/lib/helpers/config.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.generateCustomHeaders = exports.configureHandlerFunctions = exports.hasManuallyAddedModule = exports.updateRequiredServerFiles = exports.getRequiredServerFiles = exports.getNextConfig = void 0;
|
|
6
|
+
exports.generateCustomHeaders = exports.configureHandlerFunctions = exports.hasManuallyAddedModule = exports.resolveModuleRoot = exports.updateRequiredServerFiles = exports.getRequiredServerFiles = exports.getNextConfig = void 0;
|
|
7
7
|
const destr_1 = __importDefault(require("destr"));
|
|
8
8
|
const fs_extra_1 = require("fs-extra");
|
|
9
9
|
const pathe_1 = require("pathe");
|
|
@@ -57,6 +57,7 @@ const resolveModuleRoot = (moduleName) => {
|
|
|
57
57
|
return null;
|
|
58
58
|
}
|
|
59
59
|
};
|
|
60
|
+
exports.resolveModuleRoot = resolveModuleRoot;
|
|
60
61
|
const DEFAULT_EXCLUDED_MODULES = ['sharp', 'electron'];
|
|
61
62
|
const hasManuallyAddedModule = ({ netlifyConfig, moduleName, }) =>
|
|
62
63
|
/* eslint-disable camelcase */
|
|
@@ -68,10 +69,9 @@ const configureHandlerFunctions = async ({ netlifyConfig, publish, ignore = [],
|
|
|
68
69
|
const config = await (0, exports.getRequiredServerFiles)(publish);
|
|
69
70
|
const files = config.files || [];
|
|
70
71
|
const cssFilesToInclude = files.filter((f) => f.startsWith(`${publish}/static/css/`));
|
|
71
|
-
/* eslint-disable no-underscore-dangle */
|
|
72
72
|
if (!(0, destr_1.default)(process.env.DISABLE_IPX)) {
|
|
73
|
-
(_a = netlifyConfig.functions).
|
|
74
|
-
netlifyConfig.functions.
|
|
73
|
+
(_a = netlifyConfig.functions)[constants_1.IMAGE_FUNCTION_NAME] || (_a[constants_1.IMAGE_FUNCTION_NAME] = {});
|
|
74
|
+
netlifyConfig.functions[constants_1.IMAGE_FUNCTION_NAME].node_bundler = 'nft';
|
|
75
75
|
}
|
|
76
76
|
// If the user has manually added the module to included_files, then don't exclude it
|
|
77
77
|
const excludedModules = DEFAULT_EXCLUDED_MODULES.filter((moduleName) => !(0, exports.hasManuallyAddedModule)({ netlifyConfig, moduleName }));
|
|
@@ -81,12 +81,12 @@ const configureHandlerFunctions = async ({ netlifyConfig, publish, ignore = [],
|
|
|
81
81
|
netlifyConfig.functions[functionName].node_bundler = 'nft';
|
|
82
82
|
(_b = netlifyConfig.functions[functionName]).included_files || (_b.included_files = []);
|
|
83
83
|
netlifyConfig.functions[functionName].included_files.push('.env', '.env.local', '.env.production', '.env.production.local', './public/locales/**', './next-i18next.config.js', `${publish}/server/**`, `${publish}/serverless/**`, `${publish}/*.json`, `${publish}/BUILD_ID`, `${publish}/static/chunks/webpack-middleware*.js`, `!${publish}/server/**/*.js.nft.json`, `!${publish}/server/**/*.map`, '!**/node_modules/@next/swc*/**/*', ...cssFilesToInclude, ...ignore.map((path) => `!${(0, slash_1.default)(path)}`));
|
|
84
|
-
const nextRoot = resolveModuleRoot('next');
|
|
84
|
+
const nextRoot = (0, exports.resolveModuleRoot)('next');
|
|
85
85
|
if (nextRoot) {
|
|
86
86
|
netlifyConfig.functions[functionName].included_files.push(`!${nextRoot}/dist/server/lib/squoosh/**/*.wasm`, `!${nextRoot}/dist/next-server/server/lib/squoosh/**/*.wasm`, `!${nextRoot}/dist/compiled/webpack/bundle4.js`, `!${nextRoot}/dist/compiled/webpack/bundle5.js`);
|
|
87
87
|
}
|
|
88
88
|
excludedModules.forEach((moduleName) => {
|
|
89
|
-
const moduleRoot = resolveModuleRoot(moduleName);
|
|
89
|
+
const moduleRoot = (0, exports.resolveModuleRoot)(moduleName);
|
|
90
90
|
if (moduleRoot) {
|
|
91
91
|
netlifyConfig.functions[functionName].included_files.push(`!${moduleRoot}/**/*`);
|
|
92
92
|
}
|
package/lib/helpers/dev.js
CHANGED
|
@@ -5,10 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.onPreDev = void 0;
|
|
7
7
|
const path_1 = require("path");
|
|
8
|
-
const stream_1 = require("stream");
|
|
9
8
|
const execa_1 = __importDefault(require("execa"));
|
|
10
|
-
const fs_extra_1 = require("fs-extra");
|
|
11
|
-
const merge_stream_1 = __importDefault(require("merge-stream"));
|
|
12
9
|
const edge_1 = require("./edge");
|
|
13
10
|
const files_1 = require("./files");
|
|
14
11
|
// The types haven't been updated yet
|
|
@@ -17,32 +14,10 @@ const onPreDev = async ({ constants, netlifyConfig }) => {
|
|
|
17
14
|
const base = (_a = netlifyConfig.build.base) !== null && _a !== void 0 ? _a : process.cwd();
|
|
18
15
|
// Need to patch the files, because build might not have been run
|
|
19
16
|
await (0, files_1.patchNextFiles)(base);
|
|
20
|
-
// Clean up old functions
|
|
21
|
-
await (0, fs_extra_1.unlink)((0, path_1.resolve)('.netlify', 'middleware.js')).catch(() => {
|
|
22
|
-
// Ignore if it doesn't exist
|
|
23
|
-
});
|
|
24
17
|
await (0, edge_1.writeDevEdgeFunction)(constants);
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
all: true,
|
|
29
|
-
env: { ...process.env, FORCE_COLOR: '1' },
|
|
30
|
-
};
|
|
31
|
-
// TypeScript
|
|
32
|
-
const tsout = (0, execa_1.default)(`esbuild`, [...common, (0, path_1.resolve)(base, 'middleware.ts')], opts).all;
|
|
33
|
-
// JavaScript
|
|
34
|
-
const jsout = (0, execa_1.default)(`esbuild`, [...common, (0, path_1.resolve)(base, 'middleware.js')], opts).all;
|
|
35
|
-
const filter = new stream_1.Transform({
|
|
36
|
-
transform(chunk, encoding, callback) {
|
|
37
|
-
const str = chunk.toString(encoding);
|
|
38
|
-
// Skip if message includes this, because we run even when the files are missing
|
|
39
|
-
if (!str.includes('[ERROR] Could not resolve')) {
|
|
40
|
-
this.push(chunk);
|
|
41
|
-
}
|
|
42
|
-
callback();
|
|
43
|
-
},
|
|
18
|
+
// Don't await this or it will never finish
|
|
19
|
+
execa_1.default.node((0, path_1.resolve)(__dirname, '..', '..', 'lib', 'helpers', 'middlewareWatcher.js'), [base, process.env.NODE_ENV === 'test' ? '--once' : ''], {
|
|
20
|
+
stdio: 'inherit',
|
|
44
21
|
});
|
|
45
|
-
(0, merge_stream_1.default)(tsout, jsout).pipe(filter).pipe(process.stdout);
|
|
46
|
-
// Don't return the promise because we don't want to wait for the child process to finish
|
|
47
22
|
};
|
|
48
23
|
exports.onPreDev = onPreDev;
|
package/lib/helpers/edge.js
CHANGED
|
@@ -10,6 +10,7 @@ const chalk_1 = require("chalk");
|
|
|
10
10
|
const destr_1 = __importDefault(require("destr"));
|
|
11
11
|
const fs_extra_1 = require("fs-extra");
|
|
12
12
|
const outdent_1 = require("outdent");
|
|
13
|
+
const constants_1 = require("../constants");
|
|
13
14
|
const config_1 = require("./config");
|
|
14
15
|
const matchers_1 = require("./matchers");
|
|
15
16
|
const maybeLoadJson = (path) => {
|
|
@@ -286,7 +287,7 @@ const writeEdgeFunctions = async ({ netlifyConfig, routesManifest, }) => {
|
|
|
286
287
|
const edgeFunctionDir = (0, path_1.join)(edgeFunctionRoot, 'ipx');
|
|
287
288
|
await (0, fs_extra_1.ensureDir)(edgeFunctionDir);
|
|
288
289
|
await copyEdgeSourceFile({ edgeFunctionDir, file: 'ipx.ts', target: 'index.ts' });
|
|
289
|
-
await (0, fs_extra_1.copyFile)((0, path_1.join)('.netlify', 'functions-internal',
|
|
290
|
+
await (0, fs_extra_1.copyFile)((0, path_1.join)('.netlify', 'functions-internal', constants_1.IMAGE_FUNCTION_NAME, 'imageconfig.json'), (0, path_1.join)(edgeFunctionDir, 'imageconfig.json'));
|
|
290
291
|
manifest.functions.push({
|
|
291
292
|
function: 'ipx',
|
|
292
293
|
name: 'next/image handler',
|
package/lib/helpers/functions.js
CHANGED
|
@@ -16,6 +16,7 @@ const getHandler_1 = require("../templates/getHandler");
|
|
|
16
16
|
const getPageResolver_1 = require("../templates/getPageResolver");
|
|
17
17
|
const analysis_1 = require("./analysis");
|
|
18
18
|
const files_1 = require("./files");
|
|
19
|
+
const functionsMetaData_1 = require("./functionsMetaData");
|
|
19
20
|
const utils_1 = require("./utils");
|
|
20
21
|
const generateFunctions = async ({ FUNCTIONS_SRC = constants_1.DEFAULT_FUNCTIONS_SRC, INTERNAL_FUNCTIONS_SRC, PUBLISH_DIR }, appDir, apiRoutes) => {
|
|
21
22
|
const publish = (0, pathe_1.resolve)(PUBLISH_DIR);
|
|
@@ -35,8 +36,11 @@ const generateFunctions = async ({ FUNCTIONS_SRC = constants_1.DEFAULT_FUNCTIONS
|
|
|
35
36
|
});
|
|
36
37
|
const functionName = (0, utils_1.getFunctionNameForPage)(route, config.type === "experimental-background" /* ApiRouteType.BACKGROUND */);
|
|
37
38
|
await (0, fs_extra_1.ensureDir)((0, pathe_1.join)(functionsDir, functionName));
|
|
39
|
+
// write main API handler file
|
|
38
40
|
await (0, fs_extra_1.writeFile)((0, pathe_1.join)(functionsDir, functionName, `${functionName}.js`), apiHandlerSource);
|
|
41
|
+
// copy handler dependencies (VercelNodeBridge, NetlifyNextServer, etc.)
|
|
39
42
|
await (0, fs_extra_1.copyFile)(node_bridge_1.default, (0, pathe_1.join)(functionsDir, functionName, 'bridge.js'));
|
|
43
|
+
await (0, fs_extra_1.copyFile)((0, pathe_1.join)(__dirname, '..', '..', 'lib', 'templates', 'server.js'), (0, pathe_1.join)(functionsDir, functionName, 'server.js'));
|
|
40
44
|
await (0, fs_extra_1.copyFile)((0, pathe_1.join)(__dirname, '..', '..', 'lib', 'templates', 'handlerUtils.js'), (0, pathe_1.join)(functionsDir, functionName, 'handlerUtils.js'));
|
|
41
45
|
const resolveSourceFile = (file) => (0, pathe_1.join)(publish, 'server', file);
|
|
42
46
|
const resolverSource = await (0, getPageResolver_1.getResolverForSourceFiles)({
|
|
@@ -46,15 +50,19 @@ const generateFunctions = async ({ FUNCTIONS_SRC = constants_1.DEFAULT_FUNCTIONS
|
|
|
46
50
|
});
|
|
47
51
|
await (0, fs_extra_1.writeFile)((0, pathe_1.join)(functionsDir, functionName, 'pages.js'), resolverSource);
|
|
48
52
|
}
|
|
49
|
-
const writeHandler = async (functionName, isODB) => {
|
|
53
|
+
const writeHandler = async (functionName, functionTitle, isODB) => {
|
|
50
54
|
const handlerSource = await (0, getHandler_1.getHandler)({ isODB, publishDir, appDir: (0, pathe_1.relative)(functionDir, appDir) });
|
|
51
55
|
await (0, fs_extra_1.ensureDir)((0, pathe_1.join)(functionsDir, functionName));
|
|
56
|
+
// write main handler file (standard or ODB)
|
|
52
57
|
await (0, fs_extra_1.writeFile)((0, pathe_1.join)(functionsDir, functionName, `${functionName}.js`), handlerSource);
|
|
58
|
+
// copy handler dependencies (VercelNodeBridge, NetlifyNextServer, etc.)
|
|
53
59
|
await (0, fs_extra_1.copyFile)(node_bridge_1.default, (0, pathe_1.join)(functionsDir, functionName, 'bridge.js'));
|
|
60
|
+
await (0, fs_extra_1.copyFile)((0, pathe_1.join)(__dirname, '..', '..', 'lib', 'templates', 'server.js'), (0, pathe_1.join)(functionsDir, functionName, 'server.js'));
|
|
54
61
|
await (0, fs_extra_1.copyFile)((0, pathe_1.join)(__dirname, '..', '..', 'lib', 'templates', 'handlerUtils.js'), (0, pathe_1.join)(functionsDir, functionName, 'handlerUtils.js'));
|
|
62
|
+
(0, functionsMetaData_1.writeFunctionConfiguration)({ functionName, functionTitle, functionsDir });
|
|
55
63
|
};
|
|
56
|
-
await writeHandler(constants_1.HANDLER_FUNCTION_NAME, false);
|
|
57
|
-
await writeHandler(constants_1.ODB_FUNCTION_NAME, true);
|
|
64
|
+
await writeHandler(constants_1.HANDLER_FUNCTION_NAME, constants_1.HANDLER_FUNCTION_TITLE, false);
|
|
65
|
+
await writeHandler(constants_1.ODB_FUNCTION_NAME, constants_1.ODB_FUNCTION_TITLE, true);
|
|
58
66
|
};
|
|
59
67
|
exports.generateFunctions = generateFunctions;
|
|
60
68
|
/**
|
|
@@ -96,6 +104,11 @@ const setupImageFunction = async ({ constants: { INTERNAL_FUNCTIONS_SRC, FUNCTIO
|
|
|
96
104
|
responseHeaders,
|
|
97
105
|
});
|
|
98
106
|
await (0, fs_extra_1.copyFile)((0, pathe_1.join)(__dirname, '..', '..', 'lib', 'templates', 'ipx.js'), (0, pathe_1.join)(functionDirectory, functionName));
|
|
107
|
+
(0, functionsMetaData_1.writeFunctionConfiguration)({
|
|
108
|
+
functionName: constants_1.IMAGE_FUNCTION_NAME,
|
|
109
|
+
functionTitle: constants_1.IMAGE_FUNCTION_TITLE,
|
|
110
|
+
functionsDir: functionsPath,
|
|
111
|
+
});
|
|
99
112
|
// If we have edge functions then the request will have already been rewritten
|
|
100
113
|
// so this won't match. This is matched if edge is disabled or unavailable.
|
|
101
114
|
netlifyConfig.redirects.push({
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.writeFunctionConfiguration = void 0;
|
|
4
|
+
const fs_extra_1 = require("fs-extra");
|
|
5
|
+
const pathe_1 = require("pathe");
|
|
6
|
+
const constants_1 = require("../constants");
|
|
7
|
+
const config_1 = require("./config");
|
|
8
|
+
const getNextRuntimeVersion = async (packageJsonPath, useNodeModulesPath) => {
|
|
9
|
+
if (!(0, fs_extra_1.existsSync)(packageJsonPath)) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const packagePlugin = await (0, fs_extra_1.readJSON)(packageJsonPath);
|
|
13
|
+
return useNodeModulesPath ? packagePlugin.version : packagePlugin.dependencies[constants_1.NEXT_PLUGIN];
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Creates a function configuration file for the given function.
|
|
17
|
+
*
|
|
18
|
+
* @param functionInfo The information needed to create a function configuration file
|
|
19
|
+
*/
|
|
20
|
+
const writeFunctionConfiguration = async (functionInfo) => {
|
|
21
|
+
const { functionName, functionTitle, functionsDir } = functionInfo;
|
|
22
|
+
const pluginPackagePath = '.netlify/plugins/package.json';
|
|
23
|
+
const moduleRoot = (0, config_1.resolveModuleRoot)(constants_1.NEXT_PLUGIN);
|
|
24
|
+
const nodeModulesPath = moduleRoot ? (0, pathe_1.join)(moduleRoot, 'package.json') : null;
|
|
25
|
+
const nextPluginVersion = (await getNextRuntimeVersion(nodeModulesPath, true)) ||
|
|
26
|
+
(await getNextRuntimeVersion(pluginPackagePath, false)) ||
|
|
27
|
+
// The runtime version should always be available, but if it's not, return 'unknown'
|
|
28
|
+
'unknown';
|
|
29
|
+
const metadata = {
|
|
30
|
+
config: {
|
|
31
|
+
name: functionTitle,
|
|
32
|
+
generator: `${constants_1.NEXT_PLUGIN_NAME}@${nextPluginVersion}`,
|
|
33
|
+
},
|
|
34
|
+
version: 1,
|
|
35
|
+
};
|
|
36
|
+
await (0, fs_extra_1.writeFile)((0, pathe_1.join)(functionsDir, functionName, `${functionName}.json`), JSON.stringify(metadata));
|
|
37
|
+
};
|
|
38
|
+
exports.writeFunctionConfiguration = writeFunctionConfiguration;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const path_1 = require("path");
|
|
4
|
+
const compiler_1 = require("./compiler");
|
|
5
|
+
const run = async () => {
|
|
6
|
+
const { isReady, watcher } = (0, compiler_1.watchForMiddlewareChanges)((0, path_1.resolve)(process.argv[2]));
|
|
7
|
+
await isReady;
|
|
8
|
+
if (process.argv[3] === '--once') {
|
|
9
|
+
watcher.close();
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
run();
|
package/lib/helpers/utils.js
CHANGED
|
@@ -49,7 +49,7 @@ const toNetlifyRoute = (nextRoute) => {
|
|
|
49
49
|
exports.toNetlifyRoute = toNetlifyRoute;
|
|
50
50
|
const generateNetlifyRoutes = ({ route, dataRoute, withData = true, }) => [...(withData ? (0, exports.toNetlifyRoute)(dataRoute) : []), ...(0, exports.toNetlifyRoute)(route)];
|
|
51
51
|
exports.generateNetlifyRoutes = generateNetlifyRoutes;
|
|
52
|
-
const routeToDataRoute = (route, buildId, locale) => `/_next/data/${buildId}${locale ? `/${locale}` : ''}${route === '/' ? '/index' : route}.json`;
|
|
52
|
+
const routeToDataRoute = (route, buildId, locale) => `/_next/data/${buildId}${locale ? `/${locale}` : ''}${route === '/' ? (locale ? '' : '/index') : route}.json`;
|
|
53
53
|
exports.routeToDataRoute = routeToDataRoute;
|
|
54
54
|
// Default locale is served from root, not localized
|
|
55
55
|
const localizeRoute = (route, locale, defaultLocale) => locale === defaultLocale ? route : `/${locale}${route}`;
|
|
@@ -10,9 +10,10 @@ 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, getPrefetchResponse,
|
|
13
|
+
const { augmentFsModule, getMaxAge, getMultiValueHeaders, getPrefetchResponse, normalizePath, } = require('./handlerUtils');
|
|
14
|
+
const { NetlifyNextServer } = require('./server');
|
|
14
15
|
// We return a function and then call `toString()` on it to serialise it as the launcher function
|
|
15
|
-
// eslint-disable-next-line max-params
|
|
16
|
+
// eslint-disable-next-line max-params, max-lines-per-function
|
|
16
17
|
const makeHandler = (conf, app, pageRoot, staticManifest = [], mode = 'ssr') => {
|
|
17
18
|
var _a;
|
|
18
19
|
// Change working directory into the site root, unless using Nx, which moves the
|
|
@@ -43,20 +44,22 @@ const makeHandler = (conf, app, pageRoot, staticManifest = [], mode = 'ssr') =>
|
|
|
43
44
|
// We memoize this because it can be shared between requests, but don't instantiate it until
|
|
44
45
|
// the first request because we need the host and port.
|
|
45
46
|
let bridge;
|
|
46
|
-
const getBridge = (event) => {
|
|
47
|
+
const getBridge = (event, context) => {
|
|
48
|
+
const { clientContext: { custom: customContext }, } = context;
|
|
47
49
|
if (bridge) {
|
|
48
50
|
return bridge;
|
|
49
51
|
}
|
|
50
52
|
const url = new URL(event.rawUrl);
|
|
51
53
|
const port = Number.parseInt(url.port) || 80;
|
|
52
54
|
base = url.origin;
|
|
53
|
-
const
|
|
54
|
-
const nextServer = new NextServer({
|
|
55
|
+
const nextServer = new NetlifyNextServer({
|
|
55
56
|
conf,
|
|
56
57
|
dir,
|
|
57
58
|
customServer: false,
|
|
58
59
|
hostname: url.hostname,
|
|
59
60
|
port,
|
|
61
|
+
}, {
|
|
62
|
+
revalidateToken: customContext.odb_refresh_hooks,
|
|
60
63
|
});
|
|
61
64
|
const requestHandler = nextServer.getRequestHandler();
|
|
62
65
|
const server = new Server(async (req, res) => {
|
|
@@ -90,7 +93,7 @@ const makeHandler = (conf, app, pageRoot, staticManifest = [], mode = 'ssr') =>
|
|
|
90
93
|
// eslint-disable-next-line no-underscore-dangle
|
|
91
94
|
process.env._NETLIFY_GRAPH_TOKEN = graphToken;
|
|
92
95
|
}
|
|
93
|
-
const { headers, ...result } = await getBridge(event).launcher(event, context);
|
|
96
|
+
const { headers, ...result } = await getBridge(event, context).launcher(event, context);
|
|
94
97
|
// Convert all headers to multiValueHeaders
|
|
95
98
|
const multiValueHeaders = getMultiValueHeaders(headers);
|
|
96
99
|
if (event.headers['x-next-debug-logging']) {
|
|
@@ -142,6 +145,7 @@ const getHandler = ({ isODB = false, publishDir = '../../../.next', appDir = '..
|
|
|
142
145
|
// We copy the file here rather than requiring from the node module
|
|
143
146
|
const { Bridge } = require("./bridge");
|
|
144
147
|
const { augmentFsModule, getMaxAge, getMultiValueHeaders, getPrefetchResponse, getNextServer, normalizePath } = require('./handlerUtils')
|
|
148
|
+
const { NetlifyNextServer } = require('./server')
|
|
145
149
|
|
|
146
150
|
${isODB ? `const { builder } = require("@netlify/functions")` : ''}
|
|
147
151
|
const { config } = require("${publishDir}/required-server-files.json")
|
|
@@ -30,7 +30,7 @@ exports.getAllPageDependencies = getAllPageDependencies;
|
|
|
30
30
|
const getResolverForDependencies = ({ dependencies, functionDir, }) => {
|
|
31
31
|
const pageFiles = dependencies.map((file) => `require.resolve('${(0, pathe_1.relative)(functionDir, file)}')`);
|
|
32
32
|
return (0, outdent_1.outdent /* javascript */) `
|
|
33
|
-
// This file is purely to allow nft to know about these pages.
|
|
33
|
+
// This file is purely to allow nft to know about these pages.
|
|
34
34
|
exports.resolvePages = () => {
|
|
35
35
|
try {
|
|
36
36
|
${pageFiles.join('\n ')}
|
|
@@ -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.normalizePath = exports.getPrefetchResponse = exports.getNextServer = exports.augmentFsModule = exports.getMultiValueHeaders = exports.getMaxAge = exports.downloadFile = void 0;
|
|
6
|
+
exports.localizeDataRoute = exports.localizeRoute = exports.unlocalizeRoute = exports.normalizeRoute = exports.netlifyApiFetch = exports.normalizePath = 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"));
|
|
@@ -206,3 +206,51 @@ const normalizePath = (event) => {
|
|
|
206
206
|
return new URL(event.rawUrl).pathname;
|
|
207
207
|
};
|
|
208
208
|
exports.normalizePath = normalizePath;
|
|
209
|
+
// Simple Netlify API client
|
|
210
|
+
const netlifyApiFetch = ({ endpoint, payload, token, method = 'GET', }) => new Promise((resolve, reject) => {
|
|
211
|
+
const body = JSON.stringify(payload);
|
|
212
|
+
const req = follow_redirects_1.https.request({
|
|
213
|
+
hostname: 'api.netlify.com',
|
|
214
|
+
port: 443,
|
|
215
|
+
path: `/api/v1/${endpoint}`,
|
|
216
|
+
method,
|
|
217
|
+
headers: {
|
|
218
|
+
'Content-Type': 'application/json',
|
|
219
|
+
'Content-Length': body.length,
|
|
220
|
+
Authorization: `Bearer ${token}`,
|
|
221
|
+
},
|
|
222
|
+
}, (res) => {
|
|
223
|
+
let data = '';
|
|
224
|
+
res.on('data', (chunk) => {
|
|
225
|
+
data += chunk;
|
|
226
|
+
});
|
|
227
|
+
res.on('end', () => {
|
|
228
|
+
resolve(JSON.parse(data));
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
req.on('error', reject);
|
|
232
|
+
req.write(body);
|
|
233
|
+
req.end();
|
|
234
|
+
});
|
|
235
|
+
exports.netlifyApiFetch = netlifyApiFetch;
|
|
236
|
+
// Remove trailing slash from a route (except for the root route)
|
|
237
|
+
const normalizeRoute = (route) => (route.endsWith('/') ? route.slice(0, -1) || '/' : route);
|
|
238
|
+
exports.normalizeRoute = normalizeRoute;
|
|
239
|
+
// Check if a route has a locale prefix (including the root route)
|
|
240
|
+
const isLocalized = (route, i18n) => i18n.locales.some((locale) => route === `/${locale}` || route.startsWith(`/${locale}/`));
|
|
241
|
+
// Remove the locale prefix from a route (if any)
|
|
242
|
+
const unlocalizeRoute = (route, i18n) => isLocalized(route, i18n) ? `/${route.split('/').slice(2).join('/')}` : route;
|
|
243
|
+
exports.unlocalizeRoute = unlocalizeRoute;
|
|
244
|
+
// Add the default locale prefix to a route (if necessary)
|
|
245
|
+
const localizeRoute = (route, i18n) => isLocalized(route, i18n) ? route : (0, exports.normalizeRoute)(`/${i18n.defaultLocale}${route}`);
|
|
246
|
+
exports.localizeRoute = localizeRoute;
|
|
247
|
+
// Normalize a data route to include the locale prefix and remove the index suffix
|
|
248
|
+
const localizeDataRoute = (dataRoute, localizedRoute) => {
|
|
249
|
+
if (dataRoute.endsWith('.rsc'))
|
|
250
|
+
return dataRoute;
|
|
251
|
+
const locale = localizedRoute.split('/').find(Boolean);
|
|
252
|
+
return dataRoute
|
|
253
|
+
.replace(new RegExp(`/_next/data/(.+?)/(${locale}/)?`), `/_next/data/$1/${locale}/`)
|
|
254
|
+
.replace(/\/index\.json$/, '.json');
|
|
255
|
+
};
|
|
256
|
+
exports.localizeDataRoute = localizeDataRoute;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NetlifyNextServer = void 0;
|
|
4
|
+
const handlerUtils_1 = require("./handlerUtils");
|
|
5
|
+
const NextServer = (0, handlerUtils_1.getNextServer)();
|
|
6
|
+
class NetlifyNextServer extends NextServer {
|
|
7
|
+
constructor(options, netlifyConfig) {
|
|
8
|
+
super(options);
|
|
9
|
+
this.netlifyConfig = netlifyConfig;
|
|
10
|
+
// copy the prerender manifest so it doesn't get mutated by Next.js
|
|
11
|
+
const manifest = this.getPrerenderManifest();
|
|
12
|
+
this.netlifyPrerenderManifest = {
|
|
13
|
+
...manifest,
|
|
14
|
+
routes: { ...manifest.routes },
|
|
15
|
+
dynamicRoutes: { ...manifest.dynamicRoutes },
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
getRequestHandler() {
|
|
19
|
+
const handler = super.getRequestHandler();
|
|
20
|
+
return async (req, res, parsedUrl) => {
|
|
21
|
+
// preserve the URL before Next.js mutates it for i18n
|
|
22
|
+
const { url, headers } = req;
|
|
23
|
+
// handle the original res.revalidate() request
|
|
24
|
+
await handler(req, res, parsedUrl);
|
|
25
|
+
// handle on-demand revalidation by purging the ODB cache
|
|
26
|
+
if (res.statusCode === 200 && headers['x-prerender-revalidate'] && this.netlifyConfig.revalidateToken) {
|
|
27
|
+
await this.netlifyRevalidate(url);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
async netlifyRevalidate(route) {
|
|
32
|
+
try {
|
|
33
|
+
// call netlify API to revalidate the path
|
|
34
|
+
const result = await (0, handlerUtils_1.netlifyApiFetch)({
|
|
35
|
+
endpoint: `sites/${process.env.SITE_ID}/refresh_on_demand_builders`,
|
|
36
|
+
payload: {
|
|
37
|
+
paths: this.getNetlifyPathsForRoute(route),
|
|
38
|
+
domain: this.hostname,
|
|
39
|
+
},
|
|
40
|
+
token: this.netlifyConfig.revalidateToken,
|
|
41
|
+
method: 'POST',
|
|
42
|
+
});
|
|
43
|
+
if (!result.ok) {
|
|
44
|
+
throw new Error(result.message);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
console.log(`Error revalidating ${route}:`, error.message);
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
getNetlifyPathsForRoute(route) {
|
|
53
|
+
const { i18n } = this.nextConfig;
|
|
54
|
+
const { routes, dynamicRoutes } = this.netlifyPrerenderManifest;
|
|
55
|
+
// matches static routes
|
|
56
|
+
const normalizedRoute = (0, handlerUtils_1.normalizeRoute)(i18n ? (0, handlerUtils_1.localizeRoute)(route, i18n) : route);
|
|
57
|
+
if (normalizedRoute in routes) {
|
|
58
|
+
const { dataRoute } = routes[normalizedRoute];
|
|
59
|
+
const normalizedDataRoute = i18n ? (0, handlerUtils_1.localizeDataRoute)(dataRoute, normalizedRoute) : dataRoute;
|
|
60
|
+
return [route, normalizedDataRoute];
|
|
61
|
+
}
|
|
62
|
+
// matches dynamic routes
|
|
63
|
+
const unlocalizedRoute = i18n ? (0, handlerUtils_1.unlocalizeRoute)(normalizedRoute, i18n) : normalizedRoute;
|
|
64
|
+
for (const dynamicRoute in dynamicRoutes) {
|
|
65
|
+
const { dataRoute, routeRegex } = dynamicRoutes[dynamicRoute];
|
|
66
|
+
const matches = unlocalizedRoute.match(routeRegex);
|
|
67
|
+
if (matches && matches.length !== 0) {
|
|
68
|
+
// remove the first match, which is the full route
|
|
69
|
+
matches.shift();
|
|
70
|
+
// replace the dynamic segments with the actual values
|
|
71
|
+
const interpolatedDataRoute = dataRoute.replace(/\[(.*?)]/g, () => matches.shift());
|
|
72
|
+
const normalizedDataRoute = i18n
|
|
73
|
+
? (0, handlerUtils_1.localizeDataRoute)(interpolatedDataRoute, normalizedRoute)
|
|
74
|
+
: interpolatedDataRoute;
|
|
75
|
+
return [route, normalizedDataRoute];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
throw new Error(`not an ISR route`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
exports.NetlifyNextServer = NetlifyNextServer;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/plugin-nextjs",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.33.0",
|
|
4
4
|
"description": "Run Next.js seamlessly on Netlify",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"@netlify/ipx": "^1.3.3",
|
|
17
17
|
"@vercel/node-bridge": "^2.1.0",
|
|
18
18
|
"chalk": "^4.1.2",
|
|
19
|
+
"chokidar": "^3.5.3",
|
|
19
20
|
"destr": "^1.1.1",
|
|
20
21
|
"execa": "^5.1.1",
|
|
21
22
|
"follow-redirects": "^1.15.2",
|
|
@@ -36,7 +37,7 @@
|
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|
|
38
39
|
"@delucis/if-env": "^1.1.2",
|
|
39
|
-
"@netlify/build": "^29.
|
|
40
|
+
"@netlify/build": "^29.7.1",
|
|
40
41
|
"@types/fs-extra": "^9.0.13",
|
|
41
42
|
"@types/jest": "^27.4.1",
|
|
42
43
|
"@types/merge-stream": "^1.1.2",
|
|
@@ -38,8 +38,7 @@ const handler = async (req, context) => {
|
|
|
38
38
|
const nextMiddleware = await import(`../../middleware.js#${++idx}`)
|
|
39
39
|
middleware = nextMiddleware.middleware
|
|
40
40
|
} catch (importError) {
|
|
41
|
-
|
|
42
|
-
if (importError.code === 'ERR_MODULE_NOT_FOUND' && importError.message.includes(`middleware.js#${idx}`)) {
|
|
41
|
+
if (importError.code === 'ERR_MODULE_NOT_FOUND' && importError.message.includes(`middleware.js`)) {
|
|
43
42
|
// No middleware, so we silently return
|
|
44
43
|
return
|
|
45
44
|
}
|
|
@@ -8,6 +8,22 @@ export interface FetchEventResult {
|
|
|
8
8
|
|
|
9
9
|
type NextDataTransform = <T>(data: T) => T
|
|
10
10
|
|
|
11
|
+
function normalizeDataUrl(redirect: string) {
|
|
12
|
+
// If the redirect is a data URL, we need to normalize it.
|
|
13
|
+
// next.js code reference: https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/get-next-pathname-info.ts#L46
|
|
14
|
+
if (redirect.startsWith('/_next/data/') && redirect.includes('.json')) {
|
|
15
|
+
const paths = redirect
|
|
16
|
+
.replace(/^\/_next\/data\//, '')
|
|
17
|
+
.replace(/\.json/, '')
|
|
18
|
+
.split('/')
|
|
19
|
+
|
|
20
|
+
const buildId = paths[0]
|
|
21
|
+
redirect = paths[1] !== 'index' ? `/${paths.slice(1).join('/')}` : '/'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return redirect
|
|
25
|
+
}
|
|
26
|
+
|
|
11
27
|
/**
|
|
12
28
|
* This is how Next handles rewritten URLs.
|
|
13
29
|
*/
|
|
@@ -196,7 +212,9 @@ export const buildResponse = async ({
|
|
|
196
212
|
// Apply all of the transforms to the props
|
|
197
213
|
const props = response.dataTransforms.reduce((prev, transform) => transform(prev), data.props)
|
|
198
214
|
// Replace the data with the transformed props
|
|
199
|
-
|
|
215
|
+
// With `html: true` the input is treated as raw HTML
|
|
216
|
+
// @see https://developers.cloudflare.com/workers/runtime-apis/html-rewriter/#global-types
|
|
217
|
+
textChunk.replace(JSON.stringify({ ...data, props }), { html: true })
|
|
200
218
|
} catch (err) {
|
|
201
219
|
console.log('Could not parse', err)
|
|
202
220
|
}
|
|
@@ -249,6 +267,12 @@ export const buildResponse = async ({
|
|
|
249
267
|
res.headers.set('x-nextjs-redirect', relativizeURL(redirect, request.url))
|
|
250
268
|
}
|
|
251
269
|
|
|
270
|
+
const nextRedirect = res.headers.get('x-nextjs-redirect')
|
|
271
|
+
|
|
272
|
+
if (nextRedirect && isDataReq) {
|
|
273
|
+
res.headers.set('x-nextjs-redirect', normalizeDataUrl(nextRedirect))
|
|
274
|
+
}
|
|
275
|
+
|
|
252
276
|
if (res.headers.get('x-middleware-next') === '1') {
|
|
253
277
|
return addMiddlewareHeaders(context.next(), res)
|
|
254
278
|
}
|