@netlify/plugin-nextjs 4.1.3 → 4.2.3
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/README.md +22 -1
- package/lib/helpers/cache.js +3 -3
- package/lib/helpers/config.js +3 -3
- package/lib/helpers/files.js +63 -59
- package/lib/helpers/functions.js +18 -15
- package/lib/helpers/redirects.js +132 -39
- package/lib/helpers/utils.js +8 -18
- package/lib/helpers/verification.js +27 -21
- package/lib/index.js +27 -26
- package/lib/templates/getHandler.js +60 -46
- package/lib/templates/getPageResolver.js +4 -4
- package/lib/templates/handlerUtils.js +21 -8
- package/lib/templates/ipx.js +1 -1
- package/package.json +10 -15
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getHandler = void 0;
|
|
4
|
+
const outdent_1 = require("outdent");
|
|
5
|
+
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
4
6
|
const { promises } = require('fs');
|
|
5
7
|
const { Server } = require('http');
|
|
6
8
|
const path = require('path');
|
|
7
9
|
// eslint-disable-next-line node/prefer-global/url, node/prefer-global/url-search-params
|
|
8
10
|
const { URLSearchParams, URL } = require('url');
|
|
9
|
-
const { Bridge } = require('@vercel/node/
|
|
11
|
+
const { Bridge } = require('@vercel/node-bridge/bridge');
|
|
10
12
|
const { augmentFsModule, getMaxAge, getMultiValueHeaders, getNextServer } = require('./handlerUtils');
|
|
11
|
-
const makeHandler = () =>
|
|
12
13
|
// We return a function and then call `toString()` on it to serialise it as the launcher function
|
|
13
14
|
// eslint-disable-next-line max-params
|
|
14
|
-
(conf, app, pageRoot, staticManifest = [], mode = 'ssr') => {
|
|
15
|
+
const makeHandler = (conf, app, pageRoot, staticManifest = [], mode = 'ssr') => {
|
|
15
16
|
var _a;
|
|
16
17
|
// This is just so nft knows about the page entrypoints. It's not actually used
|
|
17
18
|
try {
|
|
@@ -23,30 +24,47 @@ const makeHandler = () =>
|
|
|
23
24
|
(_a = process.env).NODE_ENV || (_a.NODE_ENV = 'production');
|
|
24
25
|
// We don't want to write ISR files to disk in the lambda environment
|
|
25
26
|
conf.experimental.isrFlushToDisk = false;
|
|
27
|
+
// This is our flag that we use when patching the source
|
|
26
28
|
// eslint-disable-next-line no-underscore-dangle
|
|
27
29
|
process.env._BYPASS_SSG = 'true';
|
|
28
30
|
// Set during the request as it needs the host header. Hoisted so we can define the function once
|
|
29
31
|
let base;
|
|
30
32
|
augmentFsModule({ promises, staticManifest, pageRoot, getBase: () => base });
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const requestHandler = nextServer.getRequestHandler();
|
|
38
|
-
const server = new Server(async (req, res) => {
|
|
39
|
-
try {
|
|
40
|
-
await requestHandler(req, res);
|
|
33
|
+
// We memoize this because it can be shared between requests, but don't instantiate it until
|
|
34
|
+
// the first request because we need the host and port.
|
|
35
|
+
let bridge;
|
|
36
|
+
const getBridge = (event) => {
|
|
37
|
+
if (bridge) {
|
|
38
|
+
return bridge;
|
|
41
39
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
40
|
+
const url = new URL(event.rawUrl);
|
|
41
|
+
const port = Number.parseInt(url.port) || 80;
|
|
42
|
+
const { host } = event.headers;
|
|
43
|
+
const protocol = event.headers['x-forwarded-proto'] || 'http';
|
|
44
|
+
base = `${protocol}://${host}`;
|
|
45
|
+
const NextServer = getNextServer();
|
|
46
|
+
const nextServer = new NextServer({
|
|
47
|
+
conf,
|
|
48
|
+
dir: path.resolve(__dirname, app),
|
|
49
|
+
customServer: false,
|
|
50
|
+
hostname: url.hostname,
|
|
51
|
+
port,
|
|
52
|
+
});
|
|
53
|
+
const requestHandler = nextServer.getRequestHandler();
|
|
54
|
+
const server = new Server(async (req, res) => {
|
|
55
|
+
try {
|
|
56
|
+
await requestHandler(req, res);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
console.error(error);
|
|
60
|
+
throw new Error('Error handling request. See function logs for details.');
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
bridge = new Bridge(server);
|
|
64
|
+
bridge.listen();
|
|
65
|
+
return bridge;
|
|
66
|
+
};
|
|
67
|
+
return async function handler(event, context) {
|
|
50
68
|
var _a, _b, _c;
|
|
51
69
|
let requestMode = mode;
|
|
52
70
|
// Ensure that paths are encoded - but don't double-encode them
|
|
@@ -54,13 +72,7 @@ const makeHandler = () =>
|
|
|
54
72
|
// Next expects to be able to parse the query from the URL
|
|
55
73
|
const query = new URLSearchParams(event.queryStringParameters).toString();
|
|
56
74
|
event.path = query ? `${event.path}?${query}` : event.path;
|
|
57
|
-
|
|
58
|
-
if (staticManifest.length !== 0) {
|
|
59
|
-
const { host } = event.headers;
|
|
60
|
-
const protocol = event.headers['x-forwarded-proto'] || 'http';
|
|
61
|
-
base = `${protocol}://${host}`;
|
|
62
|
-
}
|
|
63
|
-
const { headers, ...result } = await bridge.launcher(event, context);
|
|
75
|
+
const { headers, ...result } = await getBridge(event).launcher(event, context);
|
|
64
76
|
// Convert all headers to multiValueHeaders
|
|
65
77
|
const multiValueHeaders = getMultiValueHeaders(headers);
|
|
66
78
|
if ((_b = (_a = multiValueHeaders['set-cookie']) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.includes('__prerender_bypass')) {
|
|
@@ -81,6 +93,7 @@ const makeHandler = () =>
|
|
|
81
93
|
multiValueHeaders['cache-control'] = ['public, max-age=0, must-revalidate'];
|
|
82
94
|
}
|
|
83
95
|
multiValueHeaders['x-render-mode'] = [requestMode];
|
|
96
|
+
console.log(`[${event.httpMethod}] ${event.path} (${requestMode === null || requestMode === void 0 ? void 0 : requestMode.toUpperCase()})`);
|
|
84
97
|
return {
|
|
85
98
|
...result,
|
|
86
99
|
multiValueHeaders,
|
|
@@ -88,24 +101,25 @@ const makeHandler = () =>
|
|
|
88
101
|
};
|
|
89
102
|
};
|
|
90
103
|
};
|
|
91
|
-
const getHandler = ({ isODB = false, publishDir = '../../../.next', appDir = '../../..' }) =>
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const {
|
|
96
|
-
|
|
104
|
+
const getHandler = ({ isODB = false, publishDir = '../../../.next', appDir = '../../..' }) =>
|
|
105
|
+
// This is a string, but if you have the right editor plugin it should format as js
|
|
106
|
+
(0, outdent_1.outdent) `
|
|
107
|
+
const { Server } = require("http");
|
|
108
|
+
const { promises } = require("fs");
|
|
109
|
+
// We copy the file here rather than requiring from the node module
|
|
110
|
+
const { Bridge } = require("./bridge");
|
|
111
|
+
const { augmentFsModule, getMaxAge, getMultiValueHeaders, getNextServer } = require('./handlerUtils')
|
|
97
112
|
|
|
98
|
-
const { builder } = require("@netlify/functions");
|
|
99
|
-
const { config } = require("${publishDir}/required-server-files.json")
|
|
100
|
-
let staticManifest
|
|
101
|
-
try {
|
|
102
|
-
|
|
103
|
-
} catch {}
|
|
104
|
-
const path = require("path");
|
|
105
|
-
const pageRoot = path.resolve(path.join(__dirname, "${publishDir}", config.target === "server" ? "server" : "serverless", "pages"));
|
|
106
|
-
exports.handler = ${isODB
|
|
107
|
-
? `builder((${makeHandler
|
|
108
|
-
: `(${makeHandler
|
|
113
|
+
const { builder } = require("@netlify/functions");
|
|
114
|
+
const { config } = require("${publishDir}/required-server-files.json")
|
|
115
|
+
let staticManifest
|
|
116
|
+
try {
|
|
117
|
+
staticManifest = require("${publishDir}/static-manifest.json")
|
|
118
|
+
} catch {}
|
|
119
|
+
const path = require("path");
|
|
120
|
+
const pageRoot = path.resolve(path.join(__dirname, "${publishDir}", config.target === "server" ? "server" : "serverless", "pages"));
|
|
121
|
+
exports.handler = ${isODB
|
|
122
|
+
? `builder((${makeHandler.toString()})(config, "${appDir}", pageRoot, staticManifest, 'odb'));`
|
|
123
|
+
: `(${makeHandler.toString()})(config, "${appDir}", pageRoot, staticManifest, 'ssr');`}
|
|
109
124
|
`;
|
|
110
125
|
exports.getHandler = getHandler;
|
|
111
|
-
/* eslint-enable @typescript-eslint/no-var-requires */
|
|
@@ -13,15 +13,15 @@ const constants_1 = require("../constants");
|
|
|
13
13
|
// build. This is used by the nft bundler to find all the pages.
|
|
14
14
|
const getPageResolver = async ({ publish, target }) => {
|
|
15
15
|
const functionDir = path_1.posix.resolve(path_1.posix.join('.netlify', 'functions', constants_1.HANDLER_FUNCTION_NAME));
|
|
16
|
-
const root = path_1.posix.resolve(slash_1.default(publish), target === 'server' ? 'server' : 'serverless', 'pages');
|
|
17
|
-
const pages = await tiny_glob_1.default('**/*.js', {
|
|
16
|
+
const root = path_1.posix.resolve((0, slash_1.default)(publish), target === 'server' ? 'server' : 'serverless', 'pages');
|
|
17
|
+
const pages = await (0, tiny_glob_1.default)('**/*.js', {
|
|
18
18
|
cwd: root,
|
|
19
19
|
dot: true,
|
|
20
20
|
});
|
|
21
21
|
const pageFiles = pages
|
|
22
|
-
.map((page) => `require.resolve('${path_1.posix.relative(functionDir, path_1.posix.join(root, slash_1.default(page)))}')`)
|
|
22
|
+
.map((page) => `require.resolve('${path_1.posix.relative(functionDir, path_1.posix.join(root, (0, slash_1.default)(page)))}')`)
|
|
23
23
|
.sort();
|
|
24
|
-
return outdent_1.outdent `
|
|
24
|
+
return (0, outdent_1.outdent) `
|
|
25
25
|
// This file is purely to allow nft to know about these pages. It should be temporary.
|
|
26
26
|
exports.resolvePages = () => {
|
|
27
27
|
try {
|
|
@@ -11,7 +11,11 @@ const os_1 = require("os");
|
|
|
11
11
|
const path_1 = __importDefault(require("path"));
|
|
12
12
|
const stream_1 = require("stream");
|
|
13
13
|
const util_1 = require("util");
|
|
14
|
-
const streamPipeline = util_1.promisify(stream_1.pipeline);
|
|
14
|
+
const streamPipeline = (0, util_1.promisify)(stream_1.pipeline);
|
|
15
|
+
/**
|
|
16
|
+
* Downloads a file from the CDN to the local aliased filesystem. This is a fallback, because in most cases we'd expect
|
|
17
|
+
* files required at runtime to not be sent to the CDN.
|
|
18
|
+
*/
|
|
15
19
|
const downloadFile = async (url, destination) => {
|
|
16
20
|
console.log(`Downloading ${url} to ${destination}`);
|
|
17
21
|
const httpx = url.startsWith('https') ? https_1.default : http_1.default;
|
|
@@ -21,7 +25,7 @@ const downloadFile = async (url, destination) => {
|
|
|
21
25
|
reject(new Error(`Failed to download ${url}: ${response.statusCode} ${response.statusMessage || ''}`));
|
|
22
26
|
return;
|
|
23
27
|
}
|
|
24
|
-
const fileStream = fs_1.createWriteStream(destination);
|
|
28
|
+
const fileStream = (0, fs_1.createWriteStream)(destination);
|
|
25
29
|
streamPipeline(response, fileStream)
|
|
26
30
|
.then(resolve)
|
|
27
31
|
.catch((error) => {
|
|
@@ -36,6 +40,9 @@ const downloadFile = async (url, destination) => {
|
|
|
36
40
|
});
|
|
37
41
|
};
|
|
38
42
|
exports.downloadFile = downloadFile;
|
|
43
|
+
/**
|
|
44
|
+
* Parse maxage from a cache-control header
|
|
45
|
+
*/
|
|
39
46
|
const getMaxAge = (header) => {
|
|
40
47
|
const parts = header.split(',');
|
|
41
48
|
let maxAge;
|
|
@@ -66,6 +73,9 @@ const getMultiValueHeaders = (headers) => {
|
|
|
66
73
|
return multiValueHeaders;
|
|
67
74
|
};
|
|
68
75
|
exports.getMultiValueHeaders = getMultiValueHeaders;
|
|
76
|
+
/**
|
|
77
|
+
* Monkey-patch the fs module to download missing files from the CDN
|
|
78
|
+
*/
|
|
69
79
|
const augmentFsModule = ({ promises, staticManifest, pageRoot, getBase, }) => {
|
|
70
80
|
// Only do this if we have some static files moved to the CDN
|
|
71
81
|
if (staticManifest.length === 0) {
|
|
@@ -78,7 +88,7 @@ const augmentFsModule = ({ promises, staticManifest, pageRoot, getBase, }) => {
|
|
|
78
88
|
const staticFiles = new Map(staticManifest);
|
|
79
89
|
const downloadPromises = new Map();
|
|
80
90
|
// Yes, you can cache stuff locally in a Lambda
|
|
81
|
-
const cacheDir = path_1.default.join(os_1.tmpdir(), 'next-static-cache');
|
|
91
|
+
const cacheDir = path_1.default.join((0, os_1.tmpdir)(), 'next-static-cache');
|
|
82
92
|
// Grab the real fs.promises.readFile...
|
|
83
93
|
const readfileOrig = promises.readFile;
|
|
84
94
|
const statsOrig = promises.stat;
|
|
@@ -90,7 +100,7 @@ const augmentFsModule = ({ promises, staticManifest, pageRoot, getBase, }) => {
|
|
|
90
100
|
// We only want the part after `pages/`
|
|
91
101
|
const filePath = file.slice(pageRoot.length + 1);
|
|
92
102
|
// Is it in the CDN and not local?
|
|
93
|
-
if (staticFiles.has(filePath) && !fs_1.existsSync(file)) {
|
|
103
|
+
if (staticFiles.has(filePath) && !(0, fs_1.existsSync)(file)) {
|
|
94
104
|
// This name is safe to use, because it's one that was already created by Next
|
|
95
105
|
const cacheFile = path_1.default.join(cacheDir, filePath);
|
|
96
106
|
const url = `${base}/${staticFiles.get(filePath)}`;
|
|
@@ -99,11 +109,11 @@ const augmentFsModule = ({ promises, staticManifest, pageRoot, getBase, }) => {
|
|
|
99
109
|
await downloadPromises.get(url);
|
|
100
110
|
}
|
|
101
111
|
// Have we already cached it? We download every time if running locally to avoid staleness
|
|
102
|
-
if ((!fs_1.existsSync(cacheFile) || process.env.NETLIFY_DEV) && base) {
|
|
112
|
+
if ((!(0, fs_1.existsSync)(cacheFile) || process.env.NETLIFY_DEV) && base) {
|
|
103
113
|
await promises.mkdir(path_1.default.dirname(cacheFile), { recursive: true });
|
|
104
114
|
try {
|
|
105
115
|
// Append the path to our host and we can load it like a regular page
|
|
106
|
-
const downloadPromise = exports.downloadFile(url, cacheFile);
|
|
116
|
+
const downloadPromise = (0, exports.downloadFile)(url, cacheFile);
|
|
107
117
|
downloadPromises.set(url, downloadPromise);
|
|
108
118
|
await downloadPromise;
|
|
109
119
|
}
|
|
@@ -122,7 +132,7 @@ const augmentFsModule = ({ promises, staticManifest, pageRoot, getBase, }) => {
|
|
|
122
132
|
if (file.startsWith(pageRoot)) {
|
|
123
133
|
// We only want the part after `pages/`
|
|
124
134
|
const cacheFile = path_1.default.join(cacheDir, file.slice(pageRoot.length + 1));
|
|
125
|
-
if (fs_1.existsSync(cacheFile)) {
|
|
135
|
+
if ((0, fs_1.existsSync)(cacheFile)) {
|
|
126
136
|
return statsOrig(cacheFile, options);
|
|
127
137
|
}
|
|
128
138
|
}
|
|
@@ -130,6 +140,9 @@ const augmentFsModule = ({ promises, staticManifest, pageRoot, getBase, }) => {
|
|
|
130
140
|
});
|
|
131
141
|
};
|
|
132
142
|
exports.augmentFsModule = augmentFsModule;
|
|
143
|
+
/**
|
|
144
|
+
* Next.js has an annoying habit of needing deep imports, but then moving those in patch releases. This is our abstraction.
|
|
145
|
+
*/
|
|
133
146
|
const getNextServer = () => {
|
|
134
147
|
let NextServer;
|
|
135
148
|
try {
|
|
@@ -142,7 +155,7 @@ const getNextServer = () => {
|
|
|
142
155
|
// A different error, so rethrow it
|
|
143
156
|
throw error;
|
|
144
157
|
}
|
|
145
|
-
// Probably an old version of next
|
|
158
|
+
// Probably an old version of next, so fall through and find it elsewhere.
|
|
146
159
|
}
|
|
147
160
|
if (!NextServer) {
|
|
148
161
|
try {
|
package/lib/templates/ipx.js
CHANGED
|
@@ -5,7 +5,7 @@ exports.handler = void 0;
|
|
|
5
5
|
const ipx_1 = require("@netlify/ipx");
|
|
6
6
|
// @ts-ignore Injected at build time
|
|
7
7
|
const imageconfig_json_1 = require("./imageconfig.json");
|
|
8
|
-
exports.handler = ipx_1.createIPXHandler({
|
|
8
|
+
exports.handler = (0, ipx_1.createIPXHandler)({
|
|
9
9
|
basePath: imageconfig_json_1.basePath,
|
|
10
10
|
domains: imageconfig_json_1.domains,
|
|
11
11
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/plugin-nextjs",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.3",
|
|
4
4
|
"description": "Run Next.js seamlessly on Netlify",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"files": [
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"test:jest": "jest",
|
|
28
28
|
"test:jest:update": "jest --updateSnapshot",
|
|
29
29
|
"test:update": "run-s build build:demo test:jest:update",
|
|
30
|
-
"prepare": "npm run build",
|
|
30
|
+
"prepare": "husky install node_modules/@netlify/eslint-config-node/.husky/ && npm run build",
|
|
31
31
|
"clean": "rimraf lib",
|
|
32
32
|
"build": "tsc",
|
|
33
33
|
"watch": "tsc --watch"
|
|
@@ -53,9 +53,9 @@
|
|
|
53
53
|
},
|
|
54
54
|
"homepage": "https://github.com/netlify/netlify-plugin-nextjs#readme",
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@netlify/functions": "^0.
|
|
57
|
-
"@netlify/ipx": "^0.0.
|
|
58
|
-
"@vercel/node": "^1.
|
|
56
|
+
"@netlify/functions": "^0.11.0",
|
|
57
|
+
"@netlify/ipx": "^0.0.9",
|
|
58
|
+
"@vercel/node-bridge": "^2.1.0",
|
|
59
59
|
"chalk": "^4.1.2",
|
|
60
60
|
"fs-extra": "^10.0.0",
|
|
61
61
|
"globby": "^11.0.4",
|
|
@@ -74,20 +74,21 @@
|
|
|
74
74
|
"@babel/core": "^7.15.8",
|
|
75
75
|
"@babel/preset-env": "^7.15.8",
|
|
76
76
|
"@babel/preset-typescript": "^7.16.0",
|
|
77
|
-
"@netlify/build": "^26.
|
|
78
|
-
"@netlify/eslint-config-node": "^4.1.
|
|
77
|
+
"@netlify/build": "^26.2.2",
|
|
78
|
+
"@netlify/eslint-config-node": "^4.1.7",
|
|
79
79
|
"@testing-library/cypress": "^8.0.1",
|
|
80
80
|
"@types/fs-extra": "^9.0.13",
|
|
81
81
|
"@types/jest": "^27.0.2",
|
|
82
82
|
"@types/mocha": "^9.0.0",
|
|
83
|
+
"@types/node": "^17.0.10",
|
|
83
84
|
"babel-jest": "^27.2.5",
|
|
84
85
|
"cpy": "^8.1.2",
|
|
85
86
|
"cypress": "^9.0.0",
|
|
86
87
|
"eslint-config-next": "^12.0.0",
|
|
87
|
-
"husky": "^
|
|
88
|
+
"husky": "^7.0.4",
|
|
88
89
|
"jest": "^27.0.0",
|
|
89
90
|
"netlify-plugin-cypress": "^2.2.0",
|
|
90
|
-
"next": "^12.0.
|
|
91
|
+
"next": "^12.0.8",
|
|
91
92
|
"npm-run-all": "^4.1.5",
|
|
92
93
|
"prettier": "^2.1.2",
|
|
93
94
|
"react": "^17.0.1",
|
|
@@ -96,12 +97,6 @@
|
|
|
96
97
|
"tmp-promise": "^3.0.2",
|
|
97
98
|
"typescript": "^4.3.4"
|
|
98
99
|
},
|
|
99
|
-
"husky": {
|
|
100
|
-
"hooks": {
|
|
101
|
-
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
|
|
102
|
-
"pre-push": "npm run format"
|
|
103
|
-
}
|
|
104
|
-
},
|
|
105
100
|
"engines": {
|
|
106
101
|
"node": ">=12.0.0"
|
|
107
102
|
},
|