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

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 (95) hide show
  1. package/README.md +20 -55
  2. package/lib/constants.js +21 -0
  3. package/lib/helpers/cache.js +20 -0
  4. package/lib/helpers/config.js +119 -0
  5. package/lib/helpers/files.js +48 -0
  6. package/lib/helpers/functions.js +64 -0
  7. package/lib/helpers/verification.js +79 -0
  8. package/lib/index.js +47 -0
  9. package/lib/templates/getHandler.js +164 -0
  10. package/lib/templates/getPageResolver.js +24 -0
  11. package/lib/templates/ipx.js +8 -0
  12. package/manifest.yml +1 -1
  13. package/package.json +47 -54
  14. package/LICENSE.md +0 -7
  15. package/helpers/cacheBuild.js +0 -31
  16. package/helpers/checkNxConfig.js +0 -56
  17. package/helpers/copyUnstableIncludedDirs.js +0 -27
  18. package/helpers/doesNotNeedPlugin.js +0 -49
  19. package/helpers/doesSiteUseNextOnNetlify.js +0 -20
  20. package/helpers/getNextConfig.js +0 -46
  21. package/helpers/getNextRoot.js +0 -20
  22. package/helpers/isStaticExportProject.js +0 -20
  23. package/helpers/resolveNextModule.js +0 -30
  24. package/helpers/usesBuildCommand.js +0 -37
  25. package/helpers/validateNextUsage.js +0 -53
  26. package/helpers/verifyBuildTarget.js +0 -69
  27. package/index.js +0 -175
  28. package/src/index.js +0 -94
  29. package/src/lib/config.js +0 -58
  30. package/src/lib/constants/regex.js +0 -9
  31. package/src/lib/helpers/addDefaultLocaleRedirect.js +0 -25
  32. package/src/lib/helpers/addLocaleRedirects.js +0 -19
  33. package/src/lib/helpers/asyncForEach.js +0 -7
  34. package/src/lib/helpers/convertToBasePathRedirects.js +0 -80
  35. package/src/lib/helpers/copyDynamicImportChunks.js +0 -39
  36. package/src/lib/helpers/formatRedirectTarget.js +0 -9
  37. package/src/lib/helpers/getDataRouteForRoute.js +0 -30
  38. package/src/lib/helpers/getFilePathForRoute.js +0 -10
  39. package/src/lib/helpers/getI18n.js +0 -10
  40. package/src/lib/helpers/getNetlifyFunctionName.js +0 -38
  41. package/src/lib/helpers/getNetlifyRoutes.js +0 -45
  42. package/src/lib/helpers/getNextDistDir.js +0 -12
  43. package/src/lib/helpers/getNextSrcDir.js +0 -5
  44. package/src/lib/helpers/getPagesManifest.js +0 -19
  45. package/src/lib/helpers/getPrerenderManifest.js +0 -40
  46. package/src/lib/helpers/getPreviewModeFunctionName.js +0 -5
  47. package/src/lib/helpers/getRoutesManifest.js +0 -12
  48. package/src/lib/helpers/getSortedRedirects.js +0 -37
  49. package/src/lib/helpers/handleFileTracking.js +0 -61
  50. package/src/lib/helpers/isApiRoute.js +0 -4
  51. package/src/lib/helpers/isDynamicRoute.js +0 -6
  52. package/src/lib/helpers/isFrameworkRoute.js +0 -5
  53. package/src/lib/helpers/isHtmlFile.js +0 -4
  54. package/src/lib/helpers/isRootCatchAllRedirect.js +0 -6
  55. package/src/lib/helpers/isRouteInPrerenderManifest.js +0 -23
  56. package/src/lib/helpers/isRouteWithDataRoute.js +0 -13
  57. package/src/lib/helpers/isRouteWithFallback.js +0 -12
  58. package/src/lib/helpers/logger.js +0 -44
  59. package/src/lib/helpers/removeFileExtension.js +0 -4
  60. package/src/lib/helpers/runJobsQueue.js +0 -18
  61. package/src/lib/helpers/setupNetlifyFunctionForPage.js +0 -44
  62. package/src/lib/helpers/setupStaticFileForPage.js +0 -18
  63. package/src/lib/pages/api/pages.js +0 -22
  64. package/src/lib/pages/api/redirects.js +0 -13
  65. package/src/lib/pages/api/setup.js +0 -15
  66. package/src/lib/pages/getInitialProps/pages.js +0 -40
  67. package/src/lib/pages/getInitialProps/redirects.js +0 -26
  68. package/src/lib/pages/getInitialProps/setup.js +0 -15
  69. package/src/lib/pages/getServerSideProps/pages.js +0 -43
  70. package/src/lib/pages/getServerSideProps/redirects.js +0 -44
  71. package/src/lib/pages/getServerSideProps/setup.js +0 -15
  72. package/src/lib/pages/getStaticProps/pages.js +0 -26
  73. package/src/lib/pages/getStaticProps/redirects.js +0 -70
  74. package/src/lib/pages/getStaticProps/setup.js +0 -68
  75. package/src/lib/pages/getStaticPropsWithFallback/pages.js +0 -24
  76. package/src/lib/pages/getStaticPropsWithFallback/redirects.js +0 -51
  77. package/src/lib/pages/getStaticPropsWithFallback/setup.js +0 -29
  78. package/src/lib/pages/getStaticPropsWithRevalidate/pages.js +0 -45
  79. package/src/lib/pages/getStaticPropsWithRevalidate/redirects.js +0 -55
  80. package/src/lib/pages/getStaticPropsWithRevalidate/setup.js +0 -40
  81. package/src/lib/pages/withoutProps/pages.js +0 -27
  82. package/src/lib/pages/withoutProps/redirects.js +0 -51
  83. package/src/lib/pages/withoutProps/setup.js +0 -34
  84. package/src/lib/pages/worker.js +0 -19
  85. package/src/lib/steps/copyNextAssets.js +0 -31
  86. package/src/lib/steps/copyPublicFiles.js +0 -18
  87. package/src/lib/steps/prepareFolders.js +0 -39
  88. package/src/lib/steps/setupHeaders.js +0 -37
  89. package/src/lib/steps/setupImageFunction.js +0 -17
  90. package/src/lib/steps/setupPages.js +0 -25
  91. package/src/lib/steps/setupRedirects.js +0 -105
  92. package/src/lib/templates/getHandlerFunction.js +0 -209
  93. package/src/lib/templates/getTemplate.js +0 -15
  94. package/src/lib/templates/imageFunction.js +0 -135
  95. 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
