@netlify/plugin-nextjs 4.0.0-beta.1 → 4.0.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,6 +14,9 @@
14
14
  </a>
15
15
  </p>
16
16
 
17
+ ## What's new in this version
18
+
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)
17
20
 
18
21
  ## Installing the beta
19
22
 
@@ -31,4 +34,14 @@ publish = ".next"
31
34
  package = "@netlify/plugin-nextjs"
32
35
  ```
33
36
 
34
- If you previously set `target: "serverless"` in your `next.config.js` this is no longer needed and can be removed.
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.
38
+
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.
40
+
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.
42
+
43
+ If you are using Nx, then you will need to point `publish` to the folder inside `dist`, e.g. `dist/apps/myapp/.next`.
44
+
45
+ ## Beta feedback
46
+
47
+ Please share any thoughts, feedback or questions about the beta [in our discussion](https://github.com/netlify/netlify-plugin-nextjs/discussions/706).
package/lib/constants.js CHANGED
@@ -1,8 +1,6 @@
1
- const destr = require('destr');
2
1
  const HANDLER_FUNCTION_NAME = '___netlify-handler';
3
2
  const ODB_FUNCTION_NAME = '___netlify-odb-handler';
4
3
  const IMAGE_FUNCTION_NAME = '_ipx';
5
- const ODB_PATH = destr(process.env.EXPERIMENTAL_PERSISTENT_BUILDERS) ? 'builders' : 'functions';
6
4
  // These are paths in .next that shouldn't be publicly accessible
7
5
  const HIDDEN_PATHS = [
8
6
  '/cache/*',
@@ -20,5 +18,4 @@ module.exports = {
20
18
  IMAGE_FUNCTION_NAME,
21
19
  HANDLER_FUNCTION_NAME,
22
20
  ODB_FUNCTION_NAME,
23
- ODB_PATH,
24
21
  };
@@ -1,11 +1,12 @@
1
1
  // @ts-check
2
- const { join } = require('path');
3
- const { readJSON } = require('fs-extra');
2
+ const { readJSON, existsSync } = require('fs-extra');
3
+ const { join, dirname, relative } = require('pathe');
4
+ const slash = require('slash');
4
5
  const defaultFailBuild = (message, { error }) => {
5
6
  throw new Error(`${message}\n${error && error.stack}`);
6
7
  };
7
- const { HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME, HIDDEN_PATHS, ODB_PATH } = require('../constants');
8
- const ODB_FUNCTION_PATH = `/.netlify/${ODB_PATH}/${ODB_FUNCTION_NAME}`;
8
+ const { HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME, HIDDEN_PATHS } = require('../constants');
9
+ const ODB_FUNCTION_PATH = `/.netlify/builders/${ODB_FUNCTION_NAME}`;
9
10
  const HANDLER_FUNCTION_PATH = `/.netlify/functions/${HANDLER_FUNCTION_NAME}`;
10
11
  const CATCH_ALL_REGEX = /\/\[\.{3}(.*)](.json)?$/;
11
12
  const OPTIONAL_CATCH_ALL_REGEX = /\/\[{2}\.{3}(.*)]{2}(.json)?$/;
@@ -58,12 +59,23 @@ exports.generateRedirects = async ({ netlifyConfig, basePath, i18n }) => {
58
59
  if (i18n) {
59
60
  netlifyConfig.redirects.push({ from: `${basePath}/:locale/_next/static/*`, to: `/static/:splat`, status: 200 });
60
61
  }
62
+ const staticManifest = join(netlifyConfig.build.publish, 'static-manifest.json');
63
+ if (process.env.EXPERIMENTAL_MOVE_STATIC_PAGES && existsSync(staticManifest)) {
64
+ // Static page files need to have a forced redirect for preview mode. Otherwise it's non-forced
65
+ const staticFiles = await readJSON(staticManifest);
66
+ netlifyConfig.redirects.push(...staticFiles.map((file) => ({
67
+ from: `${basePath}/${file}`,
68
+ to: HANDLER_FUNCTION_PATH,
69
+ status: 200,
70
+ force: true,
71
+ conditions: { Cookie: ['__prerender_bypass', '__next_preview_data'] },
72
+ })));
73
+ }
61
74
  // This is only used in prod, so dev uses `next dev` directly
62
75
  netlifyConfig.redirects.push({ from: `${basePath}/_next/static/*`, to: `/static/:splat`, status: 200 }, {
63
76
  from: `${basePath}/*`,
64
77
  to: HANDLER_FUNCTION_PATH,
65
78
  status: 200,
66
- force: true,
67
79
  conditions: { Cookie: ['__prerender_bypass', '__next_preview_data'] },
68
80
  }, ...redirects.map((redirect) => ({
69
81
  from: `${basePath}${redirect}`,
@@ -73,23 +85,45 @@ exports.generateRedirects = async ({ netlifyConfig, basePath, i18n }) => {
73
85
  };
74
86
  exports.getNextConfig = async function getNextConfig({ publish, failBuild = defaultFailBuild }) {
75
87
  try {
76
- const { config, appDir } = await readJSON(join(publish, 'required-server-files.json'));
88
+ const { config, appDir, ignore } = await readJSON(join(publish, 'required-server-files.json'));
77
89
  if (!config) {
78
90
  return failBuild('Error loading your Next config');
79
91
  }
80
- return { ...config, appDir };
92
+ return { ...config, appDir, ignore };
81
93
  }
82
94
  catch (error) {
83
95
  return failBuild('Error loading your Next config', { error });
84
96
  }
85
97
  };
86
- exports.configureHandlerFunctions = ({ netlifyConfig, publish }) => {
87
- ;
98
+ const resolveModuleRoot = (moduleName) => {
99
+ try {
100
+ return dirname(relative(process.cwd(), require.resolve(`${moduleName}/package.json`, { paths: [process.cwd()] })));
101
+ }
102
+ catch (error) {
103
+ return null;
104
+ }
105
+ };
106
+ const DEFAULT_EXCLUDED_MODULES = ['sharp', 'electron'];
107
+ exports.configureHandlerFunctions = ({ netlifyConfig, publish, ignore = [] }) => {
108
+ var _a;
109
+ /* eslint-disable no-underscore-dangle */
110
+ (_a = netlifyConfig.functions)._ipx || (_a._ipx = {});
111
+ netlifyConfig.functions._ipx.node_bundler = 'nft';
88
112
  [HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME].forEach((functionName) => {
89
113
  var _a, _b;
90
114
  (_a = netlifyConfig.functions)[functionName] || (_a[functionName] = { included_files: [], external_node_modules: [] });
91
115
  netlifyConfig.functions[functionName].node_bundler = 'nft';
92
116
  (_b = netlifyConfig.functions[functionName]).included_files || (_b.included_files = []);
93
- netlifyConfig.functions[functionName].included_files.push(`${publish}/server/**`, `${publish}/serverless/**`, `${publish}/*.json`, `${publish}/BUILD_ID`, '!node_modules/@next/swc-*/**/*', '!node_modules/next/dist/compiled/@ampproject/toolbox-optimizer/**/*', '!node_modules/next/dist/pages/**/*', `!node_modules/next/dist/server/lib/squoosh/**/*.wasm`, `!node_modules/next/dist/next-server/server/lib/squoosh/**/*.wasm`, '!node_modules/next/dist/compiled/webpack/(bundle4|bundle5).js', '!node_modules/react/**/*.development.js', '!node_modules/react-dom/**/*.development.js', '!node_modules/use-subscription/**/*.development.js', '!node_modules/sharp/**/*');
117
+ netlifyConfig.functions[functionName].included_files.push(`${publish}/server/**`, `${publish}/serverless/**`, `${publish}/*.json`, `${publish}/BUILD_ID`, `${publish}/static/chunks/webpack-middleware*.js`, ...ignore.map((path) => `!${slash(path)}`));
118
+ const nextRoot = resolveModuleRoot('next');
119
+ if (nextRoot) {
120
+ 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`);
121
+ }
122
+ DEFAULT_EXCLUDED_MODULES.forEach((moduleName) => {
123
+ const moduleRoot = resolveModuleRoot(moduleName);
124
+ if (moduleRoot) {
125
+ netlifyConfig.functions[functionName].included_files.push(`!${moduleRoot}/**/*`);
126
+ }
127
+ });
94
128
  });
95
129
  };
@@ -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
+ };
@@ -1,6 +1,6 @@
1
- const { join, relative } = require('path');
2
1
  const { copyFile, ensureDir, writeFile, writeJSON } = require('fs-extra');
3
- const { HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME, IMAGE_FUNCTION_NAME, ODB_PATH } = require('../constants');
2
+ const { join, relative } = require('pathe');
3
+ const { HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME, IMAGE_FUNCTION_NAME } = require('../constants');
4
4
  const getHandler = require('../templates/getHandler');
5
5
  const { getPageResolver } = require('../templates/getPageResolver');
6
6
  const DEFAULT_FUNCTIONS_SRC = 'netlify/functions';
@@ -50,7 +50,7 @@ exports.setupImageFunction = async ({ constants: { INTERNAL_FUNCTIONS_SRC, FUNCT
50
50
  status: 301,
51
51
  }, {
52
52
  from: `${basePath}/${IMAGE_FUNCTION_NAME}/*`,
53
- to: `/.netlify/${ODB_PATH}/${IMAGE_FUNCTION_NAME}`,
53
+ to: `/.netlify/builders/${IMAGE_FUNCTION_NAME}`,
54
54
  status: 200,
55
55
  });
56
56
  if (basePath) {
@@ -1,13 +1,11 @@
1
+ const { existsSync, promises } = require('fs');
1
2
  const path = require('path');
2
- const { yellowBright } = require('chalk');
3
- const { existsSync } = require('fs-extra');
3
+ const { relative } = require('path');
4
+ const { yellowBright, greenBright, blueBright, redBright } = require('chalk');
5
+ const { async: StreamZip } = require('node-stream-zip');
4
6
  const outdent = require('outdent');
7
+ const prettyBytes = require('pretty-bytes');
5
8
  const { satisfies } = require('semver');
6
- exports.verifyBuildTarget = (target) => {
7
- if (target !== 'server') {
8
- console.log(yellowBright `Setting target to ${target} is no longer required. You should check if target=server works for you.`);
9
- }
10
- };
11
9
  // This is when nft support was added
12
10
  const REQUIRED_BUILD_VERSION = '>=18.16.0';
13
11
  exports.verifyNetlifyBuildVersion = ({ IS_LOCAL, NETLIFY_BUILD_VERSION, failBuild }) => {
@@ -41,3 +39,41 @@ exports.checkForRootPublish = ({ publish, failBuild }) => {
41
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.`);
42
40
  }
43
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 CHANGED
@@ -1,28 +1,34 @@
1
1
  // @ts-check
2
2
  const { join, relative } = require('path');
3
3
  const { copy, existsSync } = require('fs-extra');
4
+ const { ODB_FUNCTION_NAME } = require('./constants');
4
5
  const { restoreCache, saveCache } = require('./helpers/cache');
5
6
  const { getNextConfig, configureHandlerFunctions, generateRedirects } = require('./helpers/config');
7
+ const { moveStaticPages, movePublicFiles } = require('./helpers/files');
6
8
  const { generateFunctions, setupImageFunction, generatePagesResolver } = require('./helpers/functions');
7
- const { verifyNetlifyBuildVersion, checkNextSiteHasBuilt, verifyBuildTarget, checkForRootPublish, } = require('./helpers/verification');
9
+ const { verifyNetlifyBuildVersion, checkNextSiteHasBuilt, checkForRootPublish, logBetaMessage, checkZipSize, } = require('./helpers/verification');
8
10
  module.exports = {
9
11
  async onPreBuild({ constants, netlifyConfig, utils: { build: { failBuild }, cache, }, }) {
12
+ var _a;
13
+ logBetaMessage();
10
14
  const { publish } = netlifyConfig.build;
11
15
  checkForRootPublish({ publish, failBuild });
12
16
  verifyNetlifyBuildVersion({ failBuild, ...constants });
13
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';
14
21
  },
15
22
  async onBuild({ constants, netlifyConfig, utils: { build: { failBuild }, }, }) {
16
23
  const { publish } = netlifyConfig.build;
17
24
  checkNextSiteHasBuilt({ publish, failBuild });
18
- const { appDir, basePath, i18n, images, target } = await getNextConfig({ publish, failBuild });
19
- verifyBuildTarget(target);
20
- configureHandlerFunctions({ netlifyConfig, publish: relative(process.cwd(), publish) });
25
+ const { appDir, basePath, i18n, images, target, ignore } = await getNextConfig({ publish, failBuild });
26
+ configureHandlerFunctions({ netlifyConfig, ignore, publish: relative(process.cwd(), publish) });
21
27
  await generateFunctions(constants, appDir);
22
28
  await generatePagesResolver({ netlifyConfig, target, constants });
23
- const publicDir = join(appDir, 'public');
24
- if (existsSync(publicDir)) {
25
- await copy(publicDir, `${publish}/`);
29
+ await movePublicFiles({ appDir, publish });
30
+ if (process.env.EXPERIMENTAL_MOVE_STATIC_PAGES) {
31
+ await moveStaticPages({ target, failBuild, netlifyConfig, i18n });
26
32
  }
27
33
  await setupImageFunction({ constants, imageconfig: images, netlifyConfig, basePath });
28
34
  await generateRedirects({
@@ -31,7 +37,11 @@ module.exports = {
31
37
  i18n,
32
38
  });
33
39
  },
34
- async onPostBuild({ netlifyConfig, utils: { cache } }) {
35
- return saveCache({ cache, publish: netlifyConfig.build.publish });
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();
36
46
  },
37
47
  };
@@ -1,30 +1,86 @@
1
+ const { promises, createWriteStream, existsSync } = require('fs');
1
2
  const { Server } = require('http');
3
+ const { tmpdir } = require('os');
2
4
  const path = require('path');
5
+ const { promisify } = require('util');
6
+ const streamPipeline = promisify(require('stream').pipeline);
3
7
  const { Bridge } = require('@vercel/node/dist/bridge');
8
+ const fetch = require('node-fetch');
4
9
  const makeHandler = () =>
5
10
  // We return a function and then call `toString()` on it to serialise it as the launcher function
6
- (conf, app) => {
11
+ (conf, app, pageRoot, staticManifest = []) => {
12
+ // This is just so nft knows about the page entrypoints. It's not actually used
13
+ try {
14
+ // eslint-disable-next-line node/no-missing-require
15
+ require.resolve('./pages.js');
16
+ }
17
+ catch { }
18
+ // Set during the request as it needs the host header. Hoisted so we can define the function once
19
+ let base;
20
+ // Only do this if we have some static files moved to the CDN
21
+ if (staticManifest.length !== 0) {
22
+ // These are static page files that have been removed from the function bundle
23
+ // In most cases these are served from the CDN, but for rewrites Next may try to read them
24
+ // from disk. We need to intercept these and load them from the CDN instead
25
+ // Sadly the only way to do this is to monkey-patch fs.promises. Yeah, I know.
26
+ const staticFiles = new Set(staticManifest);
27
+ // Yes, you can cache stuff locally in a Lambda
28
+ const cacheDir = path.join(tmpdir(), 'next-static-cache');
29
+ // Grab the real fs.promises.readFile...
30
+ const readfileOrig = promises.readFile;
31
+ // ...then money-patch it to see if it's requesting a CDN file
32
+ promises.readFile = async (file, options) => {
33
+ // We only care about page files
34
+ if (file.startsWith(pageRoot)) {
35
+ // We only want the part after `pages/`
36
+ const filePath = file.slice(pageRoot.length + 1);
37
+ // Is it in the CDN and not local?
38
+ if (staticFiles.has(filePath) && !existsSync(file)) {
39
+ // This name is safe to use, because it's one that was already created by Next
40
+ const cacheFile = path.join(cacheDir, filePath);
41
+ // Have we already cached it? We ignore the cache if running locally to avoid staleness
42
+ if ((!existsSync(cacheFile) || process.env.NETLIFY_DEV) && base) {
43
+ await promises.mkdir(path.dirname(cacheFile), { recursive: true });
44
+ // Append the path to our host and we can load it like a regular page
45
+ const url = `${base}/${filePath}`;
46
+ console.log(`Downloading ${url} to ${cacheFile}`);
47
+ const response = await fetch(url);
48
+ if (!response.ok) {
49
+ // Next catches this and returns it as a not found file
50
+ throw new Error(`Failed to fetch ${url}`);
51
+ }
52
+ // Stream it to disk
53
+ await streamPipeline(response.body, createWriteStream(cacheFile));
54
+ }
55
+ // Return the cache file
56
+ return readfileOrig(cacheFile, options);
57
+ }
58
+ }
59
+ return readfileOrig(file, options);
60
+ };
61
+ }
7
62
  let NextServer;
8
63
  try {
9
64
  // next >= 11.0.1. Yay breaking changes in patch releases!
10
65
  NextServer = require('next/dist/server/next-server').default;
11
66
  }
12
- catch {
67
+ catch (error) {
68
+ if (!error.message.includes("Cannot find module 'next/dist/server/next-server'")) {
69
+ // A different error, so rethrow it
70
+ throw error;
71
+ }
13
72
  // Probably an old version of next
14
73
  }
15
- // This is just so nft knows about the page entrypoints
16
- try {
17
- // eslint-disable-next-line node/no-missing-require
18
- require.resolve('./pages.js');
19
- }
20
- catch { }
21
74
  if (!NextServer) {
22
75
  try {
23
76
  // next < 11.0.1
24
77
  // eslint-disable-next-line node/no-missing-require, import/no-unresolved
25
78
  NextServer = require('next/dist/next-server/server/next-server').default;
26
79
  }
27
- catch {
80
+ catch (error) {
81
+ if (!error.message.includes("Cannot find module 'next/dist/next-server/server/next-server'")) {
82
+ throw error;
83
+ }
28
84
  throw new Error('Could not find Next.js server');
29
85
  }
30
86
  }
@@ -46,6 +102,16 @@ const makeHandler = () =>
46
102
  const bridge = new Bridge(server);
47
103
  bridge.listen();
48
104
  return async (event, context) => {
105
+ var _a, _b, _c, _d;
106
+ // Next expects to be able to parse the query from the URL
107
+ const query = new URLSearchParams(event.queryStringParameters).toString();
108
+ event.path = query ? `${event.path}?${query}` : event.path;
109
+ // Only needed if we're intercepting static files
110
+ if (staticManifest.length !== 0) {
111
+ const { host } = event.headers;
112
+ const protocol = event.headers['x-forwarded-proto'] || 'http';
113
+ base = `${protocol}://${host}`;
114
+ }
49
115
  const { headers, ...result } = await bridge.launcher(event, context);
50
116
  /** @type import("@netlify/functions").HandlerResponse */
51
117
  // Convert all headers to multiValueHeaders
@@ -58,12 +124,14 @@ const makeHandler = () =>
58
124
  multiValueHeaders[key] = [headers[key]];
59
125
  }
60
126
  }
61
- if (multiValueHeaders['set-cookie'] &&
62
- multiValueHeaders['set-cookie'][0] &&
63
- multiValueHeaders['set-cookie'][0].includes('__prerender_bypass')) {
127
+ if ((_b = (_a = multiValueHeaders['set-cookie']) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.includes('__prerender_bypass')) {
64
128
  delete multiValueHeaders.etag;
65
129
  multiValueHeaders['cache-control'] = ['no-cache'];
66
130
  }
131
+ // Sending SWR headers causes undefined behaviour with the Netlify CDN
132
+ if ((_d = (_c = multiValueHeaders['cache-control']) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.includes('stale-while-revalidate')) {
133
+ multiValueHeaders['cache-control'] = ['public, max-age=0, must-revalidate'];
134
+ }
67
135
  return {
68
136
  ...result,
69
137
  multiValueHeaders,
@@ -73,13 +141,24 @@ const makeHandler = () =>
73
141
  };
74
142
  const getHandler = ({ isODB = false, publishDir = '../../../.next', appDir = '../../..' }) => `
75
143
  const { Server } = require("http");
144
+ const { tmpdir } = require('os')
145
+ const { promises, createWriteStream, existsSync } = require("fs");
146
+ const { promisify } = require('util')
147
+ const streamPipeline = promisify(require('stream').pipeline)
76
148
  // We copy the file here rather than requiring from the node module
77
149
  const { Bridge } = require("./bridge");
150
+ const fetch = require('node-fetch')
151
+
78
152
  const { builder } = require("@netlify/functions");
79
153
  const { config } = require("${publishDir}/required-server-files.json")
154
+ let staticManifest
155
+ try {
156
+ staticManifest = require("${publishDir}/static-manifest.json")
157
+ } catch {}
80
158
  const path = require("path");
159
+ const pageRoot = path.resolve(path.join(__dirname, "${publishDir}", config.target === "server" ? "server" : "serverless", "pages"));
81
160
  exports.handler = ${isODB
82
- ? `builder((${makeHandler().toString()})(config, "${appDir}"));`
83
- : `(${makeHandler().toString()})(config, "${appDir}");`}
161
+ ? `builder((${makeHandler().toString()})(config, "${appDir}", pageRoot, staticManifest));`
162
+ : `(${makeHandler().toString()})(config, "${appDir}", pageRoot, staticManifest);`}
84
163
  `;
85
164
  module.exports = getHandler;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/plugin-nextjs",
3
- "version": "4.0.0-beta.1",
3
+ "version": "4.0.0-beta.5",
4
4
  "description": "Run Next.js seamlessly on Netlify",
5
5
  "main": "lib/index.js",
6
6
  "files": [
@@ -52,14 +52,18 @@
52
52
  },
53
53
  "homepage": "https://github.com/netlify/netlify-plugin-nextjs#readme",
54
54
  "dependencies": {
55
- "@netlify/functions": "^0.7.2",
55
+ "@netlify/functions": "^0.8.0",
56
56
  "@netlify/ipx": "^0.0.7",
57
57
  "@vercel/node": "^1.11.2-canary.4",
58
58
  "chalk": "^4.1.2",
59
- "destr": "^1.1.0",
60
59
  "fs-extra": "^10.0.0",
61
60
  "moize": "^6.1.0",
61
+ "node-fetch": "^2.6.6",
62
+ "node-stream-zip": "^1.15.0",
62
63
  "outdent": "^0.8.0",
64
+ "p-limit": "^3.1.0",
65
+ "pathe": "^0.2.0",
66
+ "pretty-bytes": "^5.6.0",
63
67
  "semver": "^7.3.5",
64
68
  "slash": "^3.0.0",
65
69
  "tiny-glob": "^0.2.9"
@@ -69,6 +73,7 @@
69
73
  "@babel/preset-env": "^7.15.8",
70
74
  "@netlify/eslint-config-node": "^3.3.0",
71
75
  "@testing-library/cypress": "^8.0.1",
76
+ "@types/fs-extra": "^9.0.13",
72
77
  "@types/jest": "^27.0.2",
73
78
  "@types/mocha": "^9.0.0",
74
79
  "babel-jest": "^27.2.5",
@@ -78,7 +83,7 @@
78
83
  "husky": "^4.3.0",
79
84
  "jest": "^27.0.0",
80
85
  "netlify-plugin-cypress": "^2.2.0",
81
- "next": "^11.1.3-canary.70",
86
+ "next": "^12.0.2",
82
87
  "npm-run-all": "^4.1.5",
83
88
  "prettier": "^2.1.2",
84
89
  "react": "^17.0.1",