@netlify/plugin-nextjs 4.0.0-rc.0 → 4.1.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 +51 -22
- package/lib/constants.js +7 -1
- package/lib/helpers/cache.js +3 -3
- package/lib/helpers/config.js +3 -3
- package/lib/helpers/files.js +104 -79
- package/lib/helpers/functions.js +16 -16
- package/lib/helpers/redirects.js +28 -25
- package/lib/helpers/utils.js +6 -1
- package/lib/helpers/verification.js +107 -45
- package/lib/index.js +59 -34
- package/lib/templates/getHandler.js +2 -2
- package/lib/templates/getPageResolver.js +5 -5
- package/lib/templates/handlerUtils.js +8 -8
- package/lib/templates/ipx.js +1 -1
- package/package.json +6 -7
package/README.md
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-

|
|
2
2
|
|
|
3
|
-
# Essential Next.js Build Plugin
|
|
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
|
|
19
|
+
## Installing the plugin
|
|
23
20
|
|
|
24
|
-
|
|
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
|
|
24
|
+
npm install -D @netlify/plugin-nextjs
|
|
28
25
|
```
|
|
29
26
|
|
|
30
|
-
|
|
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
|
-
|
|
41
|
-
|
|
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,29 @@ 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
|
-
##
|
|
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
|
+
## Use with `next export`
|
|
80
|
+
|
|
81
|
+
If you are using `next export` to generate a static site, you do not need most of the functionality of this plugin and
|
|
82
|
+
you can remove it. Alternatively you can
|
|
83
|
+
[set the environment variable](https://docs.netlify.com/configure-builds/environment-variables/)
|
|
84
|
+
`NETLIFY_NEXT_PLUGIN_SKIP` to `true` and the plugin will handle caching but won't generate any functions for SSR
|
|
85
|
+
support. See [`demos/next-export`](https://github.com/netlify/netlify-plugin-nextjs/tree/main/demos/next-export) for an
|
|
86
|
+
example.
|
|
87
|
+
|
|
88
|
+
## Feedback
|
|
61
89
|
|
|
62
|
-
|
|
63
|
-
[
|
|
90
|
+
If you think you have found a bug in the plugin,
|
|
91
|
+
[please open an issue](https://github.com/netlify/netlify-plugin-nextjs/issues). If you have comments or feature
|
|
92
|
+
requests, [see the dicussion board](https://github.com/netlify/netlify-plugin-nextjs/discussions)
|
package/lib/constants.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
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_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';
|
|
@@ -22,3 +22,9 @@ exports.DEFAULT_FUNCTIONS_SRC = 'netlify/functions';
|
|
|
22
22
|
exports.CATCH_ALL_REGEX = /\/\[\.{3}(.*)](.json)?$/;
|
|
23
23
|
exports.OPTIONAL_CATCH_ALL_REGEX = /\/\[{2}\.{3}(.*)]{2}(.json)?$/;
|
|
24
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
|
+
`;
|
package/lib/helpers/cache.js
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.saveCache = exports.restoreCache = void 0;
|
|
4
4
|
const path_1 = require("path");
|
|
5
5
|
const restoreCache = async ({ cache, publish }) => {
|
|
6
|
-
const cacheDir = path_1.
|
|
6
|
+
const cacheDir = path_1.join(publish, 'cache');
|
|
7
7
|
if (await cache.restore(cacheDir)) {
|
|
8
8
|
console.log('Next.js cache restored.');
|
|
9
9
|
}
|
|
@@ -13,8 +13,8 @@ const restoreCache = async ({ cache, publish }) => {
|
|
|
13
13
|
};
|
|
14
14
|
exports.restoreCache = restoreCache;
|
|
15
15
|
const saveCache = async ({ cache, publish }) => {
|
|
16
|
-
const cacheDir = path_1.
|
|
17
|
-
const buildManifest = path_1.
|
|
16
|
+
const cacheDir = path_1.join(publish, 'cache');
|
|
17
|
+
const buildManifest = path_1.join(publish, 'build-manifest.json');
|
|
18
18
|
if (await cache.save(cacheDir, { digests: [buildManifest] })) {
|
|
19
19
|
console.log('Next.js cache saved.');
|
|
20
20
|
}
|
package/lib/helpers/config.js
CHANGED
|
@@ -13,7 +13,7 @@ const defaultFailBuild = (message, { error }) => {
|
|
|
13
13
|
};
|
|
14
14
|
const getNextConfig = async function getNextConfig({ publish, failBuild = defaultFailBuild, }) {
|
|
15
15
|
try {
|
|
16
|
-
const { config, appDir, ignore } = await
|
|
16
|
+
const { config, appDir, ignore } = await fs_extra_1.readJSON(pathe_1.join(publish, 'required-server-files.json'));
|
|
17
17
|
if (!config) {
|
|
18
18
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
19
19
|
// @ts-ignore
|
|
@@ -28,7 +28,7 @@ const getNextConfig = async function getNextConfig({ publish, failBuild = defaul
|
|
|
28
28
|
exports.getNextConfig = getNextConfig;
|
|
29
29
|
const resolveModuleRoot = (moduleName) => {
|
|
30
30
|
try {
|
|
31
|
-
return
|
|
31
|
+
return pathe_1.dirname(pathe_1.relative(process.cwd(), require.resolve(`${moduleName}/package.json`, { paths: [process.cwd()] })));
|
|
32
32
|
}
|
|
33
33
|
catch (error) {
|
|
34
34
|
return null;
|
|
@@ -45,7 +45,7 @@ const configureHandlerFunctions = ({ netlifyConfig, publish, ignore = [] }) => {
|
|
|
45
45
|
(_a = netlifyConfig.functions)[functionName] || (_a[functionName] = { included_files: [], external_node_modules: [] });
|
|
46
46
|
netlifyConfig.functions[functionName].node_bundler = 'nft';
|
|
47
47
|
(_b = netlifyConfig.functions[functionName]).included_files || (_b.included_files = []);
|
|
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) => `!${
|
|
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)}`));
|
|
49
49
|
const nextRoot = resolveModuleRoot('next');
|
|
50
50
|
if (nextRoot) {
|
|
51
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`);
|
package/lib/helpers/files.js
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
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;
|
|
1
7
|
/* eslint-disable max-lines */
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
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,8 +24,10 @@ 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}/`)));
|
|
30
|
+
exports.matchMiddleware = matchMiddleware;
|
|
21
31
|
const matchesRedirect = (file, redirects) => {
|
|
22
32
|
if (!Array.isArray(redirects)) {
|
|
23
33
|
return false;
|
|
@@ -30,66 +40,67 @@ const matchesRedirect = (file, redirects) => {
|
|
|
30
40
|
return new RegExp(redirect.regex).test(`/${file.slice(0, -5)}`);
|
|
31
41
|
});
|
|
32
42
|
};
|
|
43
|
+
exports.matchesRedirect = matchesRedirect;
|
|
33
44
|
const matchesRewrite = (file, rewrites) => {
|
|
34
45
|
if (Array.isArray(rewrites)) {
|
|
35
|
-
return matchesRedirect(file, rewrites);
|
|
46
|
+
return exports.matchesRedirect(file, rewrites);
|
|
36
47
|
}
|
|
37
48
|
if (!Array.isArray(rewrites === null || rewrites === void 0 ? void 0 : rewrites.beforeFiles)) {
|
|
38
49
|
return false;
|
|
39
50
|
}
|
|
40
|
-
return matchesRedirect(file, rewrites.beforeFiles);
|
|
51
|
+
return exports.matchesRedirect(file, rewrites.beforeFiles);
|
|
41
52
|
};
|
|
42
|
-
exports.matchesRedirect = matchesRedirect;
|
|
43
53
|
exports.matchesRewrite = matchesRewrite;
|
|
44
|
-
exports.matchMiddleware = matchMiddleware;
|
|
45
|
-
exports.stripLocale = stripLocale;
|
|
46
|
-
exports.isDynamicRoute = isDynamicRoute;
|
|
47
54
|
// eslint-disable-next-line max-lines-per-function
|
|
48
|
-
|
|
55
|
+
const moveStaticPages = async ({ netlifyConfig, target, i18n, }) => {
|
|
49
56
|
console.log('Moving static page files to serve from CDN...');
|
|
50
|
-
const outputDir = join(netlifyConfig.build.publish, target === 'server' ? 'server' : 'serverless');
|
|
51
|
-
const root = join(outputDir, 'pages');
|
|
52
|
-
const buildId = readFileSync(join(netlifyConfig.build.publish, 'BUILD_ID'), 'utf8').trim();
|
|
53
|
-
const dataDir = join('_next', 'data', buildId);
|
|
54
|
-
await ensureDir(dataDir);
|
|
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);
|
|
55
62
|
// Load the middleware manifest so we can check if a file matches it before moving
|
|
56
63
|
let middleware;
|
|
57
|
-
const manifestPath = join(outputDir, 'middleware-manifest.json');
|
|
58
|
-
if (existsSync(manifestPath)) {
|
|
59
|
-
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);
|
|
60
67
|
if (manifest === null || manifest === void 0 ? void 0 : manifest.middleware) {
|
|
61
68
|
middleware = Object.keys(manifest.middleware).map((path) => path.slice(1));
|
|
62
69
|
}
|
|
63
70
|
}
|
|
64
|
-
const prerenderManifest = await readJson(join(netlifyConfig.build.publish, 'prerender-manifest.json'));
|
|
65
|
-
const { redirects, rewrites } = await readJson(join(netlifyConfig.build.publish, 'routes-manifest.json'));
|
|
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'));
|
|
66
73
|
const isrFiles = new Set();
|
|
74
|
+
const shortRevalidateRoutes = [];
|
|
67
75
|
Object.entries(prerenderManifest.routes).forEach(([route, { initialRevalidateSeconds }]) => {
|
|
68
76
|
if (initialRevalidateSeconds) {
|
|
69
77
|
// Find all files used by ISR routes
|
|
70
|
-
const trimmedPath = route.slice(1);
|
|
78
|
+
const trimmedPath = route === '/' ? 'index' : route.slice(1);
|
|
71
79
|
isrFiles.add(`${trimmedPath}.html`);
|
|
72
80
|
isrFiles.add(`${trimmedPath}.json`);
|
|
81
|
+
if (initialRevalidateSeconds < constants_1.MINIMUM_REVALIDATE_SECONDS) {
|
|
82
|
+
shortRevalidateRoutes.push({ Route: route, Revalidate: initialRevalidateSeconds });
|
|
83
|
+
}
|
|
73
84
|
}
|
|
74
85
|
});
|
|
75
86
|
const files = [];
|
|
76
87
|
const filesManifest = {};
|
|
77
88
|
const moveFile = async (file) => {
|
|
78
89
|
const isData = file.endsWith('.json');
|
|
79
|
-
const source = join(root, file);
|
|
80
|
-
const
|
|
90
|
+
const source = pathe_1.join(root, file);
|
|
91
|
+
const targetFile = isData ? pathe_1.join(dataDir, file) : file;
|
|
81
92
|
files.push(file);
|
|
82
|
-
filesManifest[file] =
|
|
83
|
-
const dest = join(netlifyConfig.build.publish,
|
|
93
|
+
filesManifest[file] = targetFile;
|
|
94
|
+
const dest = pathe_1.join(netlifyConfig.build.publish, targetFile);
|
|
84
95
|
try {
|
|
85
|
-
await move(source, dest);
|
|
96
|
+
await fs_extra_1.move(source, dest);
|
|
86
97
|
}
|
|
87
98
|
catch (error) {
|
|
88
99
|
console.warn('Error moving file', source, error);
|
|
89
100
|
}
|
|
90
101
|
};
|
|
91
102
|
// Move all static files, except error documents and nft manifests
|
|
92
|
-
const pages = await
|
|
103
|
+
const pages = await globby_1.default(['**/*.{html,json}', '!**/(500|404|*.js.nft).{html,json}'], {
|
|
93
104
|
cwd: root,
|
|
94
105
|
dot: true,
|
|
95
106
|
});
|
|
@@ -98,27 +109,27 @@ exports.moveStaticPages = async ({ netlifyConfig, target, i18n }) => {
|
|
|
98
109
|
const matchedRedirects = new Set();
|
|
99
110
|
const matchedRewrites = new Set();
|
|
100
111
|
// Limit concurrent file moves to number of cpus or 2 if there is only 1
|
|
101
|
-
const limit =
|
|
102
|
-
const promises = pages.map(
|
|
103
|
-
const 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);
|
|
104
115
|
// Don't move ISR files, as they're used for the first request
|
|
105
116
|
if (isrFiles.has(filePath)) {
|
|
106
117
|
return;
|
|
107
118
|
}
|
|
108
|
-
if (isDynamicRoute(filePath)) {
|
|
119
|
+
if (exports.isDynamicRoute(filePath)) {
|
|
109
120
|
return;
|
|
110
121
|
}
|
|
111
|
-
if (matchesRedirect(filePath, redirects)) {
|
|
122
|
+
if (exports.matchesRedirect(filePath, redirects)) {
|
|
112
123
|
matchedRedirects.add(filePath);
|
|
113
124
|
return;
|
|
114
125
|
}
|
|
115
|
-
if (matchesRewrite(filePath, rewrites)) {
|
|
126
|
+
if (exports.matchesRewrite(filePath, rewrites)) {
|
|
116
127
|
matchedRewrites.add(filePath);
|
|
117
128
|
return;
|
|
118
129
|
}
|
|
119
130
|
// Middleware matches against the unlocalised path
|
|
120
|
-
const unlocalizedPath = stripLocale(rawPath, i18n === null || i18n === void 0 ? void 0 : i18n.locales);
|
|
121
|
-
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);
|
|
122
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
|
|
123
134
|
if (middlewarePath) {
|
|
124
135
|
matchingMiddleware.add(middlewarePath);
|
|
@@ -130,81 +141,92 @@ exports.moveStaticPages = async ({ netlifyConfig, target, i18n }) => {
|
|
|
130
141
|
await Promise.all(promises);
|
|
131
142
|
console.log(`Moved ${files.length} files`);
|
|
132
143
|
if (matchedPages.size !== 0) {
|
|
133
|
-
console.log(yellowBright(outdent `
|
|
144
|
+
console.log(chalk_1.yellowBright(outdent_1.outdent `
|
|
134
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.
|
|
135
146
|
This is fine, but we're letting you know because it may not be what you expect.
|
|
136
147
|
`));
|
|
137
|
-
console.log(outdent `
|
|
148
|
+
console.log(outdent_1.outdent `
|
|
138
149
|
The following middleware matched statically-rendered pages:
|
|
139
150
|
|
|
140
|
-
${yellowBright([...matchingMiddleware].map((mid) => `- /${mid}/_middleware`).join('\n'))}
|
|
141
|
-
|
|
142
|
-
────────────────────────────────────────────────────────────────
|
|
151
|
+
${chalk_1.yellowBright([...matchingMiddleware].map((mid) => `- /${mid}/_middleware`).join('\n'))}
|
|
152
|
+
${constants_1.DIVIDER}
|
|
143
153
|
`);
|
|
144
154
|
// There could potentially be thousands of matching pages, so we don't want to spam the console with this
|
|
145
155
|
if (matchedPages.size < 50) {
|
|
146
|
-
console.log(outdent `
|
|
156
|
+
console.log(outdent_1.outdent `
|
|
147
157
|
The following files matched middleware and were not moved to the CDN:
|
|
148
158
|
|
|
149
|
-
${yellowBright([...matchedPages].map((mid) => `- ${mid}`).join('\n'))}
|
|
150
|
-
|
|
151
|
-
────────────────────────────────────────────────────────────────
|
|
159
|
+
${chalk_1.yellowBright([...matchedPages].map((mid) => `- ${mid}`).join('\n'))}
|
|
160
|
+
${constants_1.DIVIDER}
|
|
152
161
|
`);
|
|
153
162
|
}
|
|
154
163
|
}
|
|
155
164
|
if (matchedRedirects.size !== 0 || matchedRewrites.size !== 0) {
|
|
156
|
-
console.log(yellowBright(outdent `
|
|
165
|
+
console.log(chalk_1.yellowBright(outdent_1.outdent `
|
|
157
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.
|
|
158
167
|
`));
|
|
159
168
|
if (matchedRedirects.size < 50 && matchedRedirects.size !== 0) {
|
|
160
|
-
console.log(outdent `
|
|
169
|
+
console.log(outdent_1.outdent `
|
|
161
170
|
The following files matched redirects and were not moved to the CDN:
|
|
162
171
|
|
|
163
|
-
${yellowBright([...matchedRedirects].map((mid) => `- ${mid}`).join('\n'))}
|
|
164
|
-
|
|
165
|
-
────────────────────────────────────────────────────────────────
|
|
172
|
+
${chalk_1.yellowBright([...matchedRedirects].map((mid) => `- ${mid}`).join('\n'))}
|
|
173
|
+
${constants_1.DIVIDER}
|
|
166
174
|
`);
|
|
167
175
|
}
|
|
168
176
|
if (matchedRewrites.size < 50 && matchedRewrites.size !== 0) {
|
|
169
|
-
console.log(outdent `
|
|
177
|
+
console.log(outdent_1.outdent `
|
|
170
178
|
The following files matched beforeFiles rewrites and were not moved to the CDN:
|
|
171
179
|
|
|
172
|
-
${yellowBright([...matchedRewrites].map((mid) => `- ${mid}`).join('\n'))}
|
|
173
|
-
|
|
174
|
-
────────────────────────────────────────────────────────────────
|
|
180
|
+
${chalk_1.yellowBright([...matchedRewrites].map((mid) => `- ${mid}`).join('\n'))}
|
|
181
|
+
${constants_1.DIVIDER}
|
|
175
182
|
`);
|
|
176
183
|
}
|
|
177
184
|
}
|
|
178
185
|
// Write the manifest for use in the serverless functions
|
|
179
|
-
await writeJson(join(netlifyConfig.build.publish, 'static-manifest.json'), Object.entries(filesManifest));
|
|
186
|
+
await fs_extra_1.writeJson(pathe_1.join(netlifyConfig.build.publish, 'static-manifest.json'), Object.entries(filesManifest));
|
|
180
187
|
if (i18n === null || i18n === void 0 ? void 0 : i18n.defaultLocale) {
|
|
181
188
|
// Copy the default locale into the root
|
|
182
|
-
const defaultLocaleDir = join(netlifyConfig.build.publish, i18n.defaultLocale);
|
|
183
|
-
if (existsSync(defaultLocaleDir)) {
|
|
184
|
-
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}/`);
|
|
185
192
|
}
|
|
186
|
-
const defaultLocaleIndex = join(netlifyConfig.build.publish, `${i18n.defaultLocale}.html`);
|
|
187
|
-
const indexHtml = join(netlifyConfig.build.publish, 'index.html');
|
|
188
|
-
if (existsSync(defaultLocaleIndex) && !existsSync(indexHtml)) {
|
|
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)) {
|
|
189
196
|
try {
|
|
190
|
-
await copy(defaultLocaleIndex, indexHtml, { overwrite: false });
|
|
191
|
-
await copy(join(netlifyConfig.build.publish, `${i18n.defaultLocale}.json`), join(netlifyConfig.build.publish, 'index.json'), { overwrite: false });
|
|
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 });
|
|
192
199
|
}
|
|
193
200
|
catch { }
|
|
194
201
|
}
|
|
195
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
|
+
}
|
|
196
217
|
};
|
|
218
|
+
exports.moveStaticPages = moveStaticPages;
|
|
197
219
|
const patchFile = async ({ file, from, to }) => {
|
|
198
|
-
if (!existsSync(file)) {
|
|
220
|
+
if (!fs_extra_1.existsSync(file)) {
|
|
199
221
|
return;
|
|
200
222
|
}
|
|
201
|
-
const content = await readFile(file, 'utf8');
|
|
223
|
+
const content = await fs_extra_1.readFile(file, 'utf8');
|
|
202
224
|
if (content.includes(to)) {
|
|
203
225
|
return;
|
|
204
226
|
}
|
|
205
227
|
const newContent = content.replace(from, to);
|
|
206
|
-
await writeFile(`${file}.orig`, content);
|
|
207
|
-
await writeFile(file, newContent);
|
|
228
|
+
await fs_extra_1.writeFile(`${file}.orig`, content);
|
|
229
|
+
await fs_extra_1.writeFile(file, newContent);
|
|
208
230
|
};
|
|
209
231
|
const getServerFile = (root) => {
|
|
210
232
|
let serverFile;
|
|
@@ -225,7 +247,7 @@ const getServerFile = (root) => {
|
|
|
225
247
|
}
|
|
226
248
|
return serverFile;
|
|
227
249
|
};
|
|
228
|
-
|
|
250
|
+
const patchNextFiles = async (root) => {
|
|
229
251
|
const serverFile = getServerFile(root);
|
|
230
252
|
console.log(`Patching ${serverFile}`);
|
|
231
253
|
if (serverFile) {
|
|
@@ -236,23 +258,26 @@ exports.patchNextFiles = async (root) => {
|
|
|
236
258
|
});
|
|
237
259
|
}
|
|
238
260
|
};
|
|
239
|
-
exports.
|
|
261
|
+
exports.patchNextFiles = patchNextFiles;
|
|
262
|
+
const unpatchNextFiles = async (root) => {
|
|
240
263
|
const serverFile = getServerFile(root);
|
|
241
264
|
const origFile = `${serverFile}.orig`;
|
|
242
|
-
if (existsSync(origFile)) {
|
|
243
|
-
await move(origFile, serverFile, { overwrite: true });
|
|
265
|
+
if (fs_extra_1.existsSync(origFile)) {
|
|
266
|
+
await fs_extra_1.move(origFile, serverFile, { overwrite: true });
|
|
244
267
|
}
|
|
245
268
|
};
|
|
246
|
-
exports.
|
|
269
|
+
exports.unpatchNextFiles = unpatchNextFiles;
|
|
270
|
+
const movePublicFiles = async ({ appDir, outdir, publish, }) => {
|
|
247
271
|
// `outdir` is a config property added when using Next.js with Nx. It's typically
|
|
248
272
|
// a relative path outside of the appDir, e.g. '../../dist/apps/<app-name>', and
|
|
249
273
|
// the parent directory of the .next directory.
|
|
250
274
|
// If it exists, copy the files from the public folder there in order to include
|
|
251
275
|
// any files that were generated during the build. Otherwise, copy the public
|
|
252
276
|
// directory from the original app directory.
|
|
253
|
-
const publicDir = outdir ? join(appDir, outdir, 'public') : join(appDir, 'public');
|
|
254
|
-
if (existsSync(publicDir)) {
|
|
255
|
-
await copy(publicDir, `${publish}/`);
|
|
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}/`);
|
|
256
280
|
}
|
|
257
281
|
};
|
|
282
|
+
exports.movePublicFiles = movePublicFiles;
|
|
258
283
|
/* eslint-enable max-lines */
|
package/lib/helpers/functions.js
CHANGED
|
@@ -9,14 +9,14 @@ const getPageResolver_1 = require("../templates/getPageResolver");
|
|
|
9
9
|
const generateFunctions = async ({ FUNCTIONS_SRC = constants_1.DEFAULT_FUNCTIONS_SRC, INTERNAL_FUNCTIONS_SRC, PUBLISH_DIR }, appDir) => {
|
|
10
10
|
const functionsDir = INTERNAL_FUNCTIONS_SRC || FUNCTIONS_SRC;
|
|
11
11
|
const bridgeFile = require.resolve('@vercel/node/dist/bridge');
|
|
12
|
-
const functionDir =
|
|
13
|
-
const publishDir =
|
|
12
|
+
const functionDir = pathe_1.join(process.cwd(), functionsDir, constants_1.HANDLER_FUNCTION_NAME);
|
|
13
|
+
const publishDir = pathe_1.relative(functionDir, pathe_1.join(process.cwd(), PUBLISH_DIR));
|
|
14
14
|
const writeHandler = async (func, isODB) => {
|
|
15
|
-
const handlerSource = await
|
|
16
|
-
await
|
|
17
|
-
await
|
|
18
|
-
await
|
|
19
|
-
await
|
|
15
|
+
const handlerSource = await getHandler_1.getHandler({ isODB, publishDir, appDir: pathe_1.relative(functionDir, appDir) });
|
|
16
|
+
await fs_extra_1.ensureDir(pathe_1.join(functionsDir, func));
|
|
17
|
+
await fs_extra_1.writeFile(pathe_1.join(functionsDir, func, `${func}.js`), handlerSource);
|
|
18
|
+
await fs_extra_1.copyFile(bridgeFile, pathe_1.join(functionsDir, func, 'bridge.js'));
|
|
19
|
+
await fs_extra_1.copyFile(pathe_1.join(__dirname, '..', '..', 'lib', 'templates', 'handlerUtils.js'), pathe_1.join(functionsDir, func, 'handlerUtils.js'));
|
|
20
20
|
};
|
|
21
21
|
await writeHandler(constants_1.HANDLER_FUNCTION_NAME, false);
|
|
22
22
|
await writeHandler(constants_1.ODB_FUNCTION_NAME, true);
|
|
@@ -26,27 +26,27 @@ exports.generateFunctions = generateFunctions;
|
|
|
26
26
|
* Writes a file in each function directory that contains references to every page entrypoint.
|
|
27
27
|
* This is just so that the nft bundler knows about them. We'll eventually do this better.
|
|
28
28
|
*/
|
|
29
|
-
const generatePagesResolver = async ({ constants: { INTERNAL_FUNCTIONS_SRC, FUNCTIONS_SRC = constants_1.DEFAULT_FUNCTIONS_SRC },
|
|
29
|
+
const generatePagesResolver = async ({ constants: { INTERNAL_FUNCTIONS_SRC, FUNCTIONS_SRC = constants_1.DEFAULT_FUNCTIONS_SRC, PUBLISH_DIR }, target, }) => {
|
|
30
30
|
const functionsPath = INTERNAL_FUNCTIONS_SRC || FUNCTIONS_SRC;
|
|
31
|
-
const jsSource = await
|
|
32
|
-
|
|
31
|
+
const jsSource = await getPageResolver_1.getPageResolver({
|
|
32
|
+
publish: PUBLISH_DIR,
|
|
33
33
|
target,
|
|
34
34
|
});
|
|
35
|
-
await
|
|
36
|
-
await
|
|
35
|
+
await fs_extra_1.writeFile(pathe_1.join(functionsPath, constants_1.ODB_FUNCTION_NAME, 'pages.js'), jsSource);
|
|
36
|
+
await fs_extra_1.writeFile(pathe_1.join(functionsPath, constants_1.HANDLER_FUNCTION_NAME, 'pages.js'), jsSource);
|
|
37
37
|
};
|
|
38
38
|
exports.generatePagesResolver = generatePagesResolver;
|
|
39
39
|
// Move our next/image function into the correct functions directory
|
|
40
40
|
const setupImageFunction = async ({ constants: { INTERNAL_FUNCTIONS_SRC, FUNCTIONS_SRC = constants_1.DEFAULT_FUNCTIONS_SRC }, imageconfig = {}, netlifyConfig, basePath, }) => {
|
|
41
41
|
const functionsPath = INTERNAL_FUNCTIONS_SRC || FUNCTIONS_SRC;
|
|
42
42
|
const functionName = `${constants_1.IMAGE_FUNCTION_NAME}.js`;
|
|
43
|
-
const functionDirectory =
|
|
44
|
-
await
|
|
45
|
-
await
|
|
43
|
+
const functionDirectory = pathe_1.join(functionsPath, constants_1.IMAGE_FUNCTION_NAME);
|
|
44
|
+
await fs_extra_1.ensureDir(functionDirectory);
|
|
45
|
+
await fs_extra_1.writeJSON(pathe_1.join(functionDirectory, 'imageconfig.json'), {
|
|
46
46
|
...imageconfig,
|
|
47
47
|
basePath: [basePath, constants_1.IMAGE_FUNCTION_NAME].join('/'),
|
|
48
48
|
});
|
|
49
|
-
await
|
|
49
|
+
await fs_extra_1.copyFile(pathe_1.join(__dirname, '..', '..', 'lib', 'templates', 'ipx.js'), pathe_1.join(functionDirectory, functionName));
|
|
50
50
|
const imagePath = imageconfig.path || '/_next/image';
|
|
51
51
|
netlifyConfig.redirects.push({
|
|
52
52
|
from: `${imagePath}*`,
|
package/lib/helpers/redirects.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.generateRedirects = void 0;
|
|
4
|
-
const chalk_1 = require("chalk");
|
|
6
|
+
exports.generateRedirects = exports.generateStaticRedirects = void 0;
|
|
5
7
|
const fs_extra_1 = require("fs-extra");
|
|
6
|
-
const
|
|
8
|
+
const globby_1 = __importDefault(require("globby"));
|
|
7
9
|
const pathe_1 = require("pathe");
|
|
8
10
|
const constants_1 = require("../constants");
|
|
9
11
|
const utils_1 = require("./utils");
|
|
@@ -35,8 +37,16 @@ const generateLocaleRedirects = ({ i18n, basePath, trailingSlash, }) => {
|
|
|
35
37
|
});
|
|
36
38
|
return redirects;
|
|
37
39
|
};
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
+
const generateStaticRedirects = ({ netlifyConfig, nextConfig: { i18n, basePath }, }) => {
|
|
41
|
+
// Static files are in `static`
|
|
42
|
+
netlifyConfig.redirects.push({ from: `${basePath}/_next/static/*`, to: `/static/:splat`, status: 200 });
|
|
43
|
+
if (i18n) {
|
|
44
|
+
netlifyConfig.redirects.push({ from: `${basePath}/:locale/_next/static/*`, to: `/static/:splat`, status: 200 });
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
exports.generateStaticRedirects = generateStaticRedirects;
|
|
48
|
+
const generateRedirects = async ({ netlifyConfig, nextConfig: { i18n, basePath, trailingSlash, appDir }, }) => {
|
|
49
|
+
const { dynamicRoutes, routes: staticRoutes } = await fs_extra_1.readJSON(pathe_1.join(netlifyConfig.build.publish, 'prerender-manifest.json'));
|
|
40
50
|
netlifyConfig.redirects.push(...constants_1.HIDDEN_PATHS.map((path) => ({
|
|
41
51
|
from: `${basePath}${path}`,
|
|
42
52
|
to: '/404.html',
|
|
@@ -49,7 +59,6 @@ const generateRedirects = async ({ netlifyConfig, nextConfig: { i18n, basePath,
|
|
|
49
59
|
const dataRedirects = [];
|
|
50
60
|
const pageRedirects = [];
|
|
51
61
|
const isrRedirects = [];
|
|
52
|
-
let hasIsr = false;
|
|
53
62
|
const dynamicRouteEntries = Object.entries(dynamicRoutes);
|
|
54
63
|
const staticRouteEntries = Object.entries(staticRoutes);
|
|
55
64
|
staticRouteEntries.forEach(([route, { dataRoute, initialRevalidateSeconds }]) => {
|
|
@@ -61,24 +70,19 @@ const generateRedirects = async ({ netlifyConfig, nextConfig: { i18n, basePath,
|
|
|
61
70
|
if ((i18n === null || i18n === void 0 ? void 0 : i18n.defaultLocale) && route.startsWith(`/${i18n.defaultLocale}/`)) {
|
|
62
71
|
route = route.slice(i18n.defaultLocale.length + 1);
|
|
63
72
|
}
|
|
64
|
-
|
|
65
|
-
isrRedirects.push(...(0, utils_1.netlifyRoutesForNextRoute)(dataRoute), ...(0, utils_1.netlifyRoutesForNextRoute)(route));
|
|
73
|
+
isrRedirects.push(...utils_1.netlifyRoutesForNextRoute(dataRoute), ...utils_1.netlifyRoutesForNextRoute(route));
|
|
66
74
|
});
|
|
67
75
|
dynamicRouteEntries.forEach(([route, { dataRoute, fallback }]) => {
|
|
68
76
|
// Add redirects if fallback is "null" (aka blocking) or true/a string
|
|
69
77
|
if (fallback === false) {
|
|
70
78
|
return;
|
|
71
79
|
}
|
|
72
|
-
pageRedirects.push(...
|
|
73
|
-
dataRedirects.push(...
|
|
80
|
+
pageRedirects.push(...utils_1.netlifyRoutesForNextRoute(route));
|
|
81
|
+
dataRedirects.push(...utils_1.netlifyRoutesForNextRoute(dataRoute));
|
|
74
82
|
});
|
|
75
|
-
|
|
76
|
-
netlifyConfig.redirects.push({ from: `${basePath}/:locale/_next/static/*`, to: `/static/:splat`, status: 200 });
|
|
77
|
-
}
|
|
83
|
+
const publicFiles = await globby_1.default('**/*', { cwd: pathe_1.join(appDir, 'public') });
|
|
78
84
|
// This is only used in prod, so dev uses `next dev` directly
|
|
79
85
|
netlifyConfig.redirects.push(
|
|
80
|
-
// Static files are in `static`
|
|
81
|
-
{ from: `${basePath}/_next/static/*`, to: `/static/:splat`, status: 200 },
|
|
82
86
|
// API routes always need to be served from the regular function
|
|
83
87
|
{
|
|
84
88
|
from: `${basePath}/api`,
|
|
@@ -89,20 +93,24 @@ const generateRedirects = async ({ netlifyConfig, nextConfig: { i18n, basePath,
|
|
|
89
93
|
to: constants_1.HANDLER_FUNCTION_PATH,
|
|
90
94
|
status: 200,
|
|
91
95
|
},
|
|
92
|
-
// Preview mode gets forced to the function, to
|
|
93
|
-
{
|
|
96
|
+
// Preview mode gets forced to the function, to bypass pre-rendered pages, but static files need to be skipped
|
|
97
|
+
...publicFiles.map((file) => ({
|
|
98
|
+
from: `${basePath}/${file}`,
|
|
99
|
+
// This is a no-op, but we do it to stop it matching the following rule
|
|
100
|
+
to: `${basePath}/${file}`,
|
|
101
|
+
conditions: { Cookie: ['__prerender_bypass', '__next_preview_data'] },
|
|
102
|
+
status: 200,
|
|
103
|
+
})), {
|
|
94
104
|
from: `${basePath}/*`,
|
|
95
105
|
to: constants_1.HANDLER_FUNCTION_PATH,
|
|
96
106
|
status: 200,
|
|
97
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
98
|
-
// @ts-ignore The conditions type is incorrect
|
|
99
107
|
conditions: { Cookie: ['__prerender_bypass', '__next_preview_data'] },
|
|
100
108
|
force: true,
|
|
101
109
|
},
|
|
102
110
|
// ISR redirects are handled by the regular function. Forced to avoid pre-rendered pages
|
|
103
111
|
...isrRedirects.map((redirect) => ({
|
|
104
112
|
from: `${basePath}${redirect}`,
|
|
105
|
-
to:
|
|
113
|
+
to: constants_1.ODB_FUNCTION_PATH,
|
|
106
114
|
status: 200,
|
|
107
115
|
force: true,
|
|
108
116
|
})),
|
|
@@ -121,10 +129,5 @@ const generateRedirects = async ({ netlifyConfig, nextConfig: { i18n, basePath,
|
|
|
121
129
|
})),
|
|
122
130
|
// Everything else is handled by the regular function
|
|
123
131
|
{ from: `${basePath}/*`, to: constants_1.HANDLER_FUNCTION_PATH, status: 200 });
|
|
124
|
-
if (hasIsr) {
|
|
125
|
-
console.log((0, chalk_1.yellowBright)((0, outdent_1.outdent) `
|
|
126
|
-
You have some pages that use ISR (pages that use getStaticProps with revalidate set), which is not currently fully-supported by this plugin. Be aware that results may be unreliable.
|
|
127
|
-
`));
|
|
128
|
-
}
|
|
129
132
|
};
|
|
130
133
|
exports.generateRedirects = generateRedirects;
|
package/lib/helpers/utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.netlifyRoutesForNextRoute = void 0;
|
|
3
|
+
exports.shouldSkip = exports.netlifyRoutesForNextRoute = void 0;
|
|
4
4
|
const constants_1 = require("../constants");
|
|
5
5
|
const netlifyRoutesForNextRoute = (nextRoute) => {
|
|
6
6
|
const netlifyRoutes = [nextRoute];
|
|
@@ -30,3 +30,8 @@ const netlifyRoutesForNextRoute = (nextRoute) => {
|
|
|
30
30
|
.replace(constants_1.DYNAMIC_PARAMETER_REGEX, '/:$1'));
|
|
31
31
|
};
|
|
32
32
|
exports.netlifyRoutesForNextRoute = netlifyRoutesForNextRoute;
|
|
33
|
+
const shouldSkip = () => process.env.NEXT_PLUGIN_FORCE_RUN === 'false' ||
|
|
34
|
+
process.env.NEXT_PLUGIN_FORCE_RUN === '0' ||
|
|
35
|
+
process.env.NETLIFY_NEXT_PLUGIN_SKIP === 'true' ||
|
|
36
|
+
process.env.NETLIFY_NEXT_PLUGIN_SKIP === '1';
|
|
37
|
+
exports.shouldSkip = shouldSkip;
|
|
@@ -1,94 +1,156 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
5
|
+
}) : (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
o[k2] = m[k];
|
|
8
|
+
}));
|
|
9
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
10
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
11
|
+
}) : function(o, v) {
|
|
12
|
+
o["default"] = v;
|
|
13
|
+
});
|
|
14
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
15
|
+
if (mod && mod.__esModule) return mod;
|
|
16
|
+
var result = {};
|
|
17
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
18
|
+
__setModuleDefault(result, mod);
|
|
19
|
+
return result;
|
|
20
|
+
};
|
|
21
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
22
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
23
|
+
};
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.warnForProblematicUserRewrites = exports.getProblematicUserRewrites = exports.checkZipSize = exports.checkForRootPublish = exports.checkNextSiteHasBuilt = exports.checkForOldFunctions = exports.verifyNetlifyBuildVersion = void 0;
|
|
26
|
+
/* eslint-disable max-lines */
|
|
27
|
+
const fs_1 = require("fs");
|
|
28
|
+
const path_1 = __importStar(require("path"));
|
|
29
|
+
const chalk_1 = require("chalk");
|
|
30
|
+
const node_stream_zip_1 = require("node-stream-zip");
|
|
31
|
+
const outdent_1 = require("outdent");
|
|
32
|
+
const pretty_bytes_1 = __importDefault(require("pretty-bytes"));
|
|
33
|
+
const semver_1 = require("semver");
|
|
34
|
+
const constants_1 = require("../constants");
|
|
9
35
|
// This is when nft support was added
|
|
10
36
|
const REQUIRED_BUILD_VERSION = '>=18.16.0';
|
|
11
|
-
|
|
37
|
+
const verifyNetlifyBuildVersion = ({ IS_LOCAL, NETLIFY_BUILD_VERSION, failBuild, }) => {
|
|
12
38
|
// We check for build version because that's what's available to us, but prompt about the cli because that's what they can upgrade
|
|
13
|
-
if (IS_LOCAL && !satisfies(NETLIFY_BUILD_VERSION, REQUIRED_BUILD_VERSION, { includePrerelease: true })) {
|
|
14
|
-
return failBuild(outdent `
|
|
39
|
+
if (IS_LOCAL && !semver_1.satisfies(NETLIFY_BUILD_VERSION, REQUIRED_BUILD_VERSION, { includePrerelease: true })) {
|
|
40
|
+
return failBuild(outdent_1.outdent `
|
|
15
41
|
This version of the Essential Next.js plugin requires netlify-cli@6.12.4 or higher. Please upgrade and try again.
|
|
16
42
|
You can do this by running: "npm install -g netlify-cli@latest" or "yarn global add netlify-cli@latest"
|
|
17
43
|
`);
|
|
18
44
|
}
|
|
19
45
|
};
|
|
20
|
-
exports.
|
|
46
|
+
exports.verifyNetlifyBuildVersion = verifyNetlifyBuildVersion;
|
|
47
|
+
const checkForOldFunctions = async ({ functions }) => {
|
|
21
48
|
const allOldFunctions = await functions.list();
|
|
22
49
|
const oldFunctions = allOldFunctions.filter(({ name }) => name.startsWith('next_'));
|
|
23
50
|
if (oldFunctions.length !== 0) {
|
|
24
|
-
console.log(yellowBright(outdent `
|
|
51
|
+
console.log(chalk_1.yellowBright(outdent_1.outdent `
|
|
25
52
|
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_".
|
|
26
53
|
|
|
27
|
-
${reset(oldFunctions.map(({ name }) => `- ${name}`).join('\n'))}
|
|
54
|
+
${chalk_1.reset(oldFunctions.map(({ name }) => `- ${name}`).join('\n'))}
|
|
28
55
|
|
|
29
56
|
If they were created by the old plugin, these functions are likely to cause errors so should be removed. You can do this by deleting the following directories:
|
|
30
57
|
|
|
31
|
-
${reset(oldFunctions.map(({ mainFile }) => `- ${
|
|
58
|
+
${chalk_1.reset(oldFunctions.map(({ mainFile }) => `- ${path_1.default.relative(process.cwd(), path_1.default.dirname(mainFile))}`).join('\n'))}
|
|
32
59
|
`));
|
|
33
60
|
}
|
|
34
61
|
};
|
|
35
|
-
exports.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
62
|
+
exports.checkForOldFunctions = checkForOldFunctions;
|
|
63
|
+
const checkNextSiteHasBuilt = ({ publish, failBuild, }) => {
|
|
64
|
+
if (!fs_1.existsSync(path_1.default.join(publish, 'BUILD_ID'))) {
|
|
65
|
+
const outWarning = path_1.default.basename(publish) === 'out'
|
|
66
|
+
? `Your publish directory is set to "out", but in most cases it should be ".next".`
|
|
67
|
+
: `In most cases it should be set to ".next", unless you have chosen a custom "distDir" in your Next config.`;
|
|
68
|
+
return failBuild(outdent_1.outdent `
|
|
69
|
+
The directory "${path_1.default.relative(process.cwd(), publish)}" does not contain a Next.js production build. Perhaps the build command was not run, or you specified the wrong publish directory.
|
|
70
|
+
${outWarning}
|
|
71
|
+
If you are using "next export" then you should set the environment variable NETLIFY_NEXT_PLUGIN_SKIP to "true".
|
|
41
72
|
`);
|
|
42
73
|
}
|
|
43
|
-
if (existsSync(
|
|
44
|
-
failBuild(outdent `
|
|
74
|
+
if (fs_1.existsSync(path_1.default.join(publish, 'export-detail.json'))) {
|
|
75
|
+
failBuild(outdent_1.outdent `
|
|
45
76
|
Detected that "next export" was run, but site is incorrectly publishing the ".next" directory.
|
|
46
|
-
|
|
47
|
-
See https://ntl.fyi/remove-plugin for more details on how to remove this plugin.
|
|
77
|
+
The publish directory should be set to "out", and you should set the environment variable NETLIFY_NEXT_PLUGIN_SKIP to "true".
|
|
48
78
|
`);
|
|
49
79
|
}
|
|
50
80
|
};
|
|
51
|
-
exports.
|
|
52
|
-
|
|
53
|
-
|
|
81
|
+
exports.checkNextSiteHasBuilt = checkNextSiteHasBuilt;
|
|
82
|
+
const checkForRootPublish = ({ publish, failBuild, }) => {
|
|
83
|
+
if (path_1.default.resolve(publish) === path_1.default.resolve('.')) {
|
|
84
|
+
failBuild(outdent_1.outdent `
|
|
54
85
|
Your publish directory is pointing to the base directory of your site. This is not supported for Next.js sites, and is probably a mistake.
|
|
55
86
|
In most cases it should be set to ".next", unless you have chosen a custom "distDir" in your Next config, or the Next site is in a subdirectory.
|
|
56
87
|
`);
|
|
57
88
|
}
|
|
58
89
|
};
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
if (!existsSync(file)) {
|
|
90
|
+
exports.checkForRootPublish = checkForRootPublish;
|
|
91
|
+
const checkZipSize = async (file, maxSize = constants_1.LAMBDA_MAX_SIZE) => {
|
|
92
|
+
if (!fs_1.existsSync(file)) {
|
|
63
93
|
console.warn(`Could not check zip size because ${file} does not exist`);
|
|
64
94
|
return;
|
|
65
95
|
}
|
|
66
|
-
const
|
|
67
|
-
if (
|
|
96
|
+
const fileSize = await fs_1.promises.stat(file).then(({ size }) => size);
|
|
97
|
+
if (fileSize < maxSize) {
|
|
68
98
|
return;
|
|
69
99
|
}
|
|
70
100
|
// We don't fail the build, because the actual hard max size is larger so it might still succeed
|
|
71
|
-
console.log(redBright(outdent `
|
|
72
|
-
The function zip ${yellowBright(relative(process.cwd(), file))} size is ${
|
|
101
|
+
console.log(chalk_1.redBright(outdent_1.outdent `
|
|
102
|
+
The function zip ${chalk_1.yellowBright(path_1.relative(process.cwd(), file))} size is ${pretty_bytes_1.default(fileSize)}, which is larger than the maximum supported size of ${pretty_bytes_1.default(maxSize)}.
|
|
73
103
|
There are a few reasons this could happen. You may have accidentally bundled a large dependency, or you might have a
|
|
74
104
|
large number of pre-rendered pages included.
|
|
75
105
|
`));
|
|
76
|
-
const zip = new
|
|
106
|
+
const zip = new node_stream_zip_1.async({ file });
|
|
77
107
|
console.log(`Contains ${await zip.entriesCount} files`);
|
|
78
108
|
const sortedFiles = Object.values(await zip.entries()).sort((a, b) => b.size - a.size);
|
|
79
109
|
const largest = {};
|
|
80
110
|
for (let i = 0; i < 10 && i < sortedFiles.length; i++) {
|
|
81
111
|
largest[`${i + 1}`] = {
|
|
82
112
|
File: sortedFiles[i].name,
|
|
83
|
-
'Compressed Size':
|
|
84
|
-
'Uncompressed Size':
|
|
113
|
+
'Compressed Size': pretty_bytes_1.default(sortedFiles[i].compressedSize),
|
|
114
|
+
'Uncompressed Size': pretty_bytes_1.default(sortedFiles[i].size),
|
|
85
115
|
};
|
|
86
116
|
}
|
|
87
|
-
console.log(yellowBright `\n\nThese are the largest files in the zip:`);
|
|
117
|
+
console.log(chalk_1.yellowBright `\n\nThese are the largest files in the zip:`);
|
|
88
118
|
console.table(largest);
|
|
89
|
-
console.log(greenBright `\n\nFor more information on fixing this, see ${blueBright `https://ntl.fyi/large-next-functions`}`);
|
|
119
|
+
console.log(chalk_1.greenBright `\n\nFor more information on fixing this, see ${chalk_1.blueBright `https://ntl.fyi/large-next-functions`}`);
|
|
90
120
|
};
|
|
91
|
-
exports.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
121
|
+
exports.checkZipSize = checkZipSize;
|
|
122
|
+
const getProblematicUserRewrites = ({ redirects, basePath, }) => {
|
|
123
|
+
const userRewrites = [];
|
|
124
|
+
for (const redirect of redirects) {
|
|
125
|
+
// This is the first of the plugin-generated redirects so we can stop checking
|
|
126
|
+
if (redirect.from === `${basePath}/_next/static/*` && redirect.to === `/static/:splat` && redirect.status === 200) {
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
if (
|
|
130
|
+
// Redirects are fine
|
|
131
|
+
(redirect.status === 200 || redirect.status === 404) &&
|
|
132
|
+
// Rewriting to a function is also fine
|
|
133
|
+
!redirect.to.startsWith('/.netlify/') &&
|
|
134
|
+
// ...so is proxying
|
|
135
|
+
!redirect.to.startsWith('http')) {
|
|
136
|
+
userRewrites.push(redirect);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return userRewrites;
|
|
140
|
+
};
|
|
141
|
+
exports.getProblematicUserRewrites = getProblematicUserRewrites;
|
|
142
|
+
const warnForProblematicUserRewrites = ({ redirects, basePath, }) => {
|
|
143
|
+
const userRewrites = exports.getProblematicUserRewrites({ redirects, basePath });
|
|
144
|
+
if (userRewrites.length === 0) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
console.log(chalk_1.yellowBright(outdent_1.outdent `
|
|
148
|
+
You have the following Netlify rewrite${userRewrites.length === 1 ? '' : 's'} that might cause conflicts with the Next.js plugin:
|
|
149
|
+
|
|
150
|
+
${chalk_1.reset(userRewrites.map(({ from, to, status }) => `- ${from} ${to} ${status}`).join('\n'))}
|
|
151
|
+
|
|
152
|
+
For more information, see https://ntl.fyi/next-rewrites
|
|
153
|
+
`));
|
|
154
|
+
};
|
|
155
|
+
exports.warnForProblematicUserRewrites = warnForProblematicUserRewrites;
|
|
156
|
+
/* eslint-enable max-lines */
|
package/lib/index.js
CHANGED
|
@@ -1,58 +1,83 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const path_1 = require("path");
|
|
4
|
+
const fs_extra_1 = require("fs-extra");
|
|
5
|
+
const constants_1 = require("./constants");
|
|
6
|
+
const cache_1 = require("./helpers/cache");
|
|
7
|
+
const config_1 = require("./helpers/config");
|
|
8
|
+
const files_1 = require("./helpers/files");
|
|
9
|
+
const functions_1 = require("./helpers/functions");
|
|
10
|
+
const redirects_1 = require("./helpers/redirects");
|
|
11
|
+
const utils_1 = require("./helpers/utils");
|
|
12
|
+
const verification_1 = require("./helpers/verification");
|
|
13
|
+
const plugin = {
|
|
11
14
|
async onPreBuild({ constants, netlifyConfig, utils: { build: { failBuild }, cache, }, }) {
|
|
12
15
|
var _a;
|
|
13
|
-
logBetaMessage();
|
|
14
16
|
const { publish } = netlifyConfig.build;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
if (utils_1.shouldSkip()) {
|
|
18
|
+
await cache_1.restoreCache({ cache, publish });
|
|
19
|
+
console.log('Not running Essential Next.js plugin');
|
|
20
|
+
if (fs_extra_1.existsSync(path_1.join(constants.INTERNAL_FUNCTIONS_SRC, constants_1.HANDLER_FUNCTION_NAME))) {
|
|
21
|
+
console.log(`Please ensure you remove any generated functions from ${constants.INTERNAL_FUNCTIONS_SRC}`);
|
|
22
|
+
}
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
verification_1.checkForRootPublish({ publish, failBuild });
|
|
26
|
+
verification_1.verifyNetlifyBuildVersion({ failBuild, ...constants });
|
|
27
|
+
await cache_1.restoreCache({ cache, publish });
|
|
18
28
|
(_a = netlifyConfig.build).environment || (_a.environment = {});
|
|
19
29
|
// eslint-disable-next-line unicorn/consistent-destructuring
|
|
20
30
|
netlifyConfig.build.environment.NEXT_PRIVATE_TARGET = 'server';
|
|
21
31
|
},
|
|
22
32
|
async onBuild({ constants, netlifyConfig, utils: { build: { failBuild }, }, }) {
|
|
33
|
+
if (utils_1.shouldSkip()) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
23
36
|
const { publish } = netlifyConfig.build;
|
|
24
|
-
checkNextSiteHasBuilt({ publish, failBuild });
|
|
25
|
-
const { appDir, basePath, i18n, images, target, ignore, trailingSlash, outdir } = await getNextConfig({
|
|
37
|
+
verification_1.checkNextSiteHasBuilt({ publish, failBuild });
|
|
38
|
+
const { appDir, basePath, i18n, images, target, ignore, trailingSlash, outdir } = await config_1.getNextConfig({
|
|
26
39
|
publish,
|
|
27
40
|
failBuild,
|
|
28
41
|
});
|
|
29
|
-
configureHandlerFunctions({ netlifyConfig, ignore, publish: relative(process.cwd(), publish) });
|
|
30
|
-
await generateFunctions(constants, appDir);
|
|
31
|
-
await generatePagesResolver({
|
|
32
|
-
await movePublicFiles({ appDir, outdir, publish });
|
|
42
|
+
config_1.configureHandlerFunctions({ netlifyConfig, ignore, publish: path_1.relative(process.cwd(), publish) });
|
|
43
|
+
await functions_1.generateFunctions(constants, appDir);
|
|
44
|
+
await functions_1.generatePagesResolver({ target, constants });
|
|
45
|
+
await files_1.movePublicFiles({ appDir, outdir, publish });
|
|
33
46
|
if (process.env.EXPERIMENTAL_ODB_TTL) {
|
|
34
|
-
await patchNextFiles(basePath);
|
|
47
|
+
await files_1.patchNextFiles(basePath);
|
|
35
48
|
}
|
|
36
49
|
if (process.env.EXPERIMENTAL_MOVE_STATIC_PAGES) {
|
|
37
50
|
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'");
|
|
38
51
|
}
|
|
39
52
|
if (!process.env.SERVE_STATIC_FILES_FROM_ORIGIN) {
|
|
40
|
-
await moveStaticPages({ target,
|
|
53
|
+
await files_1.moveStaticPages({ target, netlifyConfig, i18n });
|
|
41
54
|
}
|
|
42
|
-
await
|
|
43
|
-
await generateRedirects({
|
|
55
|
+
await redirects_1.generateStaticRedirects({
|
|
44
56
|
netlifyConfig,
|
|
45
|
-
nextConfig: { basePath, i18n
|
|
57
|
+
nextConfig: { basePath, i18n },
|
|
58
|
+
});
|
|
59
|
+
await functions_1.setupImageFunction({ constants, imageconfig: images, netlifyConfig, basePath });
|
|
60
|
+
await redirects_1.generateRedirects({
|
|
61
|
+
netlifyConfig,
|
|
62
|
+
nextConfig: { basePath, i18n, trailingSlash, appDir },
|
|
46
63
|
});
|
|
47
64
|
},
|
|
48
|
-
async onPostBuild({ netlifyConfig, utils: { cache, functions, failBuild }, constants: { FUNCTIONS_DIST } }) {
|
|
49
|
-
await saveCache({ cache, publish
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
65
|
+
async onPostBuild({ netlifyConfig: { build: { publish }, redirects, }, utils: { status, cache, functions, build: { failBuild }, }, constants: { FUNCTIONS_DIST }, }) {
|
|
66
|
+
await cache_1.saveCache({ cache, publish });
|
|
67
|
+
if (utils_1.shouldSkip()) {
|
|
68
|
+
status.show({
|
|
69
|
+
title: 'Essential Next.js plugin did not run',
|
|
70
|
+
summary: `Next cache was stored, but all other functions were skipped because ${process.env.NETLIFY_NEXT_PLUGIN_SKIP
|
|
71
|
+
? `NETLIFY_NEXT_PLUGIN_SKIP is set`
|
|
72
|
+
: `NEXT_PLUGIN_FORCE_RUN is set to ${process.env.NEXT_PLUGIN_FORCE_RUN}`}`,
|
|
73
|
+
});
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
await verification_1.checkForOldFunctions({ functions });
|
|
77
|
+
await verification_1.checkZipSize(path_1.join(FUNCTIONS_DIST, `${constants_1.ODB_FUNCTION_NAME}.zip`));
|
|
78
|
+
const { basePath } = await config_1.getNextConfig({ publish, failBuild });
|
|
79
|
+
verification_1.warnForProblematicUserRewrites({ basePath, redirects });
|
|
80
|
+
await files_1.unpatchNextFiles(basePath);
|
|
57
81
|
},
|
|
58
82
|
};
|
|
83
|
+
module.exports = plugin;
|
|
@@ -48,7 +48,7 @@ const makeHandler = () =>
|
|
|
48
48
|
var _a, _b, _c;
|
|
49
49
|
let requestMode = mode;
|
|
50
50
|
// Ensure that paths are encoded - but don't double-encode them
|
|
51
|
-
event.path = new URL(event.
|
|
51
|
+
event.path = new URL(event.rawUrl).pathname;
|
|
52
52
|
// Next expects to be able to parse the query from the URL
|
|
53
53
|
const query = new URLSearchParams(event.queryStringParameters).toString();
|
|
54
54
|
event.path = query ? `${event.path}?${query}` : event.path;
|
|
@@ -68,7 +68,7 @@ const makeHandler = () =>
|
|
|
68
68
|
// Sending SWR headers causes undefined behaviour with the Netlify CDN
|
|
69
69
|
const cacheHeader = (_c = multiValueHeaders['cache-control']) === null || _c === void 0 ? void 0 : _c[0];
|
|
70
70
|
if (cacheHeader === null || cacheHeader === void 0 ? void 0 : cacheHeader.includes('stale-while-revalidate')) {
|
|
71
|
-
if (requestMode === 'odb'
|
|
71
|
+
if (requestMode === 'odb') {
|
|
72
72
|
requestMode = 'isr';
|
|
73
73
|
const ttl = getMaxAge(cacheHeader);
|
|
74
74
|
// Long-expiry TTL is basically no TTL
|
|
@@ -11,17 +11,17 @@ const tiny_glob_1 = __importDefault(require("tiny-glob"));
|
|
|
11
11
|
const constants_1 = require("../constants");
|
|
12
12
|
// Generate a file full of require.resolve() calls for all the pages in the
|
|
13
13
|
// build. This is used by the nft bundler to find all the pages.
|
|
14
|
-
const getPageResolver = async ({
|
|
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.
|
|
17
|
-
const pages = await
|
|
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', {
|
|
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,
|
|
22
|
+
.map((page) => `require.resolve('${path_1.posix.relative(functionDir, path_1.posix.join(root, slash_1.default(page)))}')`)
|
|
23
23
|
.sort();
|
|
24
|
-
return
|
|
24
|
+
return 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,7 @@ 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 =
|
|
14
|
+
const streamPipeline = util_1.promisify(stream_1.pipeline);
|
|
15
15
|
const downloadFile = async (url, destination) => {
|
|
16
16
|
console.log(`Downloading ${url} to ${destination}`);
|
|
17
17
|
const httpx = url.startsWith('https') ? https_1.default : http_1.default;
|
|
@@ -21,7 +21,7 @@ const downloadFile = async (url, destination) => {
|
|
|
21
21
|
reject(new Error(`Failed to download ${url}: ${response.statusCode} ${response.statusMessage || ''}`));
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
|
-
const fileStream =
|
|
24
|
+
const fileStream = fs_1.createWriteStream(destination);
|
|
25
25
|
streamPipeline(response, fileStream)
|
|
26
26
|
.then(resolve)
|
|
27
27
|
.catch((error) => {
|
|
@@ -78,7 +78,7 @@ const augmentFsModule = ({ promises, staticManifest, pageRoot, getBase, }) => {
|
|
|
78
78
|
const staticFiles = new Map(staticManifest);
|
|
79
79
|
const downloadPromises = new Map();
|
|
80
80
|
// Yes, you can cache stuff locally in a Lambda
|
|
81
|
-
const cacheDir = path_1.default.join(
|
|
81
|
+
const cacheDir = path_1.default.join(os_1.tmpdir(), 'next-static-cache');
|
|
82
82
|
// Grab the real fs.promises.readFile...
|
|
83
83
|
const readfileOrig = promises.readFile;
|
|
84
84
|
const statsOrig = promises.stat;
|
|
@@ -90,7 +90,7 @@ const augmentFsModule = ({ promises, staticManifest, pageRoot, getBase, }) => {
|
|
|
90
90
|
// We only want the part after `pages/`
|
|
91
91
|
const filePath = file.slice(pageRoot.length + 1);
|
|
92
92
|
// Is it in the CDN and not local?
|
|
93
|
-
if (staticFiles.has(filePath) && !
|
|
93
|
+
if (staticFiles.has(filePath) && !fs_1.existsSync(file)) {
|
|
94
94
|
// This name is safe to use, because it's one that was already created by Next
|
|
95
95
|
const cacheFile = path_1.default.join(cacheDir, filePath);
|
|
96
96
|
const url = `${base}/${staticFiles.get(filePath)}`;
|
|
@@ -99,11 +99,11 @@ const augmentFsModule = ({ promises, staticManifest, pageRoot, getBase, }) => {
|
|
|
99
99
|
await downloadPromises.get(url);
|
|
100
100
|
}
|
|
101
101
|
// Have we already cached it? We download every time if running locally to avoid staleness
|
|
102
|
-
if ((!
|
|
102
|
+
if ((!fs_1.existsSync(cacheFile) || process.env.NETLIFY_DEV) && base) {
|
|
103
103
|
await promises.mkdir(path_1.default.dirname(cacheFile), { recursive: true });
|
|
104
104
|
try {
|
|
105
105
|
// Append the path to our host and we can load it like a regular page
|
|
106
|
-
const downloadPromise =
|
|
106
|
+
const downloadPromise = exports.downloadFile(url, cacheFile);
|
|
107
107
|
downloadPromises.set(url, downloadPromise);
|
|
108
108
|
await downloadPromise;
|
|
109
109
|
}
|
|
@@ -117,12 +117,12 @@ const augmentFsModule = ({ promises, staticManifest, pageRoot, getBase, }) => {
|
|
|
117
117
|
}
|
|
118
118
|
return readfileOrig(file, options);
|
|
119
119
|
});
|
|
120
|
-
promises.stat = (
|
|
120
|
+
promises.stat = ((file, options) => {
|
|
121
121
|
// We only care about page files
|
|
122
122
|
if (file.startsWith(pageRoot)) {
|
|
123
123
|
// We only want the part after `pages/`
|
|
124
124
|
const cacheFile = path_1.default.join(cacheDir, file.slice(pageRoot.length + 1));
|
|
125
|
-
if (
|
|
125
|
+
if (fs_1.existsSync(cacheFile)) {
|
|
126
126
|
return statsOrig(cacheFile, options);
|
|
127
127
|
}
|
|
128
128
|
}
|
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 =
|
|
8
|
+
exports.handler = 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.1.0",
|
|
4
4
|
"description": "Run Next.js seamlessly on Netlify",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"files": [
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
"scripts": {
|
|
11
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
13
|
"dev:demo": "next dev demos/default",
|
|
15
14
|
"format": "run-s format:check-fix:*",
|
|
16
15
|
"format:ci": "run-s format:check:*",
|
|
@@ -34,8 +33,8 @@
|
|
|
34
33
|
"watch": "tsc --watch"
|
|
35
34
|
},
|
|
36
35
|
"config": {
|
|
37
|
-
"eslint": "--cache --format=codeframe --max-warnings=0 \"{src,scripts,tests,.github}/**/*.{js,md,html}\" \"*.{js,md,html}\" \".*.{js,md,html}\"",
|
|
38
|
-
"prettier": "--loglevel=warn \"{src,scripts,tests,.github}/**/*.{js,md,yml,json,html}\" \"*.{js,yml,json,html}\" \".*.{js,yml,json,html}\" \"!package-lock.json\""
|
|
36
|
+
"eslint": "--cache --format=codeframe --max-warnings=0 \"{src,scripts,tests,.github}/**/*.{ts,js,md,html}\" \"*.{ts,js,md,html}\" \".*.{ts,js,md,html}\"",
|
|
37
|
+
"prettier": "--loglevel=warn \"{src,scripts,tests,.github}/**/*.{ts,js,md,yml,json,html}\" \"*.{ts,js,yml,json,html}\" \".*.{ts,js,yml,json,html}\" \"!package-lock.json\""
|
|
39
38
|
},
|
|
40
39
|
"repository": {
|
|
41
40
|
"type": "git",
|
|
@@ -55,7 +54,7 @@
|
|
|
55
54
|
"homepage": "https://github.com/netlify/netlify-plugin-nextjs#readme",
|
|
56
55
|
"dependencies": {
|
|
57
56
|
"@netlify/functions": "^0.10.0",
|
|
58
|
-
"@netlify/ipx": "^0.0.
|
|
57
|
+
"@netlify/ipx": "^0.0.8",
|
|
59
58
|
"@vercel/node": "^1.11.2-canary.4",
|
|
60
59
|
"chalk": "^4.1.2",
|
|
61
60
|
"fs-extra": "^10.0.0",
|
|
@@ -75,8 +74,8 @@
|
|
|
75
74
|
"@babel/core": "^7.15.8",
|
|
76
75
|
"@babel/preset-env": "^7.15.8",
|
|
77
76
|
"@babel/preset-typescript": "^7.16.0",
|
|
78
|
-
"@netlify/build": "^
|
|
79
|
-
"@netlify/eslint-config-node": "^
|
|
77
|
+
"@netlify/build": "^25.0.1",
|
|
78
|
+
"@netlify/eslint-config-node": "^4.0.1",
|
|
80
79
|
"@testing-library/cypress": "^8.0.1",
|
|
81
80
|
"@types/fs-extra": "^9.0.13",
|
|
82
81
|
"@types/jest": "^27.0.2",
|