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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +32 -51
  2. package/lib/constants.js +21 -0
  3. package/lib/helpers/cache.js +20 -0
  4. package/lib/helpers/config.js +174 -0
  5. package/lib/helpers/files.js +236 -0
  6. package/lib/helpers/functions.js +65 -0
  7. package/lib/helpers/verification.js +94 -0
  8. package/lib/index.js +55 -0
  9. package/lib/templates/getHandler.js +165 -0
  10. package/lib/templates/getPageResolver.js +24 -0
  11. package/lib/templates/handlerUtils.js +66 -0
  12. package/lib/templates/ipx.js +8 -0
  13. package/manifest.yml +1 -1
  14. package/package.json +56 -58
  15. package/LICENSE.md +0 -7
  16. package/helpers/cacheBuild.js +0 -31
  17. package/helpers/checkNxConfig.js +0 -56
  18. package/helpers/copyUnstableIncludedDirs.js +0 -27
  19. package/helpers/doesNotNeedPlugin.js +0 -49
  20. package/helpers/doesSiteUseNextOnNetlify.js +0 -20
  21. package/helpers/getNextConfig.js +0 -46
  22. package/helpers/getNextRoot.js +0 -20
  23. package/helpers/isStaticExportProject.js +0 -20
  24. package/helpers/resolveNextModule.js +0 -30
  25. package/helpers/usesBuildCommand.js +0 -37
  26. package/helpers/validateNextUsage.js +0 -53
  27. package/helpers/verifyBuildTarget.js +0 -69
  28. package/index.js +0 -175
  29. package/src/index.js +0 -94
  30. package/src/lib/config.js +0 -58
  31. package/src/lib/constants/regex.js +0 -9
  32. package/src/lib/helpers/addDefaultLocaleRedirect.js +0 -25
  33. package/src/lib/helpers/addLocaleRedirects.js +0 -19
  34. package/src/lib/helpers/asyncForEach.js +0 -7
  35. package/src/lib/helpers/convertToBasePathRedirects.js +0 -80
  36. package/src/lib/helpers/copyDynamicImportChunks.js +0 -39
  37. package/src/lib/helpers/formatRedirectTarget.js +0 -9
  38. package/src/lib/helpers/getDataRouteForRoute.js +0 -30
  39. package/src/lib/helpers/getFilePathForRoute.js +0 -10
  40. package/src/lib/helpers/getI18n.js +0 -10
  41. package/src/lib/helpers/getNetlifyFunctionName.js +0 -38
  42. package/src/lib/helpers/getNetlifyRoutes.js +0 -45
  43. package/src/lib/helpers/getNextDistDir.js +0 -12
  44. package/src/lib/helpers/getNextSrcDir.js +0 -5
  45. package/src/lib/helpers/getPagesManifest.js +0 -19
  46. package/src/lib/helpers/getPrerenderManifest.js +0 -40
  47. package/src/lib/helpers/getPreviewModeFunctionName.js +0 -5
  48. package/src/lib/helpers/getRoutesManifest.js +0 -12
  49. package/src/lib/helpers/getSortedRedirects.js +0 -37
  50. package/src/lib/helpers/handleFileTracking.js +0 -61
  51. package/src/lib/helpers/isApiRoute.js +0 -4
  52. package/src/lib/helpers/isDynamicRoute.js +0 -6
  53. package/src/lib/helpers/isFrameworkRoute.js +0 -5
  54. package/src/lib/helpers/isHtmlFile.js +0 -4
  55. package/src/lib/helpers/isRootCatchAllRedirect.js +0 -6
  56. package/src/lib/helpers/isRouteInPrerenderManifest.js +0 -23
  57. package/src/lib/helpers/isRouteWithDataRoute.js +0 -13
  58. package/src/lib/helpers/isRouteWithFallback.js +0 -12
  59. package/src/lib/helpers/logger.js +0 -44
  60. package/src/lib/helpers/removeFileExtension.js +0 -4
  61. package/src/lib/helpers/runJobsQueue.js +0 -18
  62. package/src/lib/helpers/setupNetlifyFunctionForPage.js +0 -44
  63. package/src/lib/helpers/setupStaticFileForPage.js +0 -18
  64. package/src/lib/pages/api/pages.js +0 -22
  65. package/src/lib/pages/api/redirects.js +0 -13
  66. package/src/lib/pages/api/setup.js +0 -15
  67. package/src/lib/pages/getInitialProps/pages.js +0 -40
  68. package/src/lib/pages/getInitialProps/redirects.js +0 -26
  69. package/src/lib/pages/getInitialProps/setup.js +0 -15
  70. package/src/lib/pages/getServerSideProps/pages.js +0 -43
  71. package/src/lib/pages/getServerSideProps/redirects.js +0 -44
  72. package/src/lib/pages/getServerSideProps/setup.js +0 -15
  73. package/src/lib/pages/getStaticProps/pages.js +0 -26
  74. package/src/lib/pages/getStaticProps/redirects.js +0 -70
  75. package/src/lib/pages/getStaticProps/setup.js +0 -68
  76. package/src/lib/pages/getStaticPropsWithFallback/pages.js +0 -24
  77. package/src/lib/pages/getStaticPropsWithFallback/redirects.js +0 -51
  78. package/src/lib/pages/getStaticPropsWithFallback/setup.js +0 -29
  79. package/src/lib/pages/getStaticPropsWithRevalidate/pages.js +0 -45
  80. package/src/lib/pages/getStaticPropsWithRevalidate/redirects.js +0 -55
  81. package/src/lib/pages/getStaticPropsWithRevalidate/setup.js +0 -40
  82. package/src/lib/pages/withoutProps/pages.js +0 -27
  83. package/src/lib/pages/withoutProps/redirects.js +0 -51
  84. package/src/lib/pages/withoutProps/setup.js +0 -34
  85. package/src/lib/pages/worker.js +0 -19
  86. package/src/lib/steps/copyNextAssets.js +0 -31
  87. package/src/lib/steps/copyPublicFiles.js +0 -18
  88. package/src/lib/steps/prepareFolders.js +0 -39
  89. package/src/lib/steps/setupHeaders.js +0 -37
  90. package/src/lib/steps/setupImageFunction.js +0 -17
  91. package/src/lib/steps/setupPages.js +0 -25
  92. package/src/lib/steps/setupRedirects.js +0 -105
  93. package/src/lib/templates/getHandlerFunction.js +0 -209
  94. package/src/lib/templates/getTemplate.js +0 -15
  95. package/src/lib/templates/imageFunction.js +0 -135
  96. package/src/next-on-netlify.js +0 -22
