@netlify/plugin-nextjs 4.0.0-beta.9 → 4.0.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/README.md CHANGED
@@ -1,9 +1,6 @@
1
- ![Next.js on Netlify Build Plugin](next-on-netlify.png)
1
+ ![Essential Next.js Build Plugin](next-on-netlify.png)
2
2
 
3
- # Essential Next.js Build Plugin (beta)
4
-
5
- :warning: This is the beta version of the Essential Next.js plugin. For the stable version, refer to
6
- [Essential Next.js plugin v3](https://github.com/netlify/netlify-plugin-nextjs/tree/v3#readme) :warning:
3
+ # Essential Next.js Build Plugin
7
4
 
8
5
  <p align="center">
9
6
  <a aria-label="npm version" href="https://www.npmjs.com/package/@netlify/plugin-nextjs">
@@ -19,36 +16,46 @@
19
16
  Version 4 is a complete rewrite of the Essential Next.js plugin. For full details of everything that's new, check out
20
17
  [the v4 release notes](https://github.com/netlify/netlify-plugin-nextjs/blob/main/docs/release-notes/v4.md)
21
18
 
22
- ## Installing the beta
19
+ ## Installing the plugin
23
20
 
24
- - Install the module:
21
+ The plugin installs automatically for new Next.js sites on Netlify. You can also install it manually like this:
25
22
 
26
23
  ```shell
27
- npm install -D @netlify/plugin-nextjs@beta
24
+ npm install -D @netlify/plugin-nextjs
28
25
  ```
29
26
 
30
- - Change the `publish` directory to `.next` and add the plugin to `netlify.toml` if not already installed:
27
+ ...then add the plugin to your `netlify.toml` file:
31
28
 
32
29
  ```toml
33
- [build]
34
- publish = ".next"
35
-
36
30
  [[plugins]]
37
31
  package = "@netlify/plugin-nextjs"
38
32
  ```
39
33
 
40
- If you previously set a custom `distDir` in your `next.config.js`, or set `node_bundler` or `external_node_modules` in
41
- your `netlify.toml` these are no longer needed and can be removed.
34
+ ## Migrating from an older version of the plugin
35
+
36
+ You can manually upgrade from the previous version of the plugin by running the following command:
37
+
38
+ ```shell
39
+ npm install -D @netlify/plugin-nextjs@latest
40
+ ```
41
+
42
+ Change the `publish` directory to `.next`:
43
+
44
+ ```toml
45
+ [build]
46
+ publish = ".next"
47
+ ```
48
+
49
+ If you previously set these values, they're no longer needed and can be removed:
50
+
51
+ - `distDir` in your `next.config.js`
52
+ - `node_bundler = "esbuild"` in `netlify.toml`
53
+ - `external_node_modules` in `netlify.toml`
42
54
 
43
55
  The `serverless` and `experimental-serverless-trace` targets are deprecated in Next 12, and all builds with this plugin
44
56
  will now use the default `server` target. If you previously set the target in your `next.config.js`, you should remove
45
57
  it.
46
58
 
47
- If you are using a monorepo you will need to change `publish` to point to the full path to the built `.next` directory,
48
- which may be in a subdirectory. If you have changed your `distDir` then it will need to match that.
49
-
50
- If you are using Nx, then you will need to point `publish` to the folder inside `dist`, e.g. `dist/apps/myapp/.next`.
51
-
52
59
  If you currently use redirects or rewrites on your site, see
53
60
  [the Rewrites and Redirects guide](https://github.com/netlify/netlify-plugin-nextjs/blob/main/docs/redirects-rewrites.md)
54
61
  for information on changes to how they are handled in this version.
@@ -57,7 +64,20 @@ If you want to use Next 12's beta Middleware feature, this will mostly work as e
57
64
  [read the docs on some caveats and workarounds](https://github.com/netlify/netlify-plugin-nextjs/blob/main/docs/middleware.md)
58
65
  that are currently needed.
59
66
 
60
- ## Beta feedback
67
+ ## Monorepos
68
+
69
+ If you are using a monorepo you will need to change `publish` to point to the full path to the built `.next` directory,
70
+ which may be in a subdirectory. If you have changed your `distDir` then it will need to match that.
71
+
72
+ If you are using Nx, then you will need to point `publish` to the folder inside `dist`, e.g. `dist/apps/myapp/.next`.
73
+
74
+ ## Incremental Static Regeneration (ISR)
75
+
76
+ The Essential Next.js plugin now fully supports ISR on Netlify. For more details see
77
+ [the ISR docs](https://github.com/netlify/netlify-plugin-nextjs/blob/main/docs/isr.md).
78
+
79
+ ## Feedback
61
80
 
62
- Please share any thoughts, feedback or questions about the beta
63
- [in our discussion](https://github.com/netlify/netlify-plugin-nextjs/discussions/706).
81
+ If you think you have found a bug in the plugin,
82
+ [please open an issue](https://github.com/netlify/netlify-plugin-nextjs/issues). If you have comments or feature
83
+ requests, [see the dicussion board](https://github.com/netlify/netlify-plugin-nextjs/discussions)
package/lib/constants.js CHANGED
@@ -1,8 +1,11 @@
1
- const HANDLER_FUNCTION_NAME = '___netlify-handler';
2
- const ODB_FUNCTION_NAME = '___netlify-odb-handler';
3
- const IMAGE_FUNCTION_NAME = '_ipx';
1
+ "use strict";
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;
4
+ exports.HANDLER_FUNCTION_NAME = '___netlify-handler';
5
+ exports.ODB_FUNCTION_NAME = '___netlify-odb-handler';
6
+ exports.IMAGE_FUNCTION_NAME = '_ipx';
4
7
  // These are paths in .next that shouldn't be publicly accessible
5
- const HIDDEN_PATHS = [
8
+ exports.HIDDEN_PATHS = [
6
9
  '/cache/*',
7
10
  '/server/*',
8
11
  '/serverless/*',
@@ -13,9 +16,15 @@ const HIDDEN_PATHS = [
13
16
  '/react-loadable-manifest.json',
14
17
  '/BUILD_ID',
15
18
  ];
16
- module.exports = {
17
- HIDDEN_PATHS,
18
- IMAGE_FUNCTION_NAME,
19
- HANDLER_FUNCTION_NAME,
20
- ODB_FUNCTION_NAME,
21
- };
19
+ exports.ODB_FUNCTION_PATH = `/.netlify/builders/${exports.ODB_FUNCTION_NAME}`;
20
+ exports.HANDLER_FUNCTION_PATH = `/.netlify/functions/${exports.HANDLER_FUNCTION_NAME}`;
21
+ exports.DEFAULT_FUNCTIONS_SRC = 'netlify/functions';
22
+ exports.CATCH_ALL_REGEX = /\/\[\.{3}(.*)](.json)?$/;
23
+ exports.OPTIONAL_CATCH_ALL_REGEX = /\/\[{2}\.{3}(.*)]{2}(.json)?$/;
24
+ exports.DYNAMIC_PARAMETER_REGEX = /\/\[(.*?)]/g;
25
+ exports.MINIMUM_REVALIDATE_SECONDS = 60;
26
+ // 50MB, which is the documented max, though the hard max seems to be higher
27
+ exports.LAMBDA_MAX_SIZE = 1024 * 1024 * 50;
28
+ exports.DIVIDER = `
29
+ ────────────────────────────────────────────────────────────────
30
+ `;
@@ -1,6 +1,9 @@
1
- const { posix: { join }, } = require('path');
2
- exports.restoreCache = async ({ cache, publish }) => {
3
- const cacheDir = join(publish, 'cache');
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.saveCache = exports.restoreCache = void 0;
4
+ const path_1 = require("path");
5
+ const restoreCache = async ({ cache, publish }) => {
6
+ const cacheDir = path_1.join(publish, 'cache');
4
7
  if (await cache.restore(cacheDir)) {
5
8
  console.log('Next.js cache restored.');
6
9
  }
@@ -8,9 +11,10 @@ exports.restoreCache = async ({ cache, publish }) => {
8
11
  console.log('No Next.js cache to restore.');
9
12
  }
10
13
  };
11
- exports.saveCache = async ({ cache, publish }) => {
12
- const cacheDir = join(publish, 'cache');
13
- const buildManifest = join(publish, 'build-manifest.json');
14
+ exports.restoreCache = restoreCache;
15
+ const saveCache = async ({ cache, publish }) => {
16
+ const cacheDir = path_1.join(publish, 'cache');
17
+ const buildManifest = path_1.join(publish, 'build-manifest.json');
14
18
  if (await cache.save(cacheDir, { digests: [buildManifest] })) {
15
19
  console.log('Next.js cache saved.');
16
20
  }
@@ -18,3 +22,4 @@ exports.saveCache = async ({ cache, publish }) => {
18
22
  console.log('No Next.js cache to save.');
19
23
  }
20
24
  };
25
+ exports.saveCache = saveCache;
@@ -1,81 +1,22 @@
1
- // @ts-check
2
- const { readJSON, existsSync } = require('fs-extra');
3
- const { join, dirname, relative } = require('pathe');
4
- const slash = require('slash');
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.configureHandlerFunctions = exports.getNextConfig = void 0;
7
+ const fs_extra_1 = require("fs-extra");
8
+ const pathe_1 = require("pathe");
9
+ const slash_1 = __importDefault(require("slash"));
10
+ const constants_1 = require("../constants");
5
11
  const defaultFailBuild = (message, { error }) => {
6
12
  throw new Error(`${message}\n${error && error.stack}`);
7
13
  };
8
- const { HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME, HIDDEN_PATHS } = require('../constants');
9
- const ODB_FUNCTION_PATH = `/.netlify/builders/${ODB_FUNCTION_NAME}`;
10
- const HANDLER_FUNCTION_PATH = `/.netlify/functions/${HANDLER_FUNCTION_NAME}`;
11
- const CATCH_ALL_REGEX = /\/\[\.{3}(.*)](.json)?$/;
12
- const OPTIONAL_CATCH_ALL_REGEX = /\/\[{2}\.{3}(.*)]{2}(.json)?$/;
13
- const DYNAMIC_PARAMETER_REGEX = /\/\[(.*?)]/g;
14
- const getNetlifyRoutes = (nextRoute) => {
15
- let netlifyRoutes = [nextRoute];
16
- // If the route is an optional catch-all route, we need to add a second
17
- // Netlify route for the base path (when no parameters are present).
18
- // The file ending must be present!
19
- if (OPTIONAL_CATCH_ALL_REGEX.test(nextRoute)) {
20
- let netlifyRoute = nextRoute.replace(OPTIONAL_CATCH_ALL_REGEX, '$2');
21
- // When optional catch-all route is at top-level, the regex on line 19 will
22
- // create an empty string, but actually needs to be a forward slash
23
- if (netlifyRoute === '')
24
- netlifyRoute = '/';
25
- // When optional catch-all route is at top-level, the regex on line 19 will
26
- // create an incorrect route for the data route. For example, it creates
27
- // /_next/data/%BUILDID%.json, but NextJS looks for
28
- // /_next/data/%BUILDID%/index.json
29
- netlifyRoute = netlifyRoute.replace(/(\/_next\/data\/[^/]+).json/, '$1/index.json');
30
- // Add second route to the front of the array
31
- netlifyRoutes.unshift(netlifyRoute);
32
- }
33
- // Replace catch-all, e.g., [...slug]
34
- netlifyRoutes = netlifyRoutes.map((route) => route.replace(CATCH_ALL_REGEX, '/:$1/*'));
35
- // Replace optional catch-all, e.g., [[...slug]]
36
- netlifyRoutes = netlifyRoutes.map((route) => route.replace(OPTIONAL_CATCH_ALL_REGEX, '/*'));
37
- // Replace dynamic parameters, e.g., [id]
38
- netlifyRoutes = netlifyRoutes.map((route) => route.replace(DYNAMIC_PARAMETER_REGEX, '/:$1'));
39
- return netlifyRoutes;
40
- };
41
- exports.generateRedirects = async ({ netlifyConfig, basePath, i18n }) => {
42
- const { dynamicRoutes } = await readJSON(join(netlifyConfig.build.publish, 'prerender-manifest.json'));
43
- const redirects = [];
44
- netlifyConfig.redirects.push(...HIDDEN_PATHS.map((path) => ({
45
- from: `${basePath}${path}`,
46
- to: '/404.html',
47
- status: 404,
48
- force: true,
49
- })));
50
- const dynamicRouteEntries = Object.entries(dynamicRoutes);
51
- dynamicRouteEntries.sort((a, b) => a[0].localeCompare(b[0]));
52
- dynamicRouteEntries.forEach(([route, { dataRoute, fallback }]) => {
53
- // Add redirects if fallback is "null" (aka blocking) or true/a string
54
- if (fallback === false) {
55
- return;
56
- }
57
- redirects.push(...getNetlifyRoutes(route), ...getNetlifyRoutes(dataRoute));
58
- });
59
- if (i18n) {
60
- netlifyConfig.redirects.push({ from: `${basePath}/:locale/_next/static/*`, to: `/static/:splat`, status: 200 });
61
- }
62
- // This is only used in prod, so dev uses `next dev` directly
63
- netlifyConfig.redirects.push({ from: `${basePath}/_next/static/*`, to: `/static/:splat`, status: 200 }, {
64
- from: `${basePath}/*`,
65
- to: HANDLER_FUNCTION_PATH,
66
- status: 200,
67
- conditions: { Cookie: ['__prerender_bypass', '__next_preview_data'] },
68
- force: true,
69
- }, ...redirects.map((redirect) => ({
70
- from: `${basePath}${redirect}`,
71
- to: ODB_FUNCTION_PATH,
72
- status: 200,
73
- })), { from: `${basePath}/*`, to: HANDLER_FUNCTION_PATH, status: 200 });
74
- };
75
- exports.getNextConfig = async function getNextConfig({ publish, failBuild = defaultFailBuild }) {
14
+ const getNextConfig = async function getNextConfig({ publish, failBuild = defaultFailBuild, }) {
76
15
  try {
77
- const { config, appDir, ignore } = await readJSON(join(publish, 'required-server-files.json'));
16
+ const { config, appDir, ignore } = await fs_extra_1.readJSON(pathe_1.join(publish, 'required-server-files.json'));
78
17
  if (!config) {
18
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
19
+ // @ts-ignore
79
20
  return failBuild('Error loading your Next config');
80
21
  }
81
22
  return { ...config, appDir, ignore };
@@ -84,26 +25,27 @@ exports.getNextConfig = async function getNextConfig({ publish, failBuild = defa
84
25
  return failBuild('Error loading your Next config', { error });
85
26
  }
86
27
  };
28
+ exports.getNextConfig = getNextConfig;
87
29
  const resolveModuleRoot = (moduleName) => {
88
30
  try {
89
- return dirname(relative(process.cwd(), require.resolve(`${moduleName}/package.json`, { paths: [process.cwd()] })));
31
+ return pathe_1.dirname(pathe_1.relative(process.cwd(), require.resolve(`${moduleName}/package.json`, { paths: [process.cwd()] })));
90
32
  }
91
33
  catch (error) {
92
34
  return null;
93
35
  }
94
36
  };
95
37
  const DEFAULT_EXCLUDED_MODULES = ['sharp', 'electron'];
96
- exports.configureHandlerFunctions = ({ netlifyConfig, publish, ignore = [] }) => {
38
+ const configureHandlerFunctions = ({ netlifyConfig, publish, ignore = [] }) => {
97
39
  var _a;
98
40
  /* eslint-disable no-underscore-dangle */
99
41
  (_a = netlifyConfig.functions)._ipx || (_a._ipx = {});
100
42
  netlifyConfig.functions._ipx.node_bundler = 'nft';
101
- [HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME].forEach((functionName) => {
43
+ [constants_1.HANDLER_FUNCTION_NAME, constants_1.ODB_FUNCTION_NAME].forEach((functionName) => {
102
44
  var _a, _b;
103
45
  (_a = netlifyConfig.functions)[functionName] || (_a[functionName] = { included_files: [], external_node_modules: [] });
104
46
  netlifyConfig.functions[functionName].node_bundler = 'nft';
105
47
  (_b = netlifyConfig.functions[functionName]).included_files || (_b.included_files = []);
106
- 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)}`));
48
+ 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_1.default(path)}`));
107
49
  const nextRoot = resolveModuleRoot('next');
108
50
  if (nextRoot) {
109
51
  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`);
@@ -116,3 +58,4 @@ exports.configureHandlerFunctions = ({ netlifyConfig, publish, ignore = [] }) =>
116
58
  });
117
59
  });
118
60
  };
61
+ exports.configureHandlerFunctions = configureHandlerFunctions;
@@ -1,14 +1,22 @@
1
- // @ts-check
2
- const { cpus } = require('os');
3
- const { yellowBright } = require('chalk');
4
- const { existsSync, readJson, move, cpSync, copy, writeJson } = require('fs-extra');
5
- const globby = require('globby');
6
- const { outdent } = require('outdent');
7
- const pLimit = require('p-limit');
8
- const { join } = require('pathe');
9
- const slash = require('slash');
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.movePublicFiles = exports.unpatchNextFiles = exports.patchNextFiles = exports.moveStaticPages = exports.matchesRewrite = exports.matchesRedirect = exports.matchMiddleware = exports.stripLocale = exports.isDynamicRoute = void 0;
7
+ /* eslint-disable max-lines */
8
+ const os_1 = require("os");
9
+ const chalk_1 = require("chalk");
10
+ const fs_extra_1 = require("fs-extra");
11
+ const globby_1 = __importDefault(require("globby"));
12
+ const outdent_1 = require("outdent");
13
+ const p_limit_1 = __importDefault(require("p-limit"));
14
+ const pathe_1 = require("pathe");
15
+ const slash_1 = __importDefault(require("slash"));
16
+ const constants_1 = require("../constants");
10
17
  const TEST_ROUTE = /(|\/)\[[^/]+?](\/|\.html|$)/;
11
18
  const isDynamicRoute = (route) => TEST_ROUTE.test(route);
19
+ exports.isDynamicRoute = isDynamicRoute;
12
20
  const stripLocale = (rawPath, locales = []) => {
13
21
  const [locale, ...segments] = rawPath.split('/');
14
22
  if (locales.includes(locale)) {
@@ -16,48 +24,112 @@ const stripLocale = (rawPath, locales = []) => {
16
24
  }
17
25
  return rawPath;
18
26
  };
27
+ exports.stripLocale = stripLocale;
19
28
  const matchMiddleware = (middleware, filePath) => (middleware === null || middleware === void 0 ? void 0 : middleware.includes('')) ||
20
29
  (middleware === null || middleware === void 0 ? void 0 : middleware.find((middlewarePath) => filePath === middlewarePath || filePath === `${middlewarePath}.html` || filePath.startsWith(`${middlewarePath}/`)));
21
30
  exports.matchMiddleware = matchMiddleware;
22
- exports.stripLocale = stripLocale;
23
- exports.isDynamicRoute = isDynamicRoute;
24
- exports.moveStaticPages = async ({ netlifyConfig, target, i18n }) => {
31
+ const matchesRedirect = (file, redirects) => {
32
+ if (!Array.isArray(redirects)) {
33
+ return false;
34
+ }
35
+ return redirects.some((redirect) => {
36
+ if (!redirect.regex || redirect.internal) {
37
+ return false;
38
+ }
39
+ // Strips the extension from the file path
40
+ return new RegExp(redirect.regex).test(`/${file.slice(0, -5)}`);
41
+ });
42
+ };
43
+ exports.matchesRedirect = matchesRedirect;
44
+ const matchesRewrite = (file, rewrites) => {
45
+ if (Array.isArray(rewrites)) {
46
+ return exports.matchesRedirect(file, rewrites);
47
+ }
48
+ if (!Array.isArray(rewrites === null || rewrites === void 0 ? void 0 : rewrites.beforeFiles)) {
49
+ return false;
50
+ }
51
+ return exports.matchesRedirect(file, rewrites.beforeFiles);
52
+ };
53
+ exports.matchesRewrite = matchesRewrite;
54
+ // eslint-disable-next-line max-lines-per-function
55
+ const moveStaticPages = async ({ netlifyConfig, target, i18n, }) => {
25
56
  console.log('Moving static page files to serve from CDN...');
26
- const outputDir = join(netlifyConfig.build.publish, target === 'server' ? 'server' : 'serverless');
27
- const root = join(outputDir, 'pages');
57
+ const outputDir = pathe_1.join(netlifyConfig.build.publish, target === 'server' ? 'server' : 'serverless');
58
+ const root = pathe_1.join(outputDir, 'pages');
59
+ const buildId = fs_extra_1.readFileSync(pathe_1.join(netlifyConfig.build.publish, 'BUILD_ID'), 'utf8').trim();
60
+ const dataDir = pathe_1.join('_next', 'data', buildId);
61
+ await fs_extra_1.ensureDir(dataDir);
28
62
  // Load the middleware manifest so we can check if a file matches it before moving
29
63
  let middleware;
30
- const manifestPath = join(outputDir, 'middleware-manifest.json');
31
- if (existsSync(manifestPath)) {
32
- const manifest = await readJson(manifestPath);
64
+ const manifestPath = pathe_1.join(outputDir, 'middleware-manifest.json');
65
+ if (fs_extra_1.existsSync(manifestPath)) {
66
+ const manifest = await fs_extra_1.readJson(manifestPath);
33
67
  if (manifest === null || manifest === void 0 ? void 0 : manifest.middleware) {
34
68
  middleware = Object.keys(manifest.middleware).map((path) => path.slice(1));
35
69
  }
36
70
  }
71
+ const prerenderManifest = await fs_extra_1.readJson(pathe_1.join(netlifyConfig.build.publish, 'prerender-manifest.json'));
72
+ const { redirects, rewrites } = await fs_extra_1.readJson(pathe_1.join(netlifyConfig.build.publish, 'routes-manifest.json'));
73
+ const isrFiles = new Set();
74
+ const shortRevalidateRoutes = [];
75
+ Object.entries(prerenderManifest.routes).forEach(([route, { initialRevalidateSeconds }]) => {
76
+ if (initialRevalidateSeconds) {
77
+ // Find all files used by ISR routes
78
+ const trimmedPath = route.slice(1);
79
+ isrFiles.add(`${trimmedPath}.html`);
80
+ isrFiles.add(`${trimmedPath}.json`);
81
+ if (initialRevalidateSeconds < constants_1.MINIMUM_REVALIDATE_SECONDS) {
82
+ shortRevalidateRoutes.push({ Route: route, Revalidate: initialRevalidateSeconds });
83
+ }
84
+ }
85
+ });
37
86
  const files = [];
87
+ const filesManifest = {};
38
88
  const moveFile = async (file) => {
39
- const source = join(root, file);
89
+ const isData = file.endsWith('.json');
90
+ const source = pathe_1.join(root, file);
91
+ const targetFile = isData ? pathe_1.join(dataDir, file) : file;
40
92
  files.push(file);
41
- const dest = join(netlifyConfig.build.publish, file);
42
- await move(source, dest);
93
+ filesManifest[file] = targetFile;
94
+ const dest = pathe_1.join(netlifyConfig.build.publish, targetFile);
95
+ try {
96
+ await fs_extra_1.move(source, dest);
97
+ }
98
+ catch (error) {
99
+ console.warn('Error moving file', source, error);
100
+ }
43
101
  };
44
102
  // Move all static files, except error documents and nft manifests
45
- const pages = await globby(['**/*.{html,json}', '!**/(500|404|*.js.nft).{html,json}'], {
103
+ const pages = await globby_1.default(['**/*.{html,json}', '!**/(500|404|*.js.nft).{html,json}'], {
46
104
  cwd: root,
47
105
  dot: true,
48
106
  });
49
107
  const matchingMiddleware = new Set();
50
108
  const matchedPages = new Set();
109
+ const matchedRedirects = new Set();
110
+ const matchedRewrites = new Set();
51
111
  // Limit concurrent file moves to number of cpus or 2 if there is only 1
52
- const limit = pLimit(Math.max(2, cpus().length));
53
- const promises = pages.map(async (rawPath) => {
54
- const filePath = slash(rawPath);
55
- if (isDynamicRoute(filePath)) {
112
+ const limit = p_limit_1.default(Math.max(2, os_1.cpus().length));
113
+ const promises = pages.map((rawPath) => {
114
+ const filePath = slash_1.default(rawPath);
115
+ // Don't move ISR files, as they're used for the first request
116
+ if (isrFiles.has(filePath)) {
117
+ return;
118
+ }
119
+ if (exports.isDynamicRoute(filePath)) {
120
+ return;
121
+ }
122
+ if (exports.matchesRedirect(filePath, redirects)) {
123
+ matchedRedirects.add(filePath);
124
+ return;
125
+ }
126
+ if (exports.matchesRewrite(filePath, rewrites)) {
127
+ matchedRewrites.add(filePath);
56
128
  return;
57
129
  }
58
130
  // Middleware matches against the unlocalised path
59
- const unlocalizedPath = stripLocale(rawPath, i18n === null || i18n === void 0 ? void 0 : i18n.locales);
60
- const middlewarePath = matchMiddleware(middleware, unlocalizedPath);
131
+ const unlocalizedPath = exports.stripLocale(rawPath, i18n === null || i18n === void 0 ? void 0 : i18n.locales);
132
+ const middlewarePath = exports.matchMiddleware(middleware, unlocalizedPath);
61
133
  // If a file matches middleware it can't be offloaded to the CDN, and needs to stay at the origin to be served by next/server
62
134
  if (middlewarePath) {
63
135
  matchingMiddleware.add(middlewarePath);
@@ -69,36 +141,143 @@ exports.moveStaticPages = async ({ netlifyConfig, target, i18n }) => {
69
141
  await Promise.all(promises);
70
142
  console.log(`Moved ${files.length} files`);
71
143
  if (matchedPages.size !== 0) {
72
- console.log(yellowBright(outdent `
73
- Skipped moving ${matchedPages.size} ${matchedPages.size === 1 ? 'file because it matches' : 'files because they match'} middleware, so cannot be deployed to the CDN and will be served from the origin instead. This is fine, but we're letting you know because it may not be what you expect.
144
+ console.log(chalk_1.yellowBright(outdent_1.outdent `
145
+ Skipped moving ${matchedPages.size} ${matchedPages.size === 1 ? 'file because it matches' : 'files because they match'} middleware, so cannot be deployed to the CDN and will be served from the origin instead.
146
+ This is fine, but we're letting you know because it may not be what you expect.
74
147
  `));
75
- console.log(outdent `
148
+ console.log(outdent_1.outdent `
76
149
  The following middleware matched statically-rendered pages:
77
150
 
78
- ${yellowBright([...matchingMiddleware].map((mid) => `- /${mid}/_middleware`).join('\n'))}
151
+ ${chalk_1.yellowBright([...matchingMiddleware].map((mid) => `- /${mid}/_middleware`).join('\n'))}
152
+ ${constants_1.DIVIDER}
79
153
  `);
80
154
  // There could potentially be thousands of matching pages, so we don't want to spam the console with this
81
155
  if (matchedPages.size < 50) {
82
- console.log(outdent `
156
+ console.log(outdent_1.outdent `
83
157
  The following files matched middleware and were not moved to the CDN:
84
158
 
85
- ${yellowBright([...matchedPages].map((mid) => `- ${mid}`).join('\n'))}
159
+ ${chalk_1.yellowBright([...matchedPages].map((mid) => `- ${mid}`).join('\n'))}
160
+ ${constants_1.DIVIDER}
161
+ `);
162
+ }
163
+ }
164
+ if (matchedRedirects.size !== 0 || matchedRewrites.size !== 0) {
165
+ console.log(chalk_1.yellowBright(outdent_1.outdent `
166
+ Skipped moving ${matchedRedirects.size + matchedRewrites.size} files because they match redirects or beforeFiles rewrites, so cannot be deployed to the CDN and will be served from the origin instead.
167
+ `));
168
+ if (matchedRedirects.size < 50 && matchedRedirects.size !== 0) {
169
+ console.log(outdent_1.outdent `
170
+ The following files matched redirects and were not moved to the CDN:
171
+
172
+ ${chalk_1.yellowBright([...matchedRedirects].map((mid) => `- ${mid}`).join('\n'))}
173
+ ${constants_1.DIVIDER}
174
+ `);
175
+ }
176
+ if (matchedRewrites.size < 50 && matchedRewrites.size !== 0) {
177
+ console.log(outdent_1.outdent `
178
+ The following files matched beforeFiles rewrites and were not moved to the CDN:
179
+
180
+ ${chalk_1.yellowBright([...matchedRewrites].map((mid) => `- ${mid}`).join('\n'))}
181
+ ${constants_1.DIVIDER}
86
182
  `);
87
183
  }
88
184
  }
89
185
  // Write the manifest for use in the serverless functions
90
- await writeJson(join(netlifyConfig.build.publish, 'static-manifest.json'), files);
186
+ await fs_extra_1.writeJson(pathe_1.join(netlifyConfig.build.publish, 'static-manifest.json'), Object.entries(filesManifest));
91
187
  if (i18n === null || i18n === void 0 ? void 0 : i18n.defaultLocale) {
92
188
  // Copy the default locale into the root
93
- const defaultLocaleDir = join(netlifyConfig.build.publish, i18n.defaultLocale);
94
- if (existsSync(defaultLocaleDir)) {
95
- await copy(defaultLocaleDir, `${netlifyConfig.build.publish}/`);
189
+ const defaultLocaleDir = pathe_1.join(netlifyConfig.build.publish, i18n.defaultLocale);
190
+ if (fs_extra_1.existsSync(defaultLocaleDir)) {
191
+ await fs_extra_1.copy(defaultLocaleDir, `${netlifyConfig.build.publish}/`);
192
+ }
193
+ const defaultLocaleIndex = pathe_1.join(netlifyConfig.build.publish, `${i18n.defaultLocale}.html`);
194
+ const indexHtml = pathe_1.join(netlifyConfig.build.publish, 'index.html');
195
+ if (fs_extra_1.existsSync(defaultLocaleIndex) && !fs_extra_1.existsSync(indexHtml)) {
196
+ try {
197
+ await fs_extra_1.copy(defaultLocaleIndex, indexHtml, { overwrite: false });
198
+ await fs_extra_1.copy(pathe_1.join(netlifyConfig.build.publish, `${i18n.defaultLocale}.json`), pathe_1.join(netlifyConfig.build.publish, 'index.json'), { overwrite: false });
199
+ }
200
+ catch { }
201
+ }
202
+ }
203
+ if (shortRevalidateRoutes.length !== 0) {
204
+ console.log(outdent_1.outdent `
205
+ The following routes use "revalidate" values of under ${constants_1.MINIMUM_REVALIDATE_SECONDS} seconds, which is not supported.
206
+ They will use a revalidate time of ${constants_1.MINIMUM_REVALIDATE_SECONDS} seconds instead.
207
+ `);
208
+ console.table(shortRevalidateRoutes);
209
+ // TODO: add these docs
210
+ // console.log(
211
+ // outdent`
212
+ // For more information, see https://ntl.fyi/next-revalidate-time
213
+ // ${DIVIDER}
214
+ // `,
215
+ // )
216
+ }
217
+ };
218
+ exports.moveStaticPages = moveStaticPages;
219
+ const patchFile = async ({ file, from, to }) => {
220
+ if (!fs_extra_1.existsSync(file)) {
221
+ return;
222
+ }
223
+ const content = await fs_extra_1.readFile(file, 'utf8');
224
+ if (content.includes(to)) {
225
+ return;
226
+ }
227
+ const newContent = content.replace(from, to);
228
+ await fs_extra_1.writeFile(`${file}.orig`, content);
229
+ await fs_extra_1.writeFile(file, newContent);
230
+ };
231
+ const getServerFile = (root) => {
232
+ let serverFile;
233
+ try {
234
+ serverFile = require.resolve('next/dist/server/next-server', { paths: [root] });
235
+ }
236
+ catch {
237
+ // Ignore
238
+ }
239
+ if (!serverFile) {
240
+ try {
241
+ // eslint-disable-next-line node/no-missing-require
242
+ serverFile = require.resolve('next/dist/next-server/server/next-server', { paths: [root] });
96
243
  }
244
+ catch {
245
+ // Ignore
246
+ }
247
+ }
248
+ return serverFile;
249
+ };
250
+ const patchNextFiles = async (root) => {
251
+ const serverFile = getServerFile(root);
252
+ console.log(`Patching ${serverFile}`);
253
+ if (serverFile) {
254
+ await patchFile({
255
+ file: serverFile,
256
+ from: `let ssgCacheKey = `,
257
+ to: `let ssgCacheKey = process.env._BYPASS_SSG || `,
258
+ });
259
+ }
260
+ };
261
+ exports.patchNextFiles = patchNextFiles;
262
+ const unpatchNextFiles = async (root) => {
263
+ const serverFile = getServerFile(root);
264
+ const origFile = `${serverFile}.orig`;
265
+ if (fs_extra_1.existsSync(origFile)) {
266
+ await fs_extra_1.move(origFile, serverFile, { overwrite: true });
97
267
  }
98
268
  };
99
- exports.movePublicFiles = async ({ appDir, publish }) => {
100
- const publicDir = join(appDir, 'public');
101
- if (existsSync(publicDir)) {
102
- await copy(publicDir, `${publish}/`);
269
+ exports.unpatchNextFiles = unpatchNextFiles;
270
+ const movePublicFiles = async ({ appDir, outdir, publish, }) => {
271
+ // `outdir` is a config property added when using Next.js with Nx. It's typically
272
+ // a relative path outside of the appDir, e.g. '../../dist/apps/<app-name>', and
273
+ // the parent directory of the .next directory.
274
+ // If it exists, copy the files from the public folder there in order to include
275
+ // any files that were generated during the build. Otherwise, copy the public
276
+ // directory from the original app directory.
277
+ const publicDir = outdir ? pathe_1.join(appDir, outdir, 'public') : pathe_1.join(appDir, 'public');
278
+ if (fs_extra_1.existsSync(publicDir)) {
279
+ await fs_extra_1.copy(publicDir, `${publish}/`);
103
280
  }
104
281
  };
282
+ exports.movePublicFiles = movePublicFiles;
283
+ /* eslint-enable max-lines */