@netlify/plugin-nextjs 4.0.0-beta.7 → 4.0.0-rc.1
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/lib/constants.js +17 -10
- package/lib/helpers/cache.js +11 -6
- package/lib/helpers/config.js +20 -77
- package/lib/helpers/files.js +232 -10
- package/lib/helpers/functions.js +35 -29
- package/lib/helpers/redirects.js +130 -0
- package/lib/helpers/utils.js +32 -0
- package/lib/helpers/verification.js +17 -17
- package/lib/index.js +18 -7
- package/lib/templates/getHandler.js +37 -93
- package/lib/templates/getPageResolver.js +20 -11
- package/lib/templates/handlerUtils.js +162 -0
- package/lib/templates/ipx.js +11 -7
- package/package.json +13 -10
- package/lib/.DS_Store +0 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.generateRedirects = void 0;
|
|
7
|
+
const fs_extra_1 = require("fs-extra");
|
|
8
|
+
const globby_1 = __importDefault(require("globby"));
|
|
9
|
+
const pathe_1 = require("pathe");
|
|
10
|
+
const constants_1 = require("../constants");
|
|
11
|
+
const utils_1 = require("./utils");
|
|
12
|
+
const generateLocaleRedirects = ({ i18n, basePath, trailingSlash, }) => {
|
|
13
|
+
const redirects = [];
|
|
14
|
+
// If the cookie is set, we need to redirect at the origin
|
|
15
|
+
redirects.push({
|
|
16
|
+
from: `${basePath}/`,
|
|
17
|
+
to: constants_1.HANDLER_FUNCTION_PATH,
|
|
18
|
+
status: 200,
|
|
19
|
+
force: true,
|
|
20
|
+
conditions: {
|
|
21
|
+
Cookie: ['NEXT_LOCALE'],
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
i18n.locales.forEach((locale) => {
|
|
25
|
+
if (locale === i18n.defaultLocale) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
redirects.push({
|
|
29
|
+
from: `${basePath}/`,
|
|
30
|
+
to: `${basePath}/${locale}${trailingSlash ? '/' : ''}`,
|
|
31
|
+
status: 301,
|
|
32
|
+
conditions: {
|
|
33
|
+
Language: [locale],
|
|
34
|
+
},
|
|
35
|
+
force: true,
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
return redirects;
|
|
39
|
+
};
|
|
40
|
+
const generateRedirects = async ({ netlifyConfig, nextConfig: { i18n, basePath, trailingSlash, appDir }, }) => {
|
|
41
|
+
const { dynamicRoutes, routes: staticRoutes } = await fs_extra_1.readJSON(pathe_1.join(netlifyConfig.build.publish, 'prerender-manifest.json'));
|
|
42
|
+
netlifyConfig.redirects.push(...constants_1.HIDDEN_PATHS.map((path) => ({
|
|
43
|
+
from: `${basePath}${path}`,
|
|
44
|
+
to: '/404.html',
|
|
45
|
+
status: 404,
|
|
46
|
+
force: true,
|
|
47
|
+
})));
|
|
48
|
+
if (i18n && i18n.localeDetection !== false) {
|
|
49
|
+
netlifyConfig.redirects.push(...generateLocaleRedirects({ i18n, basePath, trailingSlash }));
|
|
50
|
+
}
|
|
51
|
+
const dataRedirects = [];
|
|
52
|
+
const pageRedirects = [];
|
|
53
|
+
const isrRedirects = [];
|
|
54
|
+
const dynamicRouteEntries = Object.entries(dynamicRoutes);
|
|
55
|
+
const staticRouteEntries = Object.entries(staticRoutes);
|
|
56
|
+
staticRouteEntries.forEach(([route, { dataRoute, initialRevalidateSeconds }]) => {
|
|
57
|
+
// Only look for revalidate as we need to rewrite these to SSR rather than ODB
|
|
58
|
+
if (initialRevalidateSeconds === false) {
|
|
59
|
+
// These can be ignored, as they're static files handled by the CDN
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if ((i18n === null || i18n === void 0 ? void 0 : i18n.defaultLocale) && route.startsWith(`/${i18n.defaultLocale}/`)) {
|
|
63
|
+
route = route.slice(i18n.defaultLocale.length + 1);
|
|
64
|
+
}
|
|
65
|
+
isrRedirects.push(...utils_1.netlifyRoutesForNextRoute(dataRoute), ...utils_1.netlifyRoutesForNextRoute(route));
|
|
66
|
+
});
|
|
67
|
+
dynamicRouteEntries.forEach(([route, { dataRoute, fallback }]) => {
|
|
68
|
+
// Add redirects if fallback is "null" (aka blocking) or true/a string
|
|
69
|
+
if (fallback === false) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
pageRedirects.push(...utils_1.netlifyRoutesForNextRoute(route));
|
|
73
|
+
dataRedirects.push(...utils_1.netlifyRoutesForNextRoute(dataRoute));
|
|
74
|
+
});
|
|
75
|
+
if (i18n) {
|
|
76
|
+
netlifyConfig.redirects.push({ from: `${basePath}/:locale/_next/static/*`, to: `/static/:splat`, status: 200 });
|
|
77
|
+
}
|
|
78
|
+
const publicFiles = await globby_1.default('**/*', { cwd: pathe_1.join(appDir, 'public') });
|
|
79
|
+
// This is only used in prod, so dev uses `next dev` directly
|
|
80
|
+
netlifyConfig.redirects.push(
|
|
81
|
+
// Static files are in `static`
|
|
82
|
+
{ from: `${basePath}/_next/static/*`, to: `/static/:splat`, status: 200 },
|
|
83
|
+
// API routes always need to be served from the regular function
|
|
84
|
+
{
|
|
85
|
+
from: `${basePath}/api`,
|
|
86
|
+
to: constants_1.HANDLER_FUNCTION_PATH,
|
|
87
|
+
status: 200,
|
|
88
|
+
}, {
|
|
89
|
+
from: `${basePath}/api/*`,
|
|
90
|
+
to: constants_1.HANDLER_FUNCTION_PATH,
|
|
91
|
+
status: 200,
|
|
92
|
+
},
|
|
93
|
+
// Preview mode gets forced to the function, to bypass pre-rendered pages, but static files need to be skipped
|
|
94
|
+
...publicFiles.map((file) => ({
|
|
95
|
+
from: `${basePath}/${file}`,
|
|
96
|
+
// This is a no-op, but we do it to stop it matching the following rule
|
|
97
|
+
to: `${basePath}/${file}`,
|
|
98
|
+
conditions: { Cookie: ['__prerender_bypass', '__next_preview_data'] },
|
|
99
|
+
status: 200,
|
|
100
|
+
})), {
|
|
101
|
+
from: `${basePath}/*`,
|
|
102
|
+
to: constants_1.HANDLER_FUNCTION_PATH,
|
|
103
|
+
status: 200,
|
|
104
|
+
conditions: { Cookie: ['__prerender_bypass', '__next_preview_data'] },
|
|
105
|
+
force: true,
|
|
106
|
+
},
|
|
107
|
+
// ISR redirects are handled by the regular function. Forced to avoid pre-rendered pages
|
|
108
|
+
...isrRedirects.map((redirect) => ({
|
|
109
|
+
from: `${basePath}${redirect}`,
|
|
110
|
+
to: constants_1.ODB_FUNCTION_PATH,
|
|
111
|
+
status: 200,
|
|
112
|
+
force: true,
|
|
113
|
+
})),
|
|
114
|
+
// These are pages with fallback set, which need an ODB
|
|
115
|
+
// Data redirects go first, to avoid conflict with splat redirects
|
|
116
|
+
...dataRedirects.map((redirect) => ({
|
|
117
|
+
from: `${basePath}${redirect}`,
|
|
118
|
+
to: constants_1.ODB_FUNCTION_PATH,
|
|
119
|
+
status: 200,
|
|
120
|
+
})),
|
|
121
|
+
// ...then all the other fallback pages
|
|
122
|
+
...pageRedirects.map((redirect) => ({
|
|
123
|
+
from: `${basePath}${redirect}`,
|
|
124
|
+
to: constants_1.ODB_FUNCTION_PATH,
|
|
125
|
+
status: 200,
|
|
126
|
+
})),
|
|
127
|
+
// Everything else is handled by the regular function
|
|
128
|
+
{ from: `${basePath}/*`, to: constants_1.HANDLER_FUNCTION_PATH, status: 200 });
|
|
129
|
+
};
|
|
130
|
+
exports.generateRedirects = generateRedirects;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.netlifyRoutesForNextRoute = void 0;
|
|
4
|
+
const constants_1 = require("../constants");
|
|
5
|
+
const netlifyRoutesForNextRoute = (nextRoute) => {
|
|
6
|
+
const netlifyRoutes = [nextRoute];
|
|
7
|
+
// If the route is an optional catch-all route, we need to add a second
|
|
8
|
+
// Netlify route for the base path (when no parameters are present).
|
|
9
|
+
// The file ending must be present!
|
|
10
|
+
if (constants_1.OPTIONAL_CATCH_ALL_REGEX.test(nextRoute)) {
|
|
11
|
+
let netlifyRoute = nextRoute.replace(constants_1.OPTIONAL_CATCH_ALL_REGEX, '$2');
|
|
12
|
+
// create an empty string, but actually needs to be a forward slash
|
|
13
|
+
if (netlifyRoute === '') {
|
|
14
|
+
netlifyRoute = '/';
|
|
15
|
+
}
|
|
16
|
+
// When optional catch-all route is at top-level, the regex on line 19 will
|
|
17
|
+
// create an incorrect route for the data route. For example, it creates
|
|
18
|
+
// /_next/data/%BUILDID%.json, but NextJS looks for
|
|
19
|
+
// /_next/data/%BUILDID%/index.json
|
|
20
|
+
netlifyRoute = netlifyRoute.replace(/(\/_next\/data\/[^/]+).json/, '$1/index.json');
|
|
21
|
+
// Add second route to the front of the array
|
|
22
|
+
netlifyRoutes.unshift(netlifyRoute);
|
|
23
|
+
}
|
|
24
|
+
return netlifyRoutes.map((route) => route
|
|
25
|
+
// Replace catch-all, e.g., [...slug]
|
|
26
|
+
.replace(constants_1.CATCH_ALL_REGEX, '/:$1/*')
|
|
27
|
+
// Replace optional catch-all, e.g., [[...slug]]
|
|
28
|
+
.replace(constants_1.OPTIONAL_CATCH_ALL_REGEX, '/*')
|
|
29
|
+
// Replace dynamic parameters, e.g., [id]
|
|
30
|
+
.replace(constants_1.DYNAMIC_PARAMETER_REGEX, '/:$1'));
|
|
31
|
+
};
|
|
32
|
+
exports.netlifyRoutesForNextRoute = netlifyRoutesForNextRoute;
|
|
@@ -18,18 +18,18 @@ exports.verifyNetlifyBuildVersion = ({ IS_LOCAL, NETLIFY_BUILD_VERSION, failBuil
|
|
|
18
18
|
}
|
|
19
19
|
};
|
|
20
20
|
exports.checkForOldFunctions = async ({ functions }) => {
|
|
21
|
-
const
|
|
21
|
+
const allOldFunctions = await functions.list();
|
|
22
|
+
const oldFunctions = allOldFunctions.filter(({ name }) => name.startsWith('next_'));
|
|
22
23
|
if (oldFunctions.length !== 0) {
|
|
23
24
|
console.log(yellowBright(outdent `
|
|
24
|
-
|
|
25
|
+
We have found the following functions in your site that seem to be left over from the old Next.js plugin (v3). We have guessed this because the name starts with "next_".
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
${reset(oldFunctions.map(({ name }) => `- ${name}`).join('\n'))}
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
If they were created by the old plugin, these functions are likely to cause errors so should be removed. You can do this by deleting the following directories:
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
`));
|
|
31
|
+
${reset(oldFunctions.map(({ mainFile }) => `- ${path.relative(process.cwd(), path.dirname(mainFile))}`).join('\n'))}
|
|
32
|
+
`));
|
|
33
33
|
}
|
|
34
34
|
};
|
|
35
35
|
exports.checkNextSiteHasBuilt = ({ publish, failBuild }) => {
|
|
@@ -38,20 +38,22 @@ exports.checkNextSiteHasBuilt = ({ publish, failBuild }) => {
|
|
|
38
38
|
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.
|
|
39
39
|
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.
|
|
40
40
|
If you are using "next export" then the Essential Next.js plugin should be removed. See https://ntl.fyi/remove-plugin for details.
|
|
41
|
-
|
|
41
|
+
`);
|
|
42
42
|
}
|
|
43
43
|
if (existsSync(path.join(publish, 'export-detail.json'))) {
|
|
44
44
|
failBuild(outdent `
|
|
45
45
|
Detected that "next export" was run, but site is incorrectly publishing the ".next" directory.
|
|
46
46
|
This plugin is not needed for "next export" so should be removed, and publish directory set to "out".
|
|
47
|
-
See https://ntl.fyi/remove-plugin for more details on how to remove this plugin
|
|
47
|
+
See https://ntl.fyi/remove-plugin for more details on how to remove this plugin.
|
|
48
|
+
`);
|
|
48
49
|
}
|
|
49
50
|
};
|
|
50
51
|
exports.checkForRootPublish = ({ publish, failBuild }) => {
|
|
51
52
|
if (path.resolve(publish) === path.resolve('.')) {
|
|
52
53
|
failBuild(outdent `
|
|
53
54
|
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.
|
|
54
|
-
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
|
|
55
|
+
In most cases it should be set to ".next", unless you have chosen a custom "distDir" in your Next config, or the Next site is in a subdirectory.
|
|
56
|
+
`);
|
|
55
57
|
}
|
|
56
58
|
};
|
|
57
59
|
// 50MB, which is the documented max, though the hard max seems to be higher
|
|
@@ -61,17 +63,15 @@ exports.checkZipSize = async (file, maxSize = LAMBDA_MAX_SIZE) => {
|
|
|
61
63
|
console.warn(`Could not check zip size because ${file} does not exist`);
|
|
62
64
|
return;
|
|
63
65
|
}
|
|
64
|
-
const
|
|
65
|
-
if (
|
|
66
|
+
const fileSize = await promises.stat(file).then(({ size }) => size);
|
|
67
|
+
if (fileSize < maxSize) {
|
|
66
68
|
return;
|
|
67
69
|
}
|
|
68
70
|
// We don't fail the build, because the actual hard max size is larger so it might still succeed
|
|
69
71
|
console.log(redBright(outdent `
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
large number of pre-rendered pages included.
|
|
74
|
-
|
|
72
|
+
The function zip ${yellowBright(relative(process.cwd(), file))} size is ${prettyBytes(fileSize)}, which is larger than the maximum supported size of ${prettyBytes(maxSize)}.
|
|
73
|
+
There are a few reasons this could happen. You may have accidentally bundled a large dependency, or you might have a
|
|
74
|
+
large number of pre-rendered pages included.
|
|
75
75
|
`));
|
|
76
76
|
const zip = new StreamZip({ file });
|
|
77
77
|
console.log(`Contains ${await zip.entriesCount} files`);
|
package/lib/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
const { join, relative } = require('path');
|
|
2
2
|
const { ODB_FUNCTION_NAME } = require('./constants');
|
|
3
3
|
const { restoreCache, saveCache } = require('./helpers/cache');
|
|
4
|
-
const { getNextConfig, configureHandlerFunctions
|
|
5
|
-
const { moveStaticPages, movePublicFiles } = require('./helpers/files');
|
|
4
|
+
const { getNextConfig, configureHandlerFunctions } = require('./helpers/config');
|
|
5
|
+
const { moveStaticPages, movePublicFiles, patchNextFiles, unpatchNextFiles } = require('./helpers/files');
|
|
6
6
|
const { generateFunctions, setupImageFunction, generatePagesResolver } = require('./helpers/functions');
|
|
7
|
+
const { generateRedirects } = require('./helpers/redirects');
|
|
7
8
|
const { verifyNetlifyBuildVersion, checkNextSiteHasBuilt, checkForRootPublish, logBetaMessage, checkZipSize, checkForOldFunctions, } = require('./helpers/verification');
|
|
8
9
|
/** @type import("@netlify/build").NetlifyPlugin */
|
|
9
10
|
module.exports = {
|
|
@@ -21,25 +22,35 @@ module.exports = {
|
|
|
21
22
|
async onBuild({ constants, netlifyConfig, utils: { build: { failBuild }, }, }) {
|
|
22
23
|
const { publish } = netlifyConfig.build;
|
|
23
24
|
checkNextSiteHasBuilt({ publish, failBuild });
|
|
24
|
-
const { appDir, basePath, i18n, images, target, ignore } = await getNextConfig({
|
|
25
|
+
const { appDir, basePath, i18n, images, target, ignore, trailingSlash, outdir } = await getNextConfig({
|
|
26
|
+
publish,
|
|
27
|
+
failBuild,
|
|
28
|
+
});
|
|
25
29
|
configureHandlerFunctions({ netlifyConfig, ignore, publish: relative(process.cwd(), publish) });
|
|
26
30
|
await generateFunctions(constants, appDir);
|
|
27
31
|
await generatePagesResolver({ netlifyConfig, target, constants });
|
|
28
|
-
await movePublicFiles({ appDir, publish });
|
|
32
|
+
await movePublicFiles({ appDir, outdir, publish });
|
|
33
|
+
if (process.env.EXPERIMENTAL_ODB_TTL) {
|
|
34
|
+
await patchNextFiles(basePath);
|
|
35
|
+
}
|
|
29
36
|
if (process.env.EXPERIMENTAL_MOVE_STATIC_PAGES) {
|
|
37
|
+
console.log("The flag 'EXPERIMENTAL_MOVE_STATIC_PAGES' is no longer required, as it is now the default. To disable this behavior, set the env var 'SERVE_STATIC_FILES_FROM_ORIGIN' to 'true'");
|
|
38
|
+
}
|
|
39
|
+
if (!process.env.SERVE_STATIC_FILES_FROM_ORIGIN) {
|
|
30
40
|
await moveStaticPages({ target, failBuild, netlifyConfig, i18n });
|
|
31
41
|
}
|
|
32
42
|
await setupImageFunction({ constants, imageconfig: images, netlifyConfig, basePath });
|
|
33
43
|
await generateRedirects({
|
|
34
44
|
netlifyConfig,
|
|
35
|
-
basePath,
|
|
36
|
-
i18n,
|
|
45
|
+
nextConfig: { basePath, i18n, trailingSlash, appDir },
|
|
37
46
|
});
|
|
38
47
|
},
|
|
39
|
-
async onPostBuild({ netlifyConfig, utils: { cache, functions }, constants: { FUNCTIONS_DIST } }) {
|
|
48
|
+
async onPostBuild({ netlifyConfig, utils: { cache, functions, failBuild }, constants: { FUNCTIONS_DIST } }) {
|
|
40
49
|
await saveCache({ cache, publish: netlifyConfig.build.publish });
|
|
41
50
|
await checkForOldFunctions({ functions });
|
|
42
51
|
await checkZipSize(join(FUNCTIONS_DIST, `${ODB_FUNCTION_NAME}.zip`));
|
|
52
|
+
const { basePath } = await getNextConfig({ publish: netlifyConfig.build.publish, failBuild });
|
|
53
|
+
await unpatchNextFiles(basePath);
|
|
43
54
|
},
|
|
44
55
|
onEnd() {
|
|
45
56
|
logBetaMessage();
|
|
@@ -1,89 +1,32 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getHandler = void 0;
|
|
4
|
+
const { promises } = require('fs');
|
|
2
5
|
const { Server } = require('http');
|
|
3
|
-
const { tmpdir } = require('os');
|
|
4
6
|
const path = require('path');
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
+
// eslint-disable-next-line node/prefer-global/url, node/prefer-global/url-search-params
|
|
8
|
+
const { URLSearchParams, URL } = require('url');
|
|
7
9
|
const { Bridge } = require('@vercel/node/dist/bridge');
|
|
8
|
-
const
|
|
10
|
+
const { augmentFsModule, getMaxAge, getMultiValueHeaders, getNextServer } = require('./handlerUtils');
|
|
9
11
|
const makeHandler = () =>
|
|
10
12
|
// We return a function and then call `toString()` on it to serialise it as the launcher function
|
|
11
|
-
|
|
13
|
+
// eslint-disable-next-line max-params
|
|
14
|
+
(conf, app, pageRoot, staticManifest = [], mode = 'ssr') => {
|
|
12
15
|
// This is just so nft knows about the page entrypoints. It's not actually used
|
|
13
16
|
try {
|
|
14
17
|
// eslint-disable-next-line node/no-missing-require
|
|
15
18
|
require.resolve('./pages.js');
|
|
16
19
|
}
|
|
17
20
|
catch { }
|
|
21
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
22
|
+
process.env._BYPASS_SSG = 'true';
|
|
23
|
+
const ONE_YEAR_IN_SECONDS = 31536000;
|
|
24
|
+
// We don't want to write ISR files to disk in the lambda environment
|
|
25
|
+
conf.experimental.isrFlushToDisk = false;
|
|
18
26
|
// Set during the request as it needs the host header. Hoisted so we can define the function once
|
|
19
27
|
let base;
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
}
|
|
62
|
-
let NextServer;
|
|
63
|
-
try {
|
|
64
|
-
// next >= 11.0.1. Yay breaking changes in patch releases!
|
|
65
|
-
NextServer = require('next/dist/server/next-server').default;
|
|
66
|
-
}
|
|
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
|
-
}
|
|
72
|
-
// Probably an old version of next
|
|
73
|
-
}
|
|
74
|
-
if (!NextServer) {
|
|
75
|
-
try {
|
|
76
|
-
// next < 11.0.1
|
|
77
|
-
// eslint-disable-next-line node/no-missing-require, import/no-unresolved
|
|
78
|
-
NextServer = require('next/dist/next-server/server/next-server').default;
|
|
79
|
-
}
|
|
80
|
-
catch (error) {
|
|
81
|
-
if (!error.message.includes("Cannot find module 'next/dist/next-server/server/next-server'")) {
|
|
82
|
-
throw error;
|
|
83
|
-
}
|
|
84
|
-
throw new Error('Could not find Next.js server');
|
|
85
|
-
}
|
|
86
|
-
}
|
|
28
|
+
augmentFsModule({ promises, staticManifest, pageRoot, getBase: () => base });
|
|
29
|
+
const NextServer = getNextServer();
|
|
87
30
|
const nextServer = new NextServer({
|
|
88
31
|
conf,
|
|
89
32
|
dir: path.resolve(__dirname, app),
|
|
@@ -102,7 +45,8 @@ const makeHandler = () =>
|
|
|
102
45
|
const bridge = new Bridge(server);
|
|
103
46
|
bridge.listen();
|
|
104
47
|
return async (event, context) => {
|
|
105
|
-
var _a, _b, _c
|
|
48
|
+
var _a, _b, _c;
|
|
49
|
+
let requestMode = mode;
|
|
106
50
|
// Ensure that paths are encoded - but don't double-encode them
|
|
107
51
|
event.path = new URL(event.path, event.rawUrl).pathname;
|
|
108
52
|
// Next expects to be able to parse the query from the URL
|
|
@@ -115,25 +59,27 @@ const makeHandler = () =>
|
|
|
115
59
|
base = `${protocol}://${host}`;
|
|
116
60
|
}
|
|
117
61
|
const { headers, ...result } = await bridge.launcher(event, context);
|
|
118
|
-
/** @type import("@netlify/functions").HandlerResponse */
|
|
119
62
|
// Convert all headers to multiValueHeaders
|
|
120
|
-
const multiValueHeaders =
|
|
121
|
-
for (const key of Object.keys(headers)) {
|
|
122
|
-
if (Array.isArray(headers[key])) {
|
|
123
|
-
multiValueHeaders[key] = headers[key];
|
|
124
|
-
}
|
|
125
|
-
else {
|
|
126
|
-
multiValueHeaders[key] = [headers[key]];
|
|
127
|
-
}
|
|
128
|
-
}
|
|
63
|
+
const multiValueHeaders = getMultiValueHeaders(headers);
|
|
129
64
|
if ((_b = (_a = multiValueHeaders['set-cookie']) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.includes('__prerender_bypass')) {
|
|
130
65
|
delete multiValueHeaders.etag;
|
|
131
66
|
multiValueHeaders['cache-control'] = ['no-cache'];
|
|
132
67
|
}
|
|
133
68
|
// Sending SWR headers causes undefined behaviour with the Netlify CDN
|
|
134
|
-
|
|
69
|
+
const cacheHeader = (_c = multiValueHeaders['cache-control']) === null || _c === void 0 ? void 0 : _c[0];
|
|
70
|
+
if (cacheHeader === null || cacheHeader === void 0 ? void 0 : cacheHeader.includes('stale-while-revalidate')) {
|
|
71
|
+
if (requestMode === 'odb') {
|
|
72
|
+
requestMode = 'isr';
|
|
73
|
+
const ttl = getMaxAge(cacheHeader);
|
|
74
|
+
// Long-expiry TTL is basically no TTL
|
|
75
|
+
if (ttl > 0 && ttl < ONE_YEAR_IN_SECONDS) {
|
|
76
|
+
result.ttl = ttl;
|
|
77
|
+
}
|
|
78
|
+
multiValueHeaders['x-rendered-at'] = [new Date().toISOString()];
|
|
79
|
+
}
|
|
135
80
|
multiValueHeaders['cache-control'] = ['public, max-age=0, must-revalidate'];
|
|
136
81
|
}
|
|
82
|
+
multiValueHeaders['x-render-mode'] = [requestMode];
|
|
137
83
|
return {
|
|
138
84
|
...result,
|
|
139
85
|
multiValueHeaders,
|
|
@@ -143,13 +89,10 @@ const makeHandler = () =>
|
|
|
143
89
|
};
|
|
144
90
|
const getHandler = ({ isODB = false, publishDir = '../../../.next', appDir = '../../..' }) => `
|
|
145
91
|
const { Server } = require("http");
|
|
146
|
-
const {
|
|
147
|
-
const { promises, createWriteStream, existsSync } = require("fs");
|
|
148
|
-
const { promisify } = require('util')
|
|
149
|
-
const streamPipeline = promisify(require('stream').pipeline)
|
|
92
|
+
const { promises } = require("fs");
|
|
150
93
|
// We copy the file here rather than requiring from the node module
|
|
151
94
|
const { Bridge } = require("./bridge");
|
|
152
|
-
const
|
|
95
|
+
const { augmentFsModule, getMaxAge, getMultiValueHeaders, getNextServer } = require('./handlerUtils')
|
|
153
96
|
|
|
154
97
|
const { builder } = require("@netlify/functions");
|
|
155
98
|
const { config } = require("${publishDir}/required-server-files.json")
|
|
@@ -160,7 +103,8 @@ try {
|
|
|
160
103
|
const path = require("path");
|
|
161
104
|
const pageRoot = path.resolve(path.join(__dirname, "${publishDir}", config.target === "server" ? "server" : "serverless", "pages"));
|
|
162
105
|
exports.handler = ${isODB
|
|
163
|
-
? `builder((${makeHandler().toString()})(config, "${appDir}", pageRoot, staticManifest));`
|
|
164
|
-
: `(${makeHandler().toString()})(config, "${appDir}", pageRoot, staticManifest);`}
|
|
106
|
+
? `builder((${makeHandler().toString()})(config, "${appDir}", pageRoot, staticManifest, 'odb'));`
|
|
107
|
+
: `(${makeHandler().toString()})(config, "${appDir}", pageRoot, staticManifest, 'ssr');`}
|
|
165
108
|
`;
|
|
166
|
-
|
|
109
|
+
exports.getHandler = getHandler;
|
|
110
|
+
/* eslint-enable @typescript-eslint/no-var-requires */
|
|
@@ -1,19 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getPageResolver = void 0;
|
|
7
|
+
const path_1 = require("path");
|
|
8
|
+
const outdent_1 = require("outdent");
|
|
9
|
+
const slash_1 = __importDefault(require("slash"));
|
|
10
|
+
const tiny_glob_1 = __importDefault(require("tiny-glob"));
|
|
11
|
+
const constants_1 = require("../constants");
|
|
6
12
|
// Generate a file full of require.resolve() calls for all the pages in the
|
|
7
13
|
// build. This is used by the nft bundler to find all the pages.
|
|
8
|
-
|
|
9
|
-
const functionDir = resolve(join('.netlify', 'functions', HANDLER_FUNCTION_NAME));
|
|
10
|
-
const root = join(netlifyConfig.build.publish, target === 'server' ? 'server' : 'serverless', 'pages');
|
|
11
|
-
const pages = await
|
|
14
|
+
const getPageResolver = async ({ netlifyConfig, target }) => {
|
|
15
|
+
const functionDir = path_1.posix.resolve(path_1.posix.join('.netlify', 'functions', constants_1.HANDLER_FUNCTION_NAME));
|
|
16
|
+
const root = path_1.posix.join(netlifyConfig.build.publish, target === 'server' ? 'server' : 'serverless', 'pages');
|
|
17
|
+
const pages = await tiny_glob_1.default('**/*.js', {
|
|
12
18
|
cwd: root,
|
|
13
19
|
dot: true,
|
|
14
20
|
});
|
|
15
|
-
const pageFiles = pages
|
|
16
|
-
|
|
21
|
+
const pageFiles = pages
|
|
22
|
+
.map((page) => `require.resolve('${path_1.posix.relative(functionDir, path_1.posix.join(root, slash_1.default(page)))}')`)
|
|
23
|
+
.sort();
|
|
24
|
+
return outdent_1.outdent `
|
|
17
25
|
// This file is purely to allow nft to know about these pages. It should be temporary.
|
|
18
26
|
exports.resolvePages = () => {
|
|
19
27
|
try {
|
|
@@ -22,3 +30,4 @@ exports.getPageResolver = async ({ netlifyConfig, target }) => {
|
|
|
22
30
|
}
|
|
23
31
|
`;
|
|
24
32
|
};
|
|
33
|
+
exports.getPageResolver = getPageResolver;
|