package/README.md CHANGED
@@ -1,6 +1,9 @@
1
1
  ![Next.js on Netlify Build Plugin](next-on-netlify.png)
2
2
 
3
- # Essential Next.js Build Plugin
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:
4
7
 
5
8
  <p align="center">
6
9
  <a aria-label="npm version" href="https://www.npmjs.com/package/@netlify/plugin-nextjs">
@@ -11,72 +14,50 @@
11
14
  </a>
12
15
  </p>
13
16
 
14
- This build plugin is a utility for supporting Next.js on Netlify. To enable server-side rendering and other framework-specific features in your Next.js application on Netlify, you will need to install this plugin for your app.
15
-
16
- ## Table of Contents
17
-
18
- - [Installation and Configuration](#installation-and-configuration)
19
- - [Docs](#docs)
20
- - [Credits](#credits)
21
-
22
- ## Installation and Configuration
23
-
24
- ### For new Next.js sites
25
-
26
- As of v3.0.0, all new sites deployed to Netlify with Next.js will automatically install this plugin for a seamless experience deploying Next.js on Netlify!
27
-
28
- This means that you don't have to do anything — just build and deploy your site to Netlify as usual and we'll handle the rest.
29
-
30
- You're able to [remove the plugin](https://docs.netlify.com/configure-builds/build-plugins/#remove-a-plugin) at any time by visiting the **Plugins** tab for your site in the Netlify UI.
17
+ ## What's new in this version
31
18
 
32
- ### For existing Next.js sites
19
+ Version 4 is a complete rewrite of the Essential Next.js plugin. For full details of everything that's new, check out
20
+ [the v4 release notes](https://github.com/netlify/netlify-plugin-nextjs/blob/main/docs/release-notes/v4.md)
33
21
 
34
- ### UI Installation
22
+ ## Installing the beta
35
23
 
36
- If your Next.js project was already deployed to Netlify pre-3.0.0, use the Netlify UI to [install the Essential Next.js Build Plugin](http://app.netlify.com/plugins/@netlify/plugin-nextjs/install) in a few clicks.
24
+ - Install the module:
37
25
 
38
- ### Manual installation
26
+ ```shell
27
+ npm install -D @netlify/plugin-nextjs@beta
28
+ ```
39
29
 
40
- 1\. Create a `netlify.toml` in the root of your project. Your file should include the plugins section below:
30
+ - Change the `publish` directory to `.next` and add the plugin to `netlify.toml` if not already installed:
41
31
 
42
32
  ```toml
43
33
  [build]
44
- command = "npm run build"
45
- publish = "out"
34
+ publish = ".next"
46
35
 
47
36
  [[plugins]]
48
- package = "@netlify/plugin-nextjs"
37
+ package = "@netlify/plugin-nextjs"
49
38
  ```
50
39
 
51
- Note: the plugin does not run for statically exported Next.js sites (aka sites that use `next export`). To use the plugin, you should use the `[build]` config in the .toml snippet above. Be sure to exclude `next export` from your build script.
52
- The plugin will attempt to detect if the site uses static export or Storybook, and will not run for either. If you want to disable the auto-detection, you can set the `NEXT_PLUGIN_FORCE_RUN` environment variable to `true` or `false`.
53
- Setting it to `true` or `1` will mean the plugin always runs, and setting it to `false` or `0` will mean it never runs. If unset, auto-detection will be used. This variable should be set in the Netlify UI or in the `netlify.toml` file.
54
-
55
- 2\. From your project's base directory, use `npm`, `yarn`, or any other Node.js package manager to add this plugin to `dependencies` in `package.json`.
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.
56
42
 
57
- ```
58
- npm install --save @netlify/plugin-nextjs
59
- ```
43
+ The `serverless` and `experimental-serverless-trace` targets are deprecated in Next 12, and all builds with this plugin
44
+ will now use the default `server` target. If you previously set the target in your `next.config.js`, you should remove
45
+ it.
60
46
 
61
- or
62
-
63
- ```
64
- yarn add @netlify/plugin-nextjs
65
- ```
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.
66
49
 
67
- Read more about [file-based plugin installation](https://docs.netlify.com/configure-builds/build-plugins/#file-based-installation) in our docs.
50
+ If you are using Nx, then you will need to point `publish` to the folder inside `dist`, e.g. `dist/apps/myapp/.next`.
68
51
 
69
- ## Docs
52
+ If you currently use redirects or rewrites on your site, see
53
+ [the Rewrites and Redirects guide](https://github.com/netlify/netlify-plugin-nextjs/blob/main/docs/redirects-rewrites.md)
54
+ for information on changes to how they are handled in this version.
70
55
 
71
- - [CLI Usage](https://github.com/netlify/netlify-plugin-nextjs/tree/main/docs/cli-usage.md)
72
- - [Custom Netlify Functions](https://github.com/netlify/netlify-plugin-nextjs/tree/main/docs/custom-functions.md)
73
- - [Image Handling](https://github.com/netlify/netlify-plugin-nextjs/tree/main/docs/image-handling.md)
74
- - [Monorepos and Nx](https://github.com/netlify/netlify-plugin-nextjs/tree/main/docs/monorepos.md)
75
- - [Custom Netlify Redirects](https://github.com/netlify/netlify-plugin-nextjs/tree/main/docs/custom-redirects.md)
76
- - [Local Files in Runtime](https://github.com/netlify/netlify-plugin-nextjs/tree/main/docs/local-files-in-runtime.md)
77
- - [FAQ](https://github.com/netlify/netlify-plugin-nextjs/tree/main/docs/faq.md)
78
- - [Caveats](https://github.com/netlify/netlify-plugin-nextjs/tree/main/docs/caveats.md)
56
+ If you want to use Next 12's beta Middleware feature, this will mostly work as expected but please
57
+ [read the docs on some caveats and workarounds](https://github.com/netlify/netlify-plugin-nextjs/blob/main/docs/middleware.md)
58
+ that are currently needed.
79
59
 
80
- ## Credits
60
+ ## Beta feedback
81
61
 
82
- This package extends the project [next-on-netlify](https://github.com/netlify/next-on-netlify), authored originally by [Finn Woelm](https://github.com/finnwoelm).
62
+ Please share any thoughts, feedback or questions about the beta
63
+ [in our discussion](https://github.com/netlify/netlify-plugin-nextjs/discussions/706).
@@ -0,0 +1,21 @@
1
+ const HANDLER_FUNCTION_NAME = '___netlify-handler';
2
+ const ODB_FUNCTION_NAME = '___netlify-odb-handler';
3
+ const IMAGE_FUNCTION_NAME = '_ipx';
4
+ // These are paths in .next that shouldn't be publicly accessible
5
+ const HIDDEN_PATHS = [
6
+ '/cache/*',
7
+ '/server/*',
8
+ '/serverless/*',
9
+ '/traces',
10
+ '/routes-manifest.json',
11
+ '/build-manifest.json',
12
+ '/prerender-manifest.json',
13
+ '/react-loadable-manifest.json',
14
+ '/BUILD_ID',
15
+ ];
16
+ module.exports = {
17
+ HIDDEN_PATHS,
18
+ IMAGE_FUNCTION_NAME,
19
+ HANDLER_FUNCTION_NAME,
20
+ ODB_FUNCTION_NAME,
21
+ };
@@ -0,0 +1,20 @@
1
+ const { posix: { join }, } = require('path');
2
+ exports.restoreCache = async ({ cache, publish }) => {
3
+ const cacheDir = join(publish, 'cache');
4
+ if (await cache.restore(cacheDir)) {
5
+ console.log('Next.js cache restored.');
6
+ }
7
+ else {
8
+ console.log('No Next.js cache to restore.');
9
+ }
10
+ };
11
+ exports.saveCache = async ({ cache, publish }) => {
12
+ const cacheDir = join(publish, 'cache');
13
+ const buildManifest = join(publish, 'build-manifest.json');
14
+ if (await cache.save(cacheDir, { digests: [buildManifest] })) {
15
+ console.log('Next.js cache saved.');
16
+ }
17
+ else {
18
+ console.log('No Next.js cache to save.');
19
+ }
20
+ };
@@ -0,0 +1,174 @@
1
+ /* eslint-disable max-lines */
2
+ const { yellowBright } = require('chalk');
3
+ const { readJSON, existsSync } = require('fs-extra');
4
+ const { outdent } = require('outdent');
5
+ const { join, dirname, relative } = require('pathe');
6
+ const slash = require('slash');
7
+ const defaultFailBuild = (message, { error }) => {
8
+ throw new Error(`${message}\n${error && error.stack}`);
9
+ };
10
+ const { HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME, HIDDEN_PATHS } = require('../constants');
11
+ const ODB_FUNCTION_PATH = `/.netlify/builders/${ODB_FUNCTION_NAME}`;
12
+ const HANDLER_FUNCTION_PATH = `/.netlify/functions/${HANDLER_FUNCTION_NAME}`;
13
+ const CATCH_ALL_REGEX = /\/\[\.{3}(.*)](.json)?$/;
14
+ const OPTIONAL_CATCH_ALL_REGEX = /\/\[{2}\.{3}(.*)]{2}(.json)?$/;
15
+ const DYNAMIC_PARAMETER_REGEX = /\/\[(.*?)]/g;
16
+ const getNetlifyRoutes = (nextRoute) => {
17
+ let netlifyRoutes = [nextRoute];
18
+ // If the route is an optional catch-all route, we need to add a second
19
+ // Netlify route for the base path (when no parameters are present).
20
+ // The file ending must be present!
21
+ if (OPTIONAL_CATCH_ALL_REGEX.test(nextRoute)) {
22
+ let netlifyRoute = nextRoute.replace(OPTIONAL_CATCH_ALL_REGEX, '$2');
23
+ // When optional catch-all route is at top-level, the regex on line 19 will
24
+ // create an empty string, but actually needs to be a forward slash
25
+ if (netlifyRoute === '')
26
+ netlifyRoute = '/';
27
+ // When optional catch-all route is at top-level, the regex on line 19 will
28
+ // create an incorrect route for the data route. For example, it creates
29
+ // /_next/data/%BUILDID%.json, but NextJS looks for
30
+ // /_next/data/%BUILDID%/index.json
31
+ netlifyRoute = netlifyRoute.replace(/(\/_next\/data\/[^/]+).json/, '$1/index.json');
32
+ // Add second route to the front of the array
33
+ netlifyRoutes.unshift(netlifyRoute);
34
+ }
35
+ // Replace catch-all, e.g., [...slug]
36
+ netlifyRoutes = netlifyRoutes.map((route) => route.replace(CATCH_ALL_REGEX, '/:$1/*'));
37
+ // Replace optional catch-all, e.g., [[...slug]]
38
+ netlifyRoutes = netlifyRoutes.map((route) => route.replace(OPTIONAL_CATCH_ALL_REGEX, '/*'));
39
+ // Replace dynamic parameters, e.g., [id]
40
+ netlifyRoutes = netlifyRoutes.map((route) => route.replace(DYNAMIC_PARAMETER_REGEX, '/:$1'));
41
+ return netlifyRoutes;
42
+ };
43
+ exports.generateRedirects = async ({ netlifyConfig, basePath, i18n }) => {
44
+ const { dynamicRoutes, routes: staticRoutes } = await readJSON(join(netlifyConfig.build.publish, 'prerender-manifest.json'));
45
+ netlifyConfig.redirects.push(...HIDDEN_PATHS.map((path) => ({
46
+ from: `${basePath}${path}`,
47
+ to: '/404.html',
48
+ status: 404,
49
+ force: true,
50
+ })));
51
+ const dataRedirects = [];
52
+ const pageRedirects = [];
53
+ const isrRedirects = [];
54
+ let hasIsr = false;
55
+ const dynamicRouteEntries = Object.entries(dynamicRoutes);
56
+ const staticRouteEntries = Object.entries(staticRoutes);
57
+ staticRouteEntries.forEach(([route, { dataRoute, initialRevalidateSeconds }]) => {
58
+ // Only look for revalidate as we need to rewrite these to SSR rather than ODB
59
+ if (initialRevalidateSeconds === false) {
60
+ // These can be ignored, as they're static files handled by the CDN
61
+ return;
62
+ }
63
+ if ((i18n === null || i18n === void 0 ? void 0 : i18n.defaultLocale) && route.startsWith(`/${i18n.defaultLocale}/`)) {
64
+ route = route.slice(i18n.defaultLocale.length + 1);
65
+ }
66
+ hasIsr = true;
67
+ isrRedirects.push(...getNetlifyRoutes(dataRoute), ...getNetlifyRoutes(route));
68
+ });
69
+ dynamicRouteEntries.forEach(([route, { dataRoute, fallback }]) => {
70
+ // Add redirects if fallback is "null" (aka blocking) or true/a string
71
+ if (fallback === false) {
72
+ return;
73
+ }
74
+ pageRedirects.push(...getNetlifyRoutes(route));
75
+ dataRedirects.push(...getNetlifyRoutes(dataRoute));
76
+ });
77
+ if (i18n) {
78
+ netlifyConfig.redirects.push({ from: `${basePath}/:locale/_next/static/*`, to: `/static/:splat`, status: 200 });
79
+ }
80
+ // This is only used in prod, so dev uses `next dev` directly
81
+ netlifyConfig.redirects.push(
82
+ // Static files are in `static`
83
+ { from: `${basePath}/_next/static/*`, to: `/static/:splat`, status: 200 },
84
+ // API routes always need to be served from the regular function
85
+ {
86
+ from: `${basePath}/api`,
87
+ to: HANDLER_FUNCTION_PATH,
88
+ status: 200,
89
+ }, {
90
+ from: `${basePath}/api/*`,
91
+ to: HANDLER_FUNCTION_PATH,
92
+ status: 200,
93
+ },
94
+ // Preview mode gets forced to the function, to bypess pre-rendered pages
95
+ {
96
+ from: `${basePath}/*`,
97
+ to: HANDLER_FUNCTION_PATH,
98
+ status: 200,
99
+ conditions: { Cookie: ['__prerender_bypass', '__next_preview_data'] },
100
+ force: true,
101
+ },
102
+ // ISR redirects are handled by the regular function. Forced to avoid pre-rendered pages
103
+ ...isrRedirects.map((redirect) => ({
104
+ from: `${basePath}${redirect}`,
105
+ to: process.env.EXPERIMENTAL_ODB_TTL ? ODB_FUNCTION_PATH : HANDLER_FUNCTION_PATH,
106
+ status: 200,
107
+ force: true,
108
+ })),
109
+ // These are pages with fallback set, which need an ODB
110
+ // Data redirects go first, to avoid conflict with splat redirects
111
+ ...dataRedirects.map((redirect) => ({
112
+ from: `${basePath}${redirect}`,
113
+ to: ODB_FUNCTION_PATH,
114
+ status: 200,
115
+ })),
116
+ // ...then all the other fallback pages
117
+ ...pageRedirects.map((redirect) => ({
118
+ from: `${basePath}${redirect}`,
119
+ to: ODB_FUNCTION_PATH,
120
+ status: 200,
121
+ })),
122
+ // Everything else is handled by the regular function
123
+ { from: `${basePath}/*`, to: HANDLER_FUNCTION_PATH, status: 200 });
124
+ if (hasIsr) {
125
+ console.log(yellowBright(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
+ };
130
+ exports.getNextConfig = async function getNextConfig({ publish, failBuild = defaultFailBuild }) {
131
+ try {
132
+ const { config, appDir, ignore } = await readJSON(join(publish, 'required-server-files.json'));
133
+ if (!config) {
134
+ return failBuild('Error loading your Next config');
135
+ }
136
+ return { ...config, appDir, ignore };
137
+ }
138
+ catch (error) {
139
+ return failBuild('Error loading your Next config', { error });
140
+ }
141
+ };
142
+ const resolveModuleRoot = (moduleName) => {
143
+ try {
144
+ return dirname(relative(process.cwd(), require.resolve(`${moduleName}/package.json`, { paths: [process.cwd()] })));
145
+ }
146
+ catch (error) {
147
+ return null;
148
+ }
149
+ };
150
+ const DEFAULT_EXCLUDED_MODULES = ['sharp', 'electron'];
151
+ exports.configureHandlerFunctions = ({ netlifyConfig, publish, ignore = [] }) => {
152
+ var _a;
153
+ /* eslint-disable no-underscore-dangle */
154
+ (_a = netlifyConfig.functions)._ipx || (_a._ipx = {});
155
+ netlifyConfig.functions._ipx.node_bundler = 'nft';
156
+ [HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME].forEach((functionName) => {
157
+ var _a, _b;
158
+ (_a = netlifyConfig.functions)[functionName] || (_a[functionName] = { included_files: [], external_node_modules: [] });
159
+ netlifyConfig.functions[functionName].node_bundler = 'nft';
160
+ (_b = netlifyConfig.functions[functionName]).included_files || (_b.included_files = []);
161
+ netlifyConfig.functions[functionName].included_files.push('.env', '.env.local', '.env.production', '.env.production.local', `${publish}/server/**`, `${publish}/serverless/**`, `${publish}/*.json`, `${publish}/BUILD_ID`, `${publish}/static/chunks/webpack-middleware*.js`, `!${publish}/server/**/*.js.nft.json`, ...ignore.map((path) => `!${slash(path)}`));
162
+ const nextRoot = resolveModuleRoot('next');
163
+ if (nextRoot) {
164
+ netlifyConfig.functions[functionName].included_files.push(`!${nextRoot}/dist/server/lib/squoosh/**/*.wasm`, `!${nextRoot}/dist/next-server/server/lib/squoosh/**/*.wasm`, `!${nextRoot}/dist/compiled/webpack/bundle4.js`, `!${nextRoot}/dist/compiled/webpack/bundle5.js`, `!${nextRoot}/dist/compiled/terser/bundle.min.js`);
165
+ }
166
+ DEFAULT_EXCLUDED_MODULES.forEach((moduleName) => {
167
+ const moduleRoot = resolveModuleRoot(moduleName);
168
+ if (moduleRoot) {
169
+ netlifyConfig.functions[functionName].included_files.push(`!${moduleRoot}/**/*`);
170
+ }
171
+ });
172
+ });
173
+ };
174
+ /* eslint-enable max-lines */
@@ -0,0 +1,236 @@
1
+ /* eslint-disable max-lines */
2
+ const { cpus } = require('os');
3
+ const { yellowBright } = require('chalk');
4
+ const { existsSync, readJson, move, cpSync, copy, writeJson, readFile, writeFile } = 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');
10
+ const TEST_ROUTE = /(|\/)\[[^/]+?](\/|\.html|$)/;
11
+ const isDynamicRoute = (route) => TEST_ROUTE.test(route);
12
+ const stripLocale = (rawPath, locales = []) => {
13
+ const [locale, ...segments] = rawPath.split('/');
14
+ if (locales.includes(locale)) {
15
+ return segments.join('/');
16
+ }
17
+ return rawPath;
18
+ };
19
+ const matchMiddleware = (middleware, filePath) => (middleware === null || middleware === void 0 ? void 0 : middleware.includes('')) ||
20
+ (middleware === null || middleware === void 0 ? void 0 : middleware.find((middlewarePath) => filePath === middlewarePath || filePath === `${middlewarePath}.html` || filePath.startsWith(`${middlewarePath}/`)));
21
+ const matchesRedirect = (file, redirects) => {
22
+ if (!Array.isArray(redirects)) {
23
+ return false;
24
+ }
25
+ return redirects.some((redirect) => {
26
+ if (!redirect.regex || redirect.internal) {
27
+ return false;
28
+ }
29
+ // Strips the extension from the file path
30
+ return new RegExp(redirect.regex).test(`/${file.slice(0, -5)}`);
31
+ });
32
+ };
33
+ const matchesRewrite = (file, rewrites) => {
34
+ if (Array.isArray(rewrites)) {
35
+ return matchesRedirect(file, rewrites);
36
+ }
37
+ if (!Array.isArray(rewrites === null || rewrites === void 0 ? void 0 : rewrites.beforeFiles)) {
38
+ return false;
39
+ }
40
+ return matchesRedirect(file, rewrites.beforeFiles);
41
+ };
42
+ exports.matchesRedirect = matchesRedirect;
43
+ exports.matchesRewrite = matchesRewrite;
44
+ exports.matchMiddleware = matchMiddleware;
45
+ exports.stripLocale = stripLocale;
46
+ exports.isDynamicRoute = isDynamicRoute;
47
+ // eslint-disable-next-line max-lines-per-function
48
+ exports.moveStaticPages = async ({ netlifyConfig, target, i18n }) => {
49
+ 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
+ // Load the middleware manifest so we can check if a file matches it before moving
53
+ let middleware;
54
+ const manifestPath = join(outputDir, 'middleware-manifest.json');
55
+ if (existsSync(manifestPath)) {
56
+ const manifest = await readJson(manifestPath);
57
+ if (manifest === null || manifest === void 0 ? void 0 : manifest.middleware) {
58
+ middleware = Object.keys(manifest.middleware).map((path) => path.slice(1));
59
+ }
60
+ }
61
+ const prerenderManifest = await readJson(join(netlifyConfig.build.publish, 'prerender-manifest.json'));
62
+ const { redirects, rewrites } = await readJson(join(netlifyConfig.build.publish, 'routes-manifest.json'));
63
+ const isrFiles = new Set();
64
+ Object.entries(prerenderManifest.routes).forEach(([route, { initialRevalidateSeconds }]) => {
65
+ if (initialRevalidateSeconds) {
66
+ // Find all files used by ISR routes
67
+ const trimmedPath = route.slice(1);
68
+ isrFiles.add(`${trimmedPath}.html`);
69
+ isrFiles.add(`${trimmedPath}.json`);
70
+ }
71
+ });
72
+ const files = [];
73
+ const moveFile = async (file) => {
74
+ const source = join(root, file);
75
+ files.push(file);
76
+ const dest = join(netlifyConfig.build.publish, file);
77
+ try {
78
+ await move(source, dest);
79
+ }
80
+ catch (error) {
81
+ console.warn('Error moving file', source, error);
82
+ }
83
+ };
84
+ // Move all static files, except error documents and nft manifests
85
+ const pages = await globby(['**/*.{html,json}', '!**/(500|404|*.js.nft).{html,json}'], {
86
+ cwd: root,
87
+ dot: true,
88
+ });
89
+ const matchingMiddleware = new Set();
90
+ const matchedPages = new Set();
91
+ const matchedRedirects = new Set();
92
+ const matchedRewrites = new Set();
93
+ // Limit concurrent file moves to number of cpus or 2 if there is only 1
94
+ const limit = pLimit(Math.max(2, cpus().length));
95
+ const promises = pages.map(async (rawPath) => {
96
+ const filePath = slash(rawPath);
97
+ // Don't move ISR files, as they're used for the first request
98
+ if (isrFiles.has(filePath)) {
99
+ return;
100
+ }
101
+ if (isDynamicRoute(filePath)) {
102
+ return;
103
+ }
104
+ if (matchesRedirect(filePath, redirects)) {
105
+ matchedRedirects.add(filePath);
106
+ return;
107
+ }
108
+ if (matchesRewrite(filePath, rewrites)) {
109
+ matchedRewrites.add(filePath);
110
+ return;
111
+ }
112
+ // Middleware matches against the unlocalised path
113
+ const unlocalizedPath = stripLocale(rawPath, i18n === null || i18n === void 0 ? void 0 : i18n.locales);
114
+ const middlewarePath = matchMiddleware(middleware, unlocalizedPath);
115
+ // 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
116
+ if (middlewarePath) {
117
+ matchingMiddleware.add(middlewarePath);
118
+ matchedPages.add(rawPath);
119
+ return;
120
+ }
121
+ return limit(moveFile, filePath);
122
+ });
123
+ await Promise.all(promises);
124
+ console.log(`Moved ${files.length} files`);
125
+ if (matchedPages.size !== 0) {
126
+ console.log(yellowBright(outdent `
127
+ 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.
128
+ This is fine, but we're letting you know because it may not be what you expect.
129
+ `));
130
+ console.log(outdent `
131
+ The following middleware matched statically-rendered pages:
132
+
133
+ ${yellowBright([...matchingMiddleware].map((mid) => `- /${mid}/_middleware`).join('\n'))}
134
+
135
+ ────────────────────────────────────────────────────────────────
136
+ `);
137
+ // There could potentially be thousands of matching pages, so we don't want to spam the console with this
138
+ if (matchedPages.size < 50) {
139
+ console.log(outdent `
140
+ The following files matched middleware and were not moved to the CDN:
141
+
142
+ ${yellowBright([...matchedPages].map((mid) => `- ${mid}`).join('\n'))}
143
+
144
+ ────────────────────────────────────────────────────────────────
145
+ `);
146
+ }
147
+ }
148
+ if (matchedRedirects.size !== 0 || matchedRewrites.size !== 0) {
149
+ console.log(yellowBright(outdent `
150
+ 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.
151
+ `));
152
+ if (matchedRedirects.size < 50 && matchedRedirects.size !== 0) {
153
+ console.log(outdent `
154
+ The following files matched redirects and were not moved to the CDN:
155
+
156
+ ${yellowBright([...matchedRedirects].map((mid) => `- ${mid}`).join('\n'))}
157
+
158
+ ────────────────────────────────────────────────────────────────
159
+ `);
160
+ }
161
+ if (matchedRewrites.size < 50 && matchedRewrites.size !== 0) {
162
+ console.log(outdent `
163
+ The following files matched beforeFiles rewrites and were not moved to the CDN:
164
+
165
+ ${yellowBright([...matchedRewrites].map((mid) => `- ${mid}`).join('\n'))}
166
+
167
+ ────────────────────────────────────────────────────────────────
168
+ `);
169
+ }
170
+ }
171
+ // Write the manifest for use in the serverless functions
172
+ await writeJson(join(netlifyConfig.build.publish, 'static-manifest.json'), files);
173
+ if (i18n === null || i18n === void 0 ? void 0 : i18n.defaultLocale) {
174
+ // Copy the default locale into the root
175
+ const defaultLocaleDir = join(netlifyConfig.build.publish, i18n.defaultLocale);
176
+ if (existsSync(defaultLocaleDir)) {
177
+ await copy(defaultLocaleDir, `${netlifyConfig.build.publish}/`);
178
+ }
179
+ }
180
+ };
181
+ const patchFile = async ({ file, from, to }) => {
182
+ if (!existsSync(file)) {
183
+ return;
184
+ }
185
+ const content = await readFile(file, 'utf8');
186
+ if (content.includes(to)) {
187
+ return;
188
+ }
189
+ const newContent = content.replace(from, to);
190
+ await writeFile(`${file}.orig`, content);
191
+ await writeFile(file, newContent);
192
+ };
193
+ const getServerFile = (root) => {
194
+ let serverFile;
195
+ try {
196
+ serverFile = require.resolve('next/dist/server/next-server', { paths: [root] });
197
+ }
198
+ catch {
199
+ // Ignore
200
+ }
201
+ if (!serverFile) {
202
+ try {
203
+ // eslint-disable-next-line node/no-missing-require
204
+ serverFile = require.resolve('next/dist/next-server/server/next-server', { paths: [root] });
205
+ }
206
+ catch {
207
+ // Ignore
208
+ }
209
+ }
210
+ return serverFile;
211
+ };
212
+ exports.patchNextFiles = async (root) => {
213
+ const serverFile = getServerFile(root);
214
+ console.log(`Patching ${serverFile}`);
215
+ if (serverFile) {
216
+ await patchFile({
217
+ file: serverFile,
218
+ from: `let ssgCacheKey = `,
219
+ to: `let ssgCacheKey = process.env._BYPASS_SSG || `,
220
+ });
221
+ }
222
+ };
223
+ exports.unpatchNextFiles = async (root) => {
224
+ const serverFile = getServerFile(root);
225
+ const origFile = `${serverFile}.orig`;
226
+ if (existsSync(origFile)) {
227
+ await move(origFile, serverFile, { overwrite: true });
228
+ }
229
+ };
230
+ exports.movePublicFiles = async ({ appDir, publish }) => {
231
+ const publicDir = join(appDir, 'public');
232
+ if (existsSync(publicDir)) {
233
+ await copy(publicDir, `${publish}/`);
234
+ }
235
+ };
236
+ /* eslint-enable max-lines */
@@ -0,0 +1,65 @@
1
+ const { copyFile, ensureDir, writeFile, writeJSON } = require('fs-extra');
2
+ const { join, relative } = require('pathe');
3
+ const { HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME, IMAGE_FUNCTION_NAME } = require('../constants');
4
+ const getHandler = require('../templates/getHandler');
5
+ const { getPageResolver } = require('../templates/getPageResolver');
6
+ const DEFAULT_FUNCTIONS_SRC = 'netlify/functions';
7
+ exports.generateFunctions = async ({ FUNCTIONS_SRC = DEFAULT_FUNCTIONS_SRC, INTERNAL_FUNCTIONS_SRC, PUBLISH_DIR }, appDir) => {
8
+ const functionsDir = INTERNAL_FUNCTIONS_SRC || FUNCTIONS_SRC;
9
+ const bridgeFile = require.resolve('@vercel/node/dist/bridge');
10
+ const functionDir = join(process.cwd(), functionsDir, HANDLER_FUNCTION_NAME);
11
+ const publishDir = relative(functionDir, join(process.cwd(), PUBLISH_DIR));
12
+ const writeHandler = async (func, isODB) => {
13
+ const handlerSource = await getHandler({ isODB, publishDir, appDir: relative(functionDir, appDir) });
14
+ await ensureDir(join(functionsDir, func));
15
+ await writeFile(join(functionsDir, func, `${func}.js`), handlerSource);
16
+ await copyFile(bridgeFile, join(functionsDir, func, 'bridge.js'));
17
+ await copyFile(join(__dirname, '..', '..', 'lib', 'templates', 'handlerUtils.js'), join(functionsDir, func, 'handlerUtils.js'));
18
+ };
19
+ await writeHandler(HANDLER_FUNCTION_NAME, false);
20
+ await writeHandler(ODB_FUNCTION_NAME, true);
21
+ };
22
+ /**
23
+ * Writes a file in each function directory that contains references to every page entrypoint.
24
+ * This is just so that the nft bundler knows about them. We'll eventually do this better.
25
+ */
26
+ exports.generatePagesResolver = async ({ constants: { INTERNAL_FUNCTIONS_SRC, FUNCTIONS_SRC = DEFAULT_FUNCTIONS_SRC }, netlifyConfig, target, }) => {
27
+ const functionsPath = INTERNAL_FUNCTIONS_SRC || FUNCTIONS_SRC;
28
+ const jsSource = await getPageResolver({
29
+ netlifyConfig,
30
+ target,
31
+ });
32
+ await writeFile(join(functionsPath, ODB_FUNCTION_NAME, 'pages.js'), jsSource);
33
+ await writeFile(join(functionsPath, HANDLER_FUNCTION_NAME, 'pages.js'), jsSource);
34
+ };
35
+ // Move our next/image function into the correct functions directory
36
+ exports.setupImageFunction = async ({ constants: { INTERNAL_FUNCTIONS_SRC, FUNCTIONS_SRC = DEFAULT_FUNCTIONS_SRC }, imageconfig = {}, netlifyConfig, basePath, }) => {
37
+ const functionsPath = INTERNAL_FUNCTIONS_SRC || FUNCTIONS_SRC;
38
+ const functionName = `${IMAGE_FUNCTION_NAME}.js`;
39
+ const functionDirectory = join(functionsPath, IMAGE_FUNCTION_NAME);
40
+ await ensureDir(functionDirectory);
41
+ await writeJSON(join(functionDirectory, 'imageconfig.json'), {
42
+ ...imageconfig,
43
+ basePath: [basePath, IMAGE_FUNCTION_NAME].join('/'),
44
+ });
45
+ await copyFile(join(__dirname, '..', 'templates', 'ipx.js'), join(functionDirectory, functionName));
46
+ const imagePath = imageconfig.path || '/_next/image';
47
+ netlifyConfig.redirects.push({
48
+ from: `${imagePath}*`,
49
+ query: { url: ':url', w: ':width', q: ':quality' },
50
+ to: `${basePath}/${IMAGE_FUNCTION_NAME}/w_:width,q_:quality/:url`,
51
+ status: 301,
52
+ }, {
53
+ from: `${basePath}/${IMAGE_FUNCTION_NAME}/*`,
54
+ to: `/.netlify/builders/${IMAGE_FUNCTION_NAME}`,
55
+ status: 200,
56
+ });
57
+ if (basePath) {
58
+ // next/image generates image static URLs that still point at the site root
59
+ netlifyConfig.redirects.push({
60
+ from: '/_next/static/image/*',
61
+ to: '/static/image/:splat',
62
+ status: 200,
63
+ });
64
+ }
65
+ };