@netlify/plugin-nextjs 4.0.0-beta.11 → 4.0.0-beta.12

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.
@@ -102,7 +102,7 @@ exports.generateRedirects = async ({ netlifyConfig, basePath, i18n }) => {
102
102
  // ISR redirects are handled by the regular function. Forced to avoid pre-rendered pages
103
103
  ...isrRedirects.map((redirect) => ({
104
104
  from: `${basePath}${redirect}`,
105
- to: HANDLER_FUNCTION_PATH,
105
+ to: process.env.EXPERIMENTAL_ODB_TTL ? ODB_FUNCTION_PATH : HANDLER_FUNCTION_PATH,
106
106
  status: 200,
107
107
  force: true,
108
108
  })),
@@ -158,7 +158,7 @@ exports.configureHandlerFunctions = ({ netlifyConfig, publish, ignore = [] }) =>
158
158
  (_a = netlifyConfig.functions)[functionName] || (_a[functionName] = { included_files: [], external_node_modules: [] });
159
159
  netlifyConfig.functions[functionName].node_bundler = 'nft';
160
160
  (_b = netlifyConfig.functions[functionName]).included_files || (_b.included_files = []);
161
- netlifyConfig.functions[functionName].included_files.push(`${publish}/server/**`, `${publish}/serverless/**`, `${publish}/*.json`, `${publish}/BUILD_ID`, `${publish}/static/chunks/webpack-middleware*.js`, `!${publish}/server/**/*.js.nft.json`, ...ignore.map((path) => `!${slash(path)}`));
161
+ netlifyConfig.functions[functionName].included_files.push('.env', '.env.local', '.env.production', '.env.production.local', `${publish}/server/**`, `${publish}/serverless/**`, `${publish}/*.json`, `${publish}/BUILD_ID`, `${publish}/static/chunks/webpack-middleware*.js`, `!${publish}/server/**/*.js.nft.json`, ...ignore.map((path) => `!${slash(path)}`));
162
162
  const nextRoot = resolveModuleRoot('next');
163
163
  if (nextRoot) {
164
164
  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`, `!${nextRoot}/dist/compiled/terser/bundle.min.js`);
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable max-lines */
2
2
  const { cpus } = require('os');
3
3
  const { yellowBright } = require('chalk');
4
- const { existsSync, readJson, move, cpSync, copy, writeJson } = require('fs-extra');
4
+ const { existsSync, readJson, move, cpSync, copy, writeJson, readFile, writeFile } = require('fs-extra');
5
5
  const globby = require('globby');
6
6
  const { outdent } = require('outdent');
7
7
  const pLimit = require('p-limit');
@@ -178,6 +178,55 @@ exports.moveStaticPages = async ({ netlifyConfig, target, i18n }) => {
178
178
  }
179
179
  }
180
180
  };
181
+ const patchFile = async ({ file, from, to }) => {
182
+ if (!existsSync(file)) {
183
+ return;
184
+ }
185
+ const content = await readFile(file, 'utf8');
186
+ if (content.includes(to)) {
187
+ return;
188
+ }
189
+ const newContent = content.replace(from, to);
190
+ await writeFile(`${file}.orig`, content);
191
+ await writeFile(file, newContent);
192
+ };
193
+ const getServerFile = (root) => {
194
+ let serverFile;
195
+ try {
196
+ serverFile = require.resolve('next/dist/server/next-server', { paths: [root] });
197
+ }
198
+ catch {
199
+ // Ignore
200
+ }
201
+ if (!serverFile) {
202
+ try {
203
+ // eslint-disable-next-line node/no-missing-require
204
+ serverFile = require.resolve('next/dist/next-server/server/next-server', { paths: [root] });
205
+ }
206
+ catch {
207
+ // Ignore
208
+ }
209
+ }
210
+ return serverFile;
211
+ };
212
+ exports.patchNextFiles = async (root) => {
213
+ const serverFile = getServerFile(root);
214
+ console.log(`Patching ${serverFile}`);
215
+ if (serverFile) {
216
+ await patchFile({
217
+ file: serverFile,
218
+ from: `let ssgCacheKey = `,
219
+ to: `let ssgCacheKey = process.env._BYPASS_SSG || `,
220
+ });
221
+ }
222
+ };
223
+ exports.unpatchNextFiles = async (root) => {
224
+ const serverFile = getServerFile(root);
225
+ const origFile = `${serverFile}.orig`;
226
+ if (existsSync(origFile)) {
227
+ await move(origFile, serverFile, { overwrite: true });
228
+ }
229
+ };
181
230
  exports.movePublicFiles = async ({ appDir, publish }) => {
182
231
  const publicDir = join(appDir, 'public');
183
232
  if (existsSync(publicDir)) {
@@ -18,7 +18,8 @@ exports.verifyNetlifyBuildVersion = ({ IS_LOCAL, NETLIFY_BUILD_VERSION, failBuil
18
18
  }
19
19
  };
20
20
  exports.checkForOldFunctions = async ({ functions }) => {
21
- const oldFunctions = (await functions.list()).filter(({ name }) => name.startsWith('next_'));
21
+ const allOldFunctions = await functions.list();
22
+ const oldFunctions = allOldFunctions.filter(({ name }) => name.startsWith('next_'));
22
23
  if (oldFunctions.length !== 0) {
23
24
  console.log(yellowBright(outdent `
24
25
  We have found the following functions in your site that seem to be left over from the old Next.js plugin (v3). We have guessed this because the name starts with "next_".
package/lib/index.js CHANGED
@@ -2,7 +2,7 @@ const { join, relative } = require('path');
2
2
  const { ODB_FUNCTION_NAME } = require('./constants');
3
3
  const { restoreCache, saveCache } = require('./helpers/cache');
4
4
  const { getNextConfig, configureHandlerFunctions, generateRedirects } = require('./helpers/config');
5
- const { moveStaticPages, movePublicFiles } = require('./helpers/files');
5
+ const { moveStaticPages, movePublicFiles, patchNextFiles, unpatchNextFiles } = require('./helpers/files');
6
6
  const { generateFunctions, setupImageFunction, generatePagesResolver } = require('./helpers/functions');
7
7
  const { verifyNetlifyBuildVersion, checkNextSiteHasBuilt, checkForRootPublish, logBetaMessage, checkZipSize, checkForOldFunctions, } = require('./helpers/verification');
8
8
  /** @type import("@netlify/build").NetlifyPlugin */
@@ -26,6 +26,9 @@ module.exports = {
26
26
  await generateFunctions(constants, appDir);
27
27
  await generatePagesResolver({ netlifyConfig, target, constants });
28
28
  await movePublicFiles({ appDir, publish });
29
+ if (process.env.EXPERIMENTAL_ODB_TTL) {
30
+ await patchNextFiles(basePath);
31
+ }
29
32
  if (process.env.EXPERIMENTAL_MOVE_STATIC_PAGES) {
30
33
  console.log("The flag 'EXPERIMENTAL_MOVE_STATIC_PAGES' is no longer required, as it is now the default. To disable this behavior, set the env var 'SERVE_STATIC_FILES_FROM_ORIGIN' to 'true'");
31
34
  }
@@ -39,10 +42,12 @@ module.exports = {
39
42
  i18n,
40
43
  });
41
44
  },
42
- async onPostBuild({ netlifyConfig, utils: { cache, functions }, constants: { FUNCTIONS_DIST } }) {
45
+ async onPostBuild({ netlifyConfig, utils: { cache, functions, failBuild }, constants: { FUNCTIONS_DIST } }) {
43
46
  await saveCache({ cache, publish: netlifyConfig.build.publish });
44
47
  await checkForOldFunctions({ functions });
45
48
  await checkZipSize(join(FUNCTIONS_DIST, `${ODB_FUNCTION_NAME}.zip`));
49
+ const { basePath } = await getNextConfig({ publish: netlifyConfig.build.publish, failBuild });
50
+ await unpatchNextFiles(basePath);
46
51
  },
47
52
  onEnd() {
48
53
  logBetaMessage();
@@ -3,7 +3,7 @@ const { Server } = require('http');
3
3
  const { tmpdir } = require('os');
4
4
  const path = require('path');
5
5
  const { Bridge } = require('@vercel/node/dist/bridge');
6
- const { downloadFile } = require('./handlerUtils');
6
+ const { downloadFile, getMaxAge, getMultiValueHeaders } = require('./handlerUtils');
7
7
  const makeHandler = () =>
8
8
  // We return a function and then call `toString()` on it to serialise it as the launcher function
9
9
  // eslint-disable-next-line max-params
@@ -14,6 +14,9 @@ const makeHandler = () =>
14
14
  require.resolve('./pages.js');
15
15
  }
16
16
  catch { }
17
+ // eslint-disable-next-line no-underscore-dangle
18
+ process.env._BYPASS_SSG = 'true';
19
+ const ONE_YEAR_IN_SECONDS = 31536000;
17
20
  // We don't want to write ISR files to disk in the lambda environment
18
21
  conf.experimental.isrFlushToDisk = false;
19
22
  // Set during the request as it needs the host header. Hoisted so we can define the function once
@@ -97,6 +100,7 @@ const makeHandler = () =>
97
100
  bridge.listen();
98
101
  return async (event, context) => {
99
102
  var _a, _b, _c;
103
+ let requestMode = mode;
100
104
  // Ensure that paths are encoded - but don't double-encode them
101
105
  event.path = new URL(event.path, event.rawUrl).pathname;
102
106
  // Next expects to be able to parse the query from the URL
@@ -111,15 +115,7 @@ const makeHandler = () =>
111
115
  const { headers, ...result } = await bridge.launcher(event, context);
112
116
  /** @type import("@netlify/functions").HandlerResponse */
113
117
  // Convert all headers to multiValueHeaders
114
- const multiValueHeaders = {};
115
- for (const key of Object.keys(headers)) {
116
- if (Array.isArray(headers[key])) {
117
- multiValueHeaders[key] = headers[key];
118
- }
119
- else {
120
- multiValueHeaders[key] = [headers[key]];
121
- }
122
- }
118
+ const multiValueHeaders = getMultiValueHeaders(headers);
123
119
  if ((_b = (_a = multiValueHeaders['set-cookie']) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.includes('__prerender_bypass')) {
124
120
  delete multiValueHeaders.etag;
125
121
  multiValueHeaders['cache-control'] = ['no-cache'];
@@ -127,10 +123,18 @@ const makeHandler = () =>
127
123
  // Sending SWR headers causes undefined behaviour with the Netlify CDN
128
124
  const cacheHeader = (_c = multiValueHeaders['cache-control']) === null || _c === void 0 ? void 0 : _c[0];
129
125
  if (cacheHeader === null || cacheHeader === void 0 ? void 0 : cacheHeader.includes('stale-while-revalidate')) {
130
- console.log({ cacheHeader });
126
+ if (requestMode === 'odb' && process.env.EXPERIMENTAL_ODB_TTL) {
127
+ requestMode = 'isr';
128
+ const ttl = getMaxAge(cacheHeader);
129
+ // Long-expiry TTL is basically no TTL
130
+ if (ttl > 0 && ttl < ONE_YEAR_IN_SECONDS) {
131
+ result.ttl = ttl;
132
+ }
133
+ multiValueHeaders['x-rendered-at'] = [new Date().toISOString()];
134
+ }
131
135
  multiValueHeaders['cache-control'] = ['public, max-age=0, must-revalidate'];
132
136
  }
133
- multiValueHeaders['x-render-mode'] = [mode];
137
+ multiValueHeaders['x-render-mode'] = [requestMode];
134
138
  return {
135
139
  ...result,
136
140
  multiValueHeaders,
@@ -144,7 +148,7 @@ const { tmpdir } = require('os')
144
148
  const { promises, existsSync } = require("fs");
145
149
  // We copy the file here rather than requiring from the node module
146
150
  const { Bridge } = require("./bridge");
147
- const { downloadFile } = require('./handlerUtils')
151
+ const { downloadFile, getMaxAge, getMultiValueHeaders } = require('./handlerUtils')
148
152
 
149
153
  const { builder } = require("@netlify/functions");
150
154
  const { config } = require("${publishDir}/required-server-files.json")
@@ -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.downloadFile = void 0;
6
+ exports.getMultiValueHeaders = exports.getMaxAge = exports.downloadFile = void 0;
7
7
  const fs_1 = require("fs");
8
8
  const http_1 = __importDefault(require("http"));
9
9
  const https_1 = __importDefault(require("https"));
@@ -34,3 +34,33 @@ const downloadFile = async (url, destination) => {
34
34
  });
35
35
  };
36
36
  exports.downloadFile = downloadFile;
37
+ const getMaxAge = (header) => {
38
+ const parts = header.split(',');
39
+ let maxAge;
40
+ for (const part of parts) {
41
+ const [key, value] = part.split('=');
42
+ if ((key === null || key === void 0 ? void 0 : key.trim()) === 's-maxage') {
43
+ maxAge = value === null || value === void 0 ? void 0 : value.trim();
44
+ }
45
+ }
46
+ if (maxAge) {
47
+ const result = Number.parseInt(maxAge);
48
+ return Number.isNaN(result) ? 0 : result;
49
+ }
50
+ return 0;
51
+ };
52
+ exports.getMaxAge = getMaxAge;
53
+ const getMultiValueHeaders = (headers) => {
54
+ const multiValueHeaders = {};
55
+ for (const key of Object.keys(headers)) {
56
+ const header = headers[key];
57
+ if (Array.isArray(header)) {
58
+ multiValueHeaders[key] = header;
59
+ }
60
+ else {
61
+ multiValueHeaders[key] = [header];
62
+ }
63
+ }
64
+ return multiValueHeaders;
65
+ };
66
+ exports.getMultiValueHeaders = getMultiValueHeaders;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/plugin-nextjs",
3
- "version": "4.0.0-beta.11",
3
+ "version": "4.0.0-beta.12",
4
4
  "description": "Run Next.js seamlessly on Netlify",
5
5
  "main": "lib/index.js",
6
6
  "files": [
@@ -8,10 +8,10 @@
8
8
  "manifest.yml"
9
9
  ],
10
10
  "scripts": {
11
- "build:demo": "next build demo",
11
+ "build:demo": "next build demos/default",
12
12
  "cy:open": "cypress open --config-file cypress/config/all.json",
13
- "cy:run": "cypress run --config-file ../cypress/config/ci.json",
14
- "dev:demo": "next dev demo",
13
+ "cy:run": "cypress run --config-file ../../cypress/config/ci.json",
14
+ "dev:demo": "next dev demos/default",
15
15
  "format": "run-s format:check-fix:*",
16
16
  "format:ci": "run-s format:check:*",
17
17
  "format:check-fix:lint": "run-e format:check:lint format:fix:lint",
@@ -26,6 +26,8 @@
26
26
  "publish:test": "npm test",
27
27
  "test": "run-s build build:demo test:jest",
28
28
  "test:jest": "jest",
29
+ "test:jest:update": "jest --updateSnapshot",
30
+ "test:update": "run-s build build:demo test:jest:update",
29
31
  "prepare": "npm run build",
30
32
  "clean": "rimraf lib",
31
33
  "build": "tsc",
@@ -72,8 +74,9 @@
72
74
  "devDependencies": {
73
75
  "@babel/core": "^7.15.8",
74
76
  "@babel/preset-env": "^7.15.8",
75
- "@netlify/build": "^18.25.2",
76
- "@netlify/eslint-config-node": "^3.3.7",
77
+ "@babel/preset-typescript": "^7.16.0",
78
+ "@netlify/build": "^19.0.7",
79
+ "@netlify/eslint-config-node": "^3.3.9",
77
80
  "@testing-library/cypress": "^8.0.1",
78
81
  "@types/fs-extra": "^9.0.13",
79
82
  "@types/jest": "^27.0.2",
@@ -81,7 +84,7 @@
81
84
  "babel-jest": "^27.2.5",
82
85
  "cpy": "^8.1.2",
83
86
  "cypress": "^9.0.0",
84
- "eslint-config-next": "^11.0.0",
87
+ "eslint-config-next": "^12.0.0",
85
88
  "husky": "^4.3.0",
86
89
  "jest": "^27.0.0",
87
90
  "netlify-plugin-cypress": "^2.2.0",