+
6
+ :warning: This is the beta version of the Essential Next.js plugin. For the stable version, see [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,34 @@
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.
31
-
32
- ### For existing Next.js sites
17
+ ## What's new in this version
33
18
 
34
- ### UI Installation
19
+ Version 4 is a complete rewrite of the Essential Next.js plugin. For full details of everything that's new, see [the v4 release notes](https://github.com/netlify/netlify-plugin-nextjs/blob/main/docs/release-notes/v4.md)
35
20
 
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.
21
+ ## Installing the beta
37
22
 
38
- ### Manual installation
39
-
40
- 1\. Create a `netlify.toml` in the root of your project. Your file should include the plugins section below:
41
23
 
24
+ - Install the module:
25
+ ```shell
26
+ npm install -D @netlify/plugin-nextjs@beta
27
+ ```
28
+ - Change the `publish` directory to `.next` and add the plugin to `netlify.toml` if not already installed:
42
29
  ```toml
43
30
  [build]
44
- command = "npm run build"
45
- publish = "out"
31
+ publish = ".next"
46
32
 
47
33
  [[plugins]]
48
- package = "@netlify/plugin-nextjs"
49
- ```
50
-
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`.
56
-
57
- ```
58
- npm install --save @netlify/plugin-nextjs
34
+ package = "@netlify/plugin-nextjs"
59
35
  ```
60
36
 
61
- or
62
-
63
- ```
64
- yarn add @netlify/plugin-nextjs
65
- ```
37
+ If you previously set `target: "serverless"` or a custom `distDir` in your `next.config.js`, or set `node_bundler` or `external_node_modules` in your `netlify.toml` these are no longer needed and can be removed.
66
38
 
67
- Read more about [file-based plugin installation](https://docs.netlify.com/configure-builds/build-plugins/#file-based-installation) in our docs.
39
+ The `serverless` and `experimental-serverless-trace` targets are deprecated in Next 12, and all builds with this plugin will now use the default `server` target.
68
40
 
69
- ## Docs
41
+ If you are using a monorepo you will need to change `publish` to point to the full path to the built `.next` directory, which may be in a subdirectory. If you have changed your `distDir` then it will need to match that.
70
42
 
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)
43
+ If you are using Nx, then you will need to point `publish` to the folder inside `dist`, e.g. `dist/apps/myapp/.next`.
79
44
 
80
- ## Credits
45
+ ## Beta feedback
81
46
 
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).
47
+ Please share any thoughts, feedback or questions about the beta [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,119 @@
1
+ // @ts-check
2
+ const { readJSON } = require('fs-extra');
3
+ const { join, dirname, relative } = require('pathe');
4
+ const slash = require('slash');
5
+ const defaultFailBuild = (message, { error }) => {
6
+ throw new Error(`${message}\n${error && error.stack}`);
7
+ };
8
+ const { HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME, HIDDEN_PATHS } = require('../constants');
9
+ const ODB_FUNCTION_PATH = `/.netlify/builders/${ODB_FUNCTION_NAME}`;
10
+ const HANDLER_FUNCTION_PATH = `/.netlify/functions/${HANDLER_FUNCTION_NAME}`;
11
+ const CATCH_ALL_REGEX = /\/\[\.{3}(.*)](.json)?$/;
12
+ const OPTIONAL_CATCH_ALL_REGEX = /\/\[{2}\.{3}(.*)]{2}(.json)?$/;
13
+ const DYNAMIC_PARAMETER_REGEX = /\/\[(.*?)]/g;
14
+ const getNetlifyRoutes = (nextRoute) => {
15
+ let netlifyRoutes = [nextRoute];
16
+ // If the route is an optional catch-all route, we need to add a second
17
+ // Netlify route for the base path (when no parameters are present).
18
+ // The file ending must be present!
19
+ if (OPTIONAL_CATCH_ALL_REGEX.test(nextRoute)) {
20
+ let netlifyRoute = nextRoute.replace(OPTIONAL_CATCH_ALL_REGEX, '$2');
21
+ // When optional catch-all route is at top-level, the regex on line 19 will
22
+ // create an empty string, but actually needs to be a forward slash
23
+ if (netlifyRoute === '')
24
+ netlifyRoute = '/';
25
+ // When optional catch-all route is at top-level, the regex on line 19 will
26
+ // create an incorrect route for the data route. For example, it creates
27
+ // /_next/data/%BUILDID%.json, but NextJS looks for
28
+ // /_next/data/%BUILDID%/index.json
29
+ netlifyRoute = netlifyRoute.replace(/(\/_next\/data\/[^/]+).json/, '$1/index.json');
30
+ // Add second route to the front of the array
31
+ netlifyRoutes.unshift(netlifyRoute);
32
+ }
33
+ // Replace catch-all, e.g., [...slug]
34
+ netlifyRoutes = netlifyRoutes.map((route) => route.replace(CATCH_ALL_REGEX, '/:$1/*'));
35
+ // Replace optional catch-all, e.g., [[...slug]]
36
+ netlifyRoutes = netlifyRoutes.map((route) => route.replace(OPTIONAL_CATCH_ALL_REGEX, '/*'));
37
+ // Replace dynamic parameters, e.g., [id]
38
+ netlifyRoutes = netlifyRoutes.map((route) => route.replace(DYNAMIC_PARAMETER_REGEX, '/:$1'));
39
+ return netlifyRoutes;
40
+ };
41
+ exports.generateRedirects = async ({ netlifyConfig, basePath, i18n }) => {
42
+ const { dynamicRoutes } = await readJSON(join(netlifyConfig.build.publish, 'prerender-manifest.json'));
43
+ const redirects = [];
44
+ netlifyConfig.redirects.push(...HIDDEN_PATHS.map((path) => ({
45
+ from: `${basePath}${path}`,
46
+ to: '/404.html',
47
+ status: 404,
48
+ force: true,
49
+ })));
50
+ const dynamicRouteEntries = Object.entries(dynamicRoutes);
51
+ dynamicRouteEntries.sort((a, b) => a[0].localeCompare(b[0]));
52
+ dynamicRouteEntries.forEach(([route, { dataRoute, fallback }]) => {
53
+ // Add redirects if fallback is "null" (aka blocking) or true/a string
54
+ if (fallback === false) {
55
+ return;
56
+ }
57
+ redirects.push(...getNetlifyRoutes(route), ...getNetlifyRoutes(dataRoute));
58
+ });
59
+ if (i18n) {
60
+ netlifyConfig.redirects.push({ from: `${basePath}/:locale/_next/static/*`, to: `/static/:splat`, status: 200 });
61
+ }
62
+ // This is only used in prod, so dev uses `next dev` directly
63
+ netlifyConfig.redirects.push({ from: `${basePath}/_next/static/*`, to: `/static/:splat`, status: 200 }, {
64
+ from: `${basePath}/*`,
65
+ to: HANDLER_FUNCTION_PATH,
66
+ status: 200,
67
+ force: true,
68
+ conditions: { Cookie: ['__prerender_bypass', '__next_preview_data'] },
69
+ }, ...redirects.map((redirect) => ({
70
+ from: `${basePath}${redirect}`,
71
+ to: ODB_FUNCTION_PATH,
72
+ status: 200,
73
+ })), { from: `${basePath}/*`, to: HANDLER_FUNCTION_PATH, status: 200 });
74
+ };
75
+ exports.getNextConfig = async function getNextConfig({ publish, failBuild = defaultFailBuild }) {
76
+ try {
77
+ const { config, appDir, ignore } = await readJSON(join(publish, 'required-server-files.json'));
78
+ if (!config) {
79
+ return failBuild('Error loading your Next config');
80
+ }
81
+ return { ...config, appDir, ignore };
82
+ }
83
+ catch (error) {
84
+ return failBuild('Error loading your Next config', { error });
85
+ }
86
+ };
87
+ const resolveModuleRoot = (moduleName) => {
88
+ try {
89
+ return dirname(relative(process.cwd(), require.resolve(`${moduleName}/package.json`, { paths: [process.cwd()] })));
90
+ }
91
+ catch (error) {
92
+ return null;
93
+ }
94
+ };
95
+ exports.configureHandlerFunctions = ({ netlifyConfig, publish, ignore = [] }) => {
96
+ var _a;
97
+ /* eslint-disable no-underscore-dangle */
98
+ (_a = netlifyConfig.functions)._ipx || (_a._ipx = {});
99
+ netlifyConfig.functions._ipx.node_bundler = 'nft';
100
+ [HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME].forEach((functionName) => {
101
+ var _a, _b;
102
+ (_a = netlifyConfig.functions)[functionName] || (_a[functionName] = { included_files: [], external_node_modules: [] });
103
+ netlifyConfig.functions[functionName].node_bundler = 'nft';
104
+ (_b = netlifyConfig.functions[functionName]).included_files || (_b.included_files = []);
105
+ netlifyConfig.functions[functionName].included_files.push(`${publish}/server/**`, `${publish}/serverless/**`, `${publish}/*.json`, `${publish}/BUILD_ID`, ...ignore.map((path) => `!${slash(path)}`));
106
+ const nextRoot = resolveModuleRoot('next');
107
+ if (nextRoot) {
108
+ 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`);
109
+ }
110
+ const sharpRoot = resolveModuleRoot('sharp');
111
+ if (sharpRoot) {
112
+ netlifyConfig.functions[functionName].included_files.push(`!${sharpRoot}/**/*`);
113
+ }
114
+ const electronRoot = resolveModuleRoot('electron');
115
+ if (electronRoot) {
116
+ netlifyConfig.functions[functionName].included_files.push(`!${electronRoot}/**/*`);
117
+ }
118
+ });
119
+ };
@@ -0,0 +1,48 @@
1
+ // @ts-check
2
+ const { existsSync, readJson, move, cpSync, copy, writeJson } = require('fs-extra');
3
+ const pLimit = require('p-limit');
4
+ const { join } = require('pathe');
5
+ const TEST_ROUTE = /\/\[[^/]+?](?=\/|$)/;
6
+ const isDynamicRoute = (route) => TEST_ROUTE.test(route);
7
+ exports.moveStaticPages = async ({ netlifyConfig, target, i18n, failBuild }) => {
8
+ const root = join(netlifyConfig.build.publish, target === 'server' ? 'server' : 'serverless');
9
+ const pagesManifestPath = join(root, 'pages-manifest.json');
10
+ if (!existsSync(pagesManifestPath)) {
11
+ failBuild(`Could not find pages manifest at ${pagesManifestPath}`);
12
+ }
13
+ const files = [];
14
+ const moveFile = async (file) => {
15
+ const source = join(root, file);
16
+ // Trim the initial "pages"
17
+ const filePath = file.slice(6);
18
+ files.push(filePath);
19
+ const dest = join(netlifyConfig.build.publish, filePath);
20
+ await move(source, dest);
21
+ };
22
+ const pagesManifest = await readJson(pagesManifestPath);
23
+ // Arbitrary limit of 10 concurrent file moves
24
+ const limit = pLimit(10);
25
+ const promises = Object.entries(pagesManifest).map(async ([route, filePath]) => {
26
+ if (isDynamicRoute(route) ||
27
+ !(filePath.endsWith('.html') || filePath.endsWith('.json')) ||
28
+ filePath.endsWith('/404.html') ||
29
+ filePath.endsWith('/500.html')) {
30
+ return;
31
+ }
32
+ return limit(moveFile, filePath);
33
+ });
34
+ await Promise.all(promises);
35
+ console.log(`Moved ${files.length} page files`);
36
+ // Write the manifest for use in the serverless functions
37
+ await writeJson(join(netlifyConfig.build.publish, 'static-manifest.json'), files);
38
+ if (i18n === null || i18n === void 0 ? void 0 : i18n.defaultLocale) {
39
+ // Copy the default locale into the root
40
+ await copy(join(netlifyConfig.build.publish, i18n.defaultLocale), `${netlifyConfig.build.publish}/`);
41
+ }
42
+ };
43
+ exports.movePublicFiles = async ({ appDir, publish }) => {
44
+ const publicDir = join(appDir, 'public');
45
+ if (existsSync(publicDir)) {
46
+ await copy(publicDir, `${publish}/`);
47
+ }
48
+ };
@@ -0,0 +1,64 @@
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
+ };
18
+ await writeHandler(HANDLER_FUNCTION_NAME, false);
19
+ await writeHandler(ODB_FUNCTION_NAME, true);
20
+ };
21
+ /**
22
+ * Writes a file in each function directory that contains references to every page entrypoint.
23
+ * This is just so that the nft bundler knows about them. We'll eventually do this better.
24
+ */
25
+ exports.generatePagesResolver = async ({ constants: { INTERNAL_FUNCTIONS_SRC, FUNCTIONS_SRC = DEFAULT_FUNCTIONS_SRC }, netlifyConfig, target, }) => {
26
+ const functionsPath = INTERNAL_FUNCTIONS_SRC || FUNCTIONS_SRC;
27
+ const jsSource = await getPageResolver({
28
+ netlifyConfig,
29
+ target,
30
+ });
31
+ await writeFile(join(functionsPath, ODB_FUNCTION_NAME, 'pages.js'), jsSource);
32
+ await writeFile(join(functionsPath, HANDLER_FUNCTION_NAME, 'pages.js'), jsSource);
33
+ };
34
+ // Move our next/image function into the correct functions directory
35
+ exports.setupImageFunction = async ({ constants: { INTERNAL_FUNCTIONS_SRC, FUNCTIONS_SRC = DEFAULT_FUNCTIONS_SRC }, imageconfig = {}, netlifyConfig, basePath, }) => {
36
+ const functionsPath = INTERNAL_FUNCTIONS_SRC || FUNCTIONS_SRC;
37
+ const functionName = `${IMAGE_FUNCTION_NAME}.js`;
38
+ const functionDirectory = join(functionsPath, IMAGE_FUNCTION_NAME);
39
+ await ensureDir(functionDirectory);
40
+ await writeJSON(join(functionDirectory, 'imageconfig.json'), {
41
+ ...imageconfig,
42
+ basePath: [basePath, IMAGE_FUNCTION_NAME].join('/'),
43
+ });
44
+ await copyFile(join(__dirname, '..', 'templates', 'ipx.js'), join(functionDirectory, functionName));
45
+ const imagePath = imageconfig.path || '/_next/image';
46
+ netlifyConfig.redirects.push({
47
+ from: `${imagePath}*`,
48
+ query: { url: ':url', w: ':width', q: ':quality' },
49
+ to: `${basePath}/${IMAGE_FUNCTION_NAME}/w_:width,q_:quality/:url`,
50
+ status: 301,
51
+ }, {
52
+ from: `${basePath}/${IMAGE_FUNCTION_NAME}/*`,
53
+ to: `/.netlify/builders/${IMAGE_FUNCTION_NAME}`,
54
+ status: 200,
55
+ });
56
+ if (basePath) {
57
+ // next/image generates image static URLs that still point at the site root
58
+ netlifyConfig.redirects.push({
59
+ from: '/_next/static/image/*',
60
+ to: '/static/image/:splat',
61
+ status: 200,
62
+ });
63
+ }
64
+ };
@@ -0,0 +1,79 @@
1
+ const { existsSync, promises } = require('fs');
2
+ const path = require('path');
3
+ const { relative } = require('path');
4
+ const { yellowBright, greenBright, blueBright, redBright } = require('chalk');
5
+ const { async: StreamZip } = require('node-stream-zip');
6
+ const outdent = require('outdent');
7
+ const prettyBytes = require('pretty-bytes');
8
+ const { satisfies } = require('semver');
9
+ // This is when nft support was added
10
+ const REQUIRED_BUILD_VERSION = '>=18.16.0';
11
+ exports.verifyNetlifyBuildVersion = ({ IS_LOCAL, NETLIFY_BUILD_VERSION, failBuild }) => {
12
+ // 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 `
15
+ This version of the Essential Next.js plugin requires netlify-cli@6.12.4 or higher. Please upgrade and try again.
16
+ You can do this by running: "npm install -g netlify-cli@latest" or "yarn global add netlify-cli@latest"
17
+ `);
18
+ }
19
+ };
20
+ exports.checkNextSiteHasBuilt = ({ publish, failBuild }) => {
21
+ if (!existsSync(path.join(publish, 'BUILD_ID'))) {
22
+ return failBuild(outdent `
23
+ The directory "${path.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.
24
+ In most cases it should be set to the site's ".next" directory path, unless you have chosen a custom "distDir" in your Next config.
25
+ If you are using "next export" then the Essential Next.js plugin should be removed. See https://ntl.fyi/remove-plugin for details.
26
+ `);
27
+ }
28
+ if (existsSync(path.join(publish, 'export-detail.json'))) {
29
+ failBuild(outdent `
30
+ Detected that "next export" was run, but site is incorrectly publishing the ".next" directory.
31
+ This plugin is not needed for "next export" so should be removed, and publish directory set to "out".
32
+ See https://ntl.fyi/remove-plugin for more details on how to remove this plugin.`);
33
+ }
34
+ };
35
+ exports.checkForRootPublish = ({ publish, failBuild }) => {
36
+ if (path.resolve(publish) === path.resolve('.')) {
37
+ failBuild(outdent `
38
+ 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.
39
+ 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.`);
40
+ }
41
+ };
42
+ // 50MB, which is the documented max, though the hard max seems to be higher
43
+ const LAMBDA_MAX_SIZE = 1024 * 1024 * 50;
44
+ exports.checkZipSize = async (file, maxSize = LAMBDA_MAX_SIZE) => {
45
+ if (!existsSync(file)) {
46
+ console.warn(`Could not check zip size because ${file} does not exist`);
47
+ return;
48
+ }
49
+ const size = await promises.stat(file).then(({ size }) => size);
50
+ if (size < maxSize) {
51
+ return;
52
+ }
53
+ // We don't fail the build, because the actual hard max size is larger so it might still succeed
54
+ console.log(redBright(outdent `
55
+
56
+ The function zip ${yellowBright(relative(process.cwd(), file))} size is ${prettyBytes(size)}, which is larger than the maximum supported size of ${prettyBytes(maxSize)}.
57
+ There are a few reasons this could happen. You may have accidentally bundled a large dependency, or you might have a
58
+ large number of pre-rendered pages included.
59
+
60
+ `));
61
+ const zip = new StreamZip({ file });
62
+ console.log(`Contains ${await zip.entriesCount} files`);
63
+ const sortedFiles = Object.values(await zip.entries()).sort((a, b) => b.size - a.size);
64
+ const largest = {};
65
+ for (let i = 0; i < 10 && i < sortedFiles.length; i++) {
66
+ largest[`${i + 1}`] = {
67
+ File: sortedFiles[i].name,
68
+ 'Compressed Size': prettyBytes(sortedFiles[i].compressedSize),
69
+ 'Uncompressed Size': prettyBytes(sortedFiles[i].size),
70
+ };
71
+ }
72
+ console.log(yellowBright `\n\nThese are the largest files in the zip:`);
73
+ console.table(largest);
74
+ console.log(greenBright `\n\nFor more information on fixing this, see ${blueBright `https://ntl.fyi/large-next-functions`}`);
75
+ };
76
+ exports.logBetaMessage = () => console.log(greenBright(outdent `
77
+ Thank you for trying the Essential Next.js beta plugin.
78
+ Please share feedback (both good and bad) at ${blueBright `https://ntl.fyi/next-beta-feedback`}
79
+ `));
package/lib/index.js ADDED
@@ -0,0 +1,47 @@
1
+ // @ts-check
2
+ const { join, relative } = require('path');
3
+ const { copy, existsSync } = require('fs-extra');
4
+ const { ODB_FUNCTION_NAME } = require('./constants');
5
+ const { restoreCache, saveCache } = require('./helpers/cache');
6
+ const { getNextConfig, configureHandlerFunctions, generateRedirects } = require('./helpers/config');
7
+ const { moveStaticPages, movePublicFiles } = require('./helpers/files');
8
+ const { generateFunctions, setupImageFunction, generatePagesResolver } = require('./helpers/functions');
9
+ const { verifyNetlifyBuildVersion, checkNextSiteHasBuilt, checkForRootPublish, logBetaMessage, checkZipSize, } = require('./helpers/verification');
10
+ module.exports = {
11
+ async onPreBuild({ constants, netlifyConfig, utils: { build: { failBuild }, cache, }, }) {
12
+ var _a;
13
+ logBetaMessage();
14
+ const { publish } = netlifyConfig.build;
15
+ checkForRootPublish({ publish, failBuild });
16
+ verifyNetlifyBuildVersion({ failBuild, ...constants });
17
+ await restoreCache({ cache, publish });
18
+ (_a = netlifyConfig.build).environment || (_a.environment = {});
19
+ // eslint-disable-next-line unicorn/consistent-destructuring
20
+ netlifyConfig.build.environment.NEXT_PRIVATE_TARGET = 'server';
21
+ },
22
+ async onBuild({ constants, netlifyConfig, utils: { build: { failBuild }, }, }) {
23
+ const { publish } = netlifyConfig.build;
24
+ checkNextSiteHasBuilt({ publish, failBuild });
25
+ const { appDir, basePath, i18n, images, target, ignore } = await getNextConfig({ publish, failBuild });
26
+ configureHandlerFunctions({ netlifyConfig, ignore, publish: relative(process.cwd(), publish) });
27
+ await generateFunctions(constants, appDir);
28
+ await generatePagesResolver({ netlifyConfig, target, constants });
29
+ await movePublicFiles({ appDir, publish });
30
+ if (process.env.EXPERIMENTAL_MOVE_STATIC_PAGES) {
31
+ await moveStaticPages({ target, failBuild, netlifyConfig, i18n });
32
+ }
33
+ await setupImageFunction({ constants, imageconfig: images, netlifyConfig, basePath });
34
+ await generateRedirects({
35
+ netlifyConfig,
36
+ basePath,
37
+ i18n,
38
+ });
39
+ },
40
+ async onPostBuild({ netlifyConfig, utils: { cache }, constants: { FUNCTIONS_DIST } }) {
41
+ await saveCache({ cache, publish: netlifyConfig.build.publish });
42
+ await checkZipSize(join(FUNCTIONS_DIST, `${ODB_FUNCTION_NAME}.zip`));
43
+ },
44
+ onEnd() {
45
+ logBetaMessage();
46
+ },
47
+ };