@netlify/plugin-nextjs 4.16.0 → 4.17.1-api-routes.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,63 @@
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.extractConfigFromFile = exports.validateConfigValue = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const extract_const_value_1 = require("next/dist/build/analysis/extract-const-value");
9
+ const parse_module_1 = require("next/dist/build/analysis/parse-module");
10
+ const pathe_1 = require("pathe");
11
+ const validateConfigValue = (config, apiFilePath) => {
12
+ if (config.type === 'experimental-scheduled') {
13
+ if (!config.schedule) {
14
+ console.error(`Invalid config value in ${(0, pathe_1.relative)(process.cwd(), apiFilePath)}: schedule is required when type is "experimental-scheduled"`);
15
+ return false;
16
+ }
17
+ if (config.runtime === 'experimental-edge') {
18
+ console.error(`Invalid config value in ${(0, pathe_1.relative)(process.cwd(), apiFilePath)}: edge runtime is not supported for scheduled functions`);
19
+ return false;
20
+ }
21
+ return true;
22
+ }
23
+ if (!config.type || config.type === 'experimental-background') {
24
+ if (config.schedule) {
25
+ console.error(`Invalid config value in ${(0, pathe_1.relative)(process.cwd(), apiFilePath)}: schedule is not allowed unless type is "experimental-scheduled"`);
26
+ return false;
27
+ }
28
+ if (config.type && config.runtime === 'experimental-edge') {
29
+ console.error(`Invalid config value in ${(0, pathe_1.relative)(process.cwd(), apiFilePath)}: edge runtime is not supported for background functions`);
30
+ return false;
31
+ }
32
+ return true;
33
+ }
34
+ console.error(`Invalid config value in ${(0, pathe_1.relative)(process.cwd(), apiFilePath)}: type ${config.type} is not supported`);
35
+ return false;
36
+ };
37
+ exports.validateConfigValue = validateConfigValue;
38
+ /**
39
+ * Uses Next's swc static analysis to extract the config values from a file.
40
+ */
41
+ const extractConfigFromFile = async (apiFilePath) => {
42
+ const fileContent = await fs_1.default.promises.readFile(apiFilePath, 'utf8');
43
+ // No need to parse if there's no "config"
44
+ if (!fileContent.includes('config')) {
45
+ return {};
46
+ }
47
+ const ast = await (0, parse_module_1.parseModule)(apiFilePath, fileContent);
48
+ let config;
49
+ try {
50
+ config = (0, extract_const_value_1.extractExportedConstValue)(ast, 'config');
51
+ }
52
+ catch (error) {
53
+ if (error instanceof extract_const_value_1.UnsupportedValueError) {
54
+ console.warn(`Unsupported config value in ${(0, pathe_1.relative)(process.cwd(), apiFilePath)}`);
55
+ }
56
+ return {};
57
+ }
58
+ if ((0, exports.validateConfigValue)(config, apiFilePath)) {
59
+ return config;
60
+ }
61
+ throw new Error(`Unsupported config value in ${(0, pathe_1.relative)(process.cwd(), apiFilePath)}`);
62
+ };
63
+ exports.extractConfigFromFile = extractConfigFromFile;
@@ -9,7 +9,7 @@ const findDistDir = (publish) => {
9
9
  if (!(0, utils_1.shouldSkip)()) {
10
10
  return publish;
11
11
  }
12
- // In this situation, the user has disabled the plugin, which means that they might be using next export,
12
+ // In this situation, the user has disabled the next-runtime, which means that they might be using next export,
13
13
  // so we'll look in a few places to find the site root. This allows us to find the .next directory.
14
14
  for (const root of [(0, path_1.resolve)(publish, '..'), (0, path_1.resolve)(publish, '..', '..')]) {
15
15
  if ((0, fs_1.existsSync)((0, path_1.join)(root, 'next.config.js'))) {
@@ -64,7 +64,7 @@ const configureHandlerFunctions = async ({ netlifyConfig, publish, ignore = [] }
64
64
  /* eslint-disable no-underscore-dangle */
65
65
  (_a = netlifyConfig.functions)._ipx || (_a._ipx = {});
66
66
  netlifyConfig.functions._ipx.node_bundler = 'nft';
67
- [constants_1.HANDLER_FUNCTION_NAME, constants_1.ODB_FUNCTION_NAME].forEach((functionName) => {
67
+ [constants_1.HANDLER_FUNCTION_NAME, constants_1.ODB_FUNCTION_NAME, '_api_*'].forEach((functionName) => {
68
68
  var _a, _b;
69
69
  (_a = netlifyConfig.functions)[functionName] || (_a[functionName] = { included_files: [], external_node_modules: [] });
70
70
  netlifyConfig.functions[functionName].node_bundler = 'nft';
@@ -0,0 +1,37 @@
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.onPreDev = void 0;
7
+ const path_1 = require("path");
8
+ const execa_1 = __importDefault(require("execa"));
9
+ const fs_extra_1 = require("fs-extra");
10
+ const edge_1 = require("./edge");
11
+ const files_1 = require("./files");
12
+ // The types haven't been updated yet
13
+ const onPreDev = async ({ constants, netlifyConfig }) => {
14
+ var _a;
15
+ const base = (_a = netlifyConfig.build.base) !== null && _a !== void 0 ? _a : process.cwd();
16
+ // Need to patch the files, because build might not have been run
17
+ await (0, files_1.patchNextFiles)(base);
18
+ // Clean up old functions
19
+ await (0, fs_extra_1.unlink)((0, path_1.resolve)('.netlify', 'middleware.js')).catch(() => {
20
+ // Ignore if it doesn't exist
21
+ });
22
+ await (0, edge_1.writeDevEdgeFunction)(constants);
23
+ if (!(0, fs_extra_1.existsSync)((0, path_1.resolve)(base, 'middleware.ts')) && !(0, fs_extra_1.existsSync)((0, path_1.resolve)(base, 'middleware.js'))) {
24
+ console.log("No middleware found. Create a 'middleware.ts' or 'middleware.js' file in your project root to add custom middleware.");
25
+ }
26
+ else {
27
+ console.log('Watching for changes in Next.js middleware...');
28
+ }
29
+ // Eventually we might want to do this via esbuild's API, but for now the CLI works fine
30
+ const common = [`--bundle`, `--outdir=${(0, path_1.resolve)('.netlify')}`, `--format=esm`, `--target=esnext`, '--watch'];
31
+ // TypeScript
32
+ (0, execa_1.default)(`esbuild`, [...common, (0, path_1.resolve)(base, 'middleware.ts')], { all: true }).all.pipe(process.stdout);
33
+ // JavaScript
34
+ (0, execa_1.default)(`esbuild`, [...common, (0, path_1.resolve)(base, 'middleware.js')], { all: true }).all.pipe(process.stdout);
35
+ // Don't return the promise because we don't want to wait for the child process to finish
36
+ };
37
+ exports.onPreDev = onPreDev;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.updateConfig = exports.writeEdgeFunctions = exports.loadMiddlewareManifest = void 0;
3
+ exports.enableEdgeInNextConfig = exports.writeEdgeFunctions = exports.writeDevEdgeFunction = exports.loadMiddlewareManifest = void 0;
4
4
  /* eslint-disable max-lines */
5
5
  const fs_1 = require("fs");
6
6
  const path_1 = require("path");
@@ -67,6 +67,25 @@ const writeEdgeFunction = async ({ edgeFunctionDefinition, edgeFunctionRoot, net
67
67
  pattern: stripLookahead(edgeFunctionDefinition.regexp),
68
68
  };
69
69
  };
70
+ const writeDevEdgeFunction = async ({ INTERNAL_EDGE_FUNCTIONS_SRC = '.netlify/edge-functions', }) => {
71
+ const manifest = {
72
+ functions: [
73
+ {
74
+ function: 'next-dev',
75
+ path: '/*',
76
+ },
77
+ ],
78
+ version: 1,
79
+ };
80
+ const edgeFunctionRoot = (0, path_1.resolve)(INTERNAL_EDGE_FUNCTIONS_SRC);
81
+ await (0, fs_extra_1.emptyDir)(edgeFunctionRoot);
82
+ await (0, fs_extra_1.writeJson)((0, path_1.join)(edgeFunctionRoot, 'manifest.json'), manifest);
83
+ const edgeFunctionDir = (0, path_1.join)(edgeFunctionRoot, 'next-dev');
84
+ await (0, fs_extra_1.ensureDir)(edgeFunctionDir);
85
+ await copyEdgeSourceFile({ edgeFunctionDir, file: 'next-dev.js', target: 'index.js' });
86
+ await copyEdgeSourceFile({ edgeFunctionDir, file: 'utils.ts' });
87
+ };
88
+ exports.writeDevEdgeFunction = writeDevEdgeFunction;
70
89
  /**
71
90
  * Writes Edge Functions for the Next middleware
72
91
  */
@@ -78,9 +97,7 @@ const writeEdgeFunctions = async (netlifyConfig) => {
78
97
  const edgeFunctionRoot = (0, path_1.resolve)('.netlify', 'edge-functions');
79
98
  await (0, fs_extra_1.emptyDir)(edgeFunctionRoot);
80
99
  if (!process.env.NEXT_DISABLE_EDGE_IMAGES) {
81
- if (!process.env.NEXT_USE_NETLIFY_EDGE) {
82
- console.log('Using Netlify Edge Functions for image format detection. Set env var "NEXT_DISABLE_EDGE_IMAGES=true" to disable.');
83
- }
100
+ console.log('Using Netlify Edge Functions for image format detection. Set env var "NEXT_DISABLE_EDGE_IMAGES=true" to disable.');
84
101
  const edgeFunctionDir = (0, path_1.join)(edgeFunctionRoot, 'ipx');
85
102
  await (0, fs_extra_1.ensureDir)(edgeFunctionDir);
86
103
  await copyEdgeSourceFile({ edgeFunctionDir, file: 'ipx.ts', target: 'index.ts' });
@@ -90,7 +107,7 @@ const writeEdgeFunctions = async (netlifyConfig) => {
90
107
  path: '/_next/image*',
91
108
  });
92
109
  }
93
- if (process.env.NEXT_USE_NETLIFY_EDGE) {
110
+ if (!process.env.NEXT_DISABLE_NETLIFY_EDGE) {
94
111
  const middlewareManifest = await (0, exports.loadMiddlewareManifest)(netlifyConfig);
95
112
  if (!middlewareManifest) {
96
113
  console.error("Couldn't find the middleware manifest");
@@ -121,11 +138,10 @@ const writeEdgeFunctions = async (netlifyConfig) => {
121
138
  await (0, fs_extra_1.writeJson)((0, path_1.join)(edgeFunctionRoot, 'manifest.json'), manifest);
122
139
  };
123
140
  exports.writeEdgeFunctions = writeEdgeFunctions;
124
- const updateConfig = async (publish) => {
141
+ const enableEdgeInNextConfig = async (publish) => {
125
142
  const configFile = (0, path_1.join)(publish, 'required-server-files.json');
126
143
  const config = await (0, fs_extra_1.readJSON)(configFile);
127
- config.config.env.NEXT_USE_NETLIFY_EDGE = 'true';
128
144
  await (0, fs_extra_1.writeJSON)(configFile, config);
129
145
  };
130
- exports.updateConfig = updateConfig;
146
+ exports.enableEdgeInNextConfig = enableEdgeInNextConfig;
131
147
  /* eslint-enable max-lines */
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.movePublicFiles = exports.unpatchNextFiles = exports.unpatchFile = exports.patchNextFiles = exports.moveStaticPages = exports.getMiddleware = exports.matchesRewrite = exports.matchesRedirect = exports.matchMiddleware = exports.stripLocale = exports.isDynamicRoute = void 0;
6
+ exports.movePublicFiles = exports.unpatchNextFiles = exports.unpatchFile = exports.patchNextFiles = exports.getDependenciesOfFile = exports.getSourceFileForPage = exports.moveStaticPages = exports.getMiddleware = exports.matchesRewrite = exports.matchesRedirect = exports.matchMiddleware = exports.stripLocale = exports.isDynamicRoute = void 0;
7
7
  /* eslint-disable max-lines */
8
8
  const os_1 = require("os");
9
9
  const chalk_1 = require("chalk");
@@ -54,7 +54,7 @@ const matchesRewrite = (file, rewrites) => {
54
54
  exports.matchesRewrite = matchesRewrite;
55
55
  const getMiddleware = async (publish) => {
56
56
  var _a;
57
- if (process.env.NEXT_USE_NETLIFY_EDGE) {
57
+ if (!process.env.NEXT_DISABLE_NETLIFY_EDGE) {
58
58
  return [];
59
59
  }
60
60
  const manifestPath = (0, pathe_1.join)(publish, 'server', 'middleware-manifest.json');
@@ -270,25 +270,49 @@ const getServerFile = (root, includeBase = true) => {
270
270
  }
271
271
  return (0, utils_1.findModuleFromBase)({ candidates, paths: [root] });
272
272
  };
273
+ /**
274
+ * Find the source file for a given page route
275
+ */
276
+ const getSourceFileForPage = (page, root) => {
277
+ for (const extension of ['ts', 'js']) {
278
+ const file = (0, pathe_1.join)(root, `${page}.${extension}`);
279
+ if ((0, fs_extra_1.existsSync)(file)) {
280
+ return file;
281
+ }
282
+ }
283
+ };
284
+ exports.getSourceFileForPage = getSourceFileForPage;
285
+ /**
286
+ * Reads the node file trace file for a given file, and resolves the dependencies
287
+ */
288
+ const getDependenciesOfFile = async (file) => {
289
+ const nft = `${file}.nft.json`;
290
+ if (!(0, fs_extra_1.existsSync)(nft)) {
291
+ return [];
292
+ }
293
+ const dependencies = await (0, fs_extra_1.readJson)(nft, 'utf8');
294
+ return dependencies.files.map((dep) => (0, pathe_1.resolve)(file, dep));
295
+ };
296
+ exports.getDependenciesOfFile = getDependenciesOfFile;
273
297
  const baseServerReplacements = [
274
298
  [`let ssgCacheKey = `, `let ssgCacheKey = process.env._BYPASS_SSG || `],
275
299
  ];
276
300
  const nextServerReplacements = [
277
301
  [
278
302
  `getMiddlewareManifest() {\n if (this.minimalMode) return null;`,
279
- `getMiddlewareManifest() {\n if (this.minimalMode || process.env.NEXT_USE_NETLIFY_EDGE) return null;`,
303
+ `getMiddlewareManifest() {\n if (this.minimalMode || !process.env.NEXT_DISABLE_NETLIFY_EDGE) return null;`,
280
304
  ],
281
305
  [
282
306
  `generateCatchAllMiddlewareRoute(devReady) {\n if (this.minimalMode) return []`,
283
- `generateCatchAllMiddlewareRoute(devReady) {\n if (this.minimalMode || process.env.NEXT_USE_NETLIFY_EDGE) return [];`,
307
+ `generateCatchAllMiddlewareRoute(devReady) {\n if (this.minimalMode || !process.env.NEXT_DISABLE_NETLIFY_EDGE) return [];`,
284
308
  ],
285
309
  [
286
310
  `generateCatchAllMiddlewareRoute() {\n if (this.minimalMode) return undefined;`,
287
- `generateCatchAllMiddlewareRoute() {\n if (this.minimalMode || process.env.NEXT_USE_NETLIFY_EDGE) return undefined;`,
311
+ `generateCatchAllMiddlewareRoute() {\n if (this.minimalMode || !process.env.NEXT_DISABLE_NETLIFY_EDGE) return undefined;`,
288
312
  ],
289
313
  [
290
314
  `getMiddlewareManifest() {\n if (this.minimalMode) {`,
291
- `getMiddlewareManifest() {\n if (!this.minimalMode && !process.env.NEXT_USE_NETLIFY_EDGE) {`,
315
+ `getMiddlewareManifest() {\n if (!this.minimalMode && process.env.NEXT_DISABLE_NETLIFY_EDGE) {`,
292
316
  ],
293
317
  ];
294
318
  const patchNextFiles = async (root) => {
@@ -3,23 +3,45 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.setupImageFunction = exports.generatePagesResolver = exports.generateFunctions = void 0;
6
+ exports.getApiRouteConfigs = exports.setupImageFunction = exports.generatePagesResolver = exports.generateFunctions = void 0;
7
7
  const node_bridge_1 = __importDefault(require("@vercel/node-bridge"));
8
8
  const fs_extra_1 = require("fs-extra");
9
9
  const pathe_1 = require("pathe");
10
10
  const constants_1 = require("../constants");
11
+ const getApiHandler_1 = require("../templates/getApiHandler");
11
12
  const getHandler_1 = require("../templates/getHandler");
12
13
  const getPageResolver_1 = require("../templates/getPageResolver");
13
- const generateFunctions = async ({ FUNCTIONS_SRC = constants_1.DEFAULT_FUNCTIONS_SRC, INTERNAL_FUNCTIONS_SRC, PUBLISH_DIR }, appDir) => {
14
- const functionsDir = INTERNAL_FUNCTIONS_SRC || FUNCTIONS_SRC;
15
- const functionDir = (0, pathe_1.join)(process.cwd(), functionsDir, constants_1.HANDLER_FUNCTION_NAME);
16
- const publishDir = (0, pathe_1.relative)(functionDir, (0, pathe_1.resolve)(PUBLISH_DIR));
17
- const writeHandler = async (func, isODB) => {
14
+ const analysis_1 = require("./analysis");
15
+ const files_1 = require("./files");
16
+ const utils_1 = require("./utils");
17
+ const generateFunctions = async ({ FUNCTIONS_SRC = constants_1.DEFAULT_FUNCTIONS_SRC, INTERNAL_FUNCTIONS_SRC, PUBLISH_DIR }, appDir, apiRoutes) => {
18
+ const publish = (0, pathe_1.resolve)(PUBLISH_DIR);
19
+ const functionsDir = (0, pathe_1.resolve)(INTERNAL_FUNCTIONS_SRC || FUNCTIONS_SRC);
20
+ console.log({ functionsDir });
21
+ const functionDir = (0, pathe_1.join)(functionsDir, constants_1.HANDLER_FUNCTION_NAME);
22
+ const publishDir = (0, pathe_1.relative)(functionDir, publish);
23
+ for (const { route, config, compiled } of apiRoutes) {
24
+ const apiHandlerSource = await (0, getApiHandler_1.getApiHandler)({
25
+ page: route,
26
+ config,
27
+ });
28
+ const functionName = (0, utils_1.getFunctionNameForPage)(route, config.type === 'experimental-background');
29
+ await (0, fs_extra_1.ensureDir)((0, pathe_1.join)(functionsDir, functionName));
30
+ await (0, fs_extra_1.writeFile)((0, pathe_1.join)(functionsDir, functionName, `${functionName}.js`), apiHandlerSource);
31
+ await (0, fs_extra_1.copyFile)(node_bridge_1.default, (0, pathe_1.join)(functionsDir, functionName, 'bridge.js'));
32
+ await (0, fs_extra_1.copyFile)((0, pathe_1.join)(__dirname, '..', '..', 'lib', 'templates', 'handlerUtils.js'), (0, pathe_1.join)(functionsDir, functionName, 'handlerUtils.js'));
33
+ const resolverSource = await (0, getPageResolver_1.getSinglePageResolver)({
34
+ functionsDir,
35
+ sourceFile: (0, pathe_1.join)(publish, 'server', compiled),
36
+ });
37
+ await (0, fs_extra_1.writeFile)((0, pathe_1.join)(functionsDir, functionName, 'pages.js'), resolverSource);
38
+ }
39
+ const writeHandler = async (functionName, isODB) => {
18
40
  const handlerSource = await (0, getHandler_1.getHandler)({ isODB, publishDir, appDir: (0, pathe_1.relative)(functionDir, appDir) });
19
- await (0, fs_extra_1.ensureDir)((0, pathe_1.join)(functionsDir, func));
20
- await (0, fs_extra_1.writeFile)((0, pathe_1.join)(functionsDir, func, `${func}.js`), handlerSource);
21
- await (0, fs_extra_1.copyFile)(node_bridge_1.default, (0, pathe_1.join)(functionsDir, func, 'bridge.js'));
22
- await (0, fs_extra_1.copyFile)((0, pathe_1.join)(__dirname, '..', '..', 'lib', 'templates', 'handlerUtils.js'), (0, pathe_1.join)(functionsDir, func, 'handlerUtils.js'));
41
+ await (0, fs_extra_1.ensureDir)((0, pathe_1.join)(functionsDir, functionName));
42
+ await (0, fs_extra_1.writeFile)((0, pathe_1.join)(functionsDir, functionName, `${functionName}.js`), handlerSource);
43
+ await (0, fs_extra_1.copyFile)(node_bridge_1.default, (0, pathe_1.join)(functionsDir, functionName, 'bridge.js'));
44
+ await (0, fs_extra_1.copyFile)((0, pathe_1.join)(__dirname, '..', '..', 'lib', 'templates', 'handlerUtils.js'), (0, pathe_1.join)(functionsDir, functionName, 'handlerUtils.js'));
23
45
  };
24
46
  await writeHandler(constants_1.HANDLER_FUNCTION_NAME, false);
25
47
  await writeHandler(constants_1.ODB_FUNCTION_NAME, true);
@@ -76,3 +98,16 @@ const setupImageFunction = async ({ constants: { INTERNAL_FUNCTIONS_SRC, FUNCTIO
76
98
  }
77
99
  };
78
100
  exports.setupImageFunction = setupImageFunction;
101
+ /**
102
+ * Look for API routes, and extract the config from the source file.
103
+ */
104
+ const getApiRouteConfigs = async (publish, baseDir) => {
105
+ const pages = await (0, fs_extra_1.readJSON)((0, pathe_1.join)(publish, 'server', 'pages-manifest.json'));
106
+ const apiRoutes = Object.keys(pages).filter((page) => page.startsWith('/api/'));
107
+ const pagesDir = (0, pathe_1.join)(baseDir, 'pages');
108
+ return Promise.all(apiRoutes.map(async (apiRoute) => {
109
+ const filePath = (0, files_1.getSourceFileForPage)(apiRoute, pagesDir);
110
+ return { route: apiRoute, config: await (0, analysis_1.extractConfigFromFile)(filePath), compiled: pages[apiRoute] };
111
+ }));
112
+ };
113
+ exports.getApiRouteConfigs = getApiRouteConfigs;
@@ -146,7 +146,7 @@ const generateDynamicRewrites = ({ dynamicRoutes, prerenderedDynamicRoutes, midd
146
146
  dynamicRewrites,
147
147
  };
148
148
  };
149
- const generateRedirects = async ({ netlifyConfig, nextConfig: { i18n, basePath, trailingSlash, appDir }, buildId, }) => {
149
+ const generateRedirects = async ({ netlifyConfig, nextConfig: { i18n, basePath, trailingSlash, appDir }, buildId, apiRoutes, }) => {
150
150
  const { dynamicRoutes: prerenderedDynamicRoutes, routes: prerenderedStaticRoutes } = await (0, fs_extra_1.readJSON)((0, pathe_1.join)(netlifyConfig.build.publish, 'prerender-manifest.json'));
151
151
  const { dynamicRoutes, staticRoutes } = await (0, fs_extra_1.readJSON)((0, pathe_1.join)(netlifyConfig.build.publish, 'routes-manifest.json'));
152
152
  netlifyConfig.redirects.push(...constants_1.HIDDEN_PATHS.map((path) => ({
@@ -161,7 +161,7 @@ const generateRedirects = async ({ netlifyConfig, nextConfig: { i18n, basePath,
161
161
  // This is only used in prod, so dev uses `next dev` directly
162
162
  netlifyConfig.redirects.push(
163
163
  // API routes always need to be served from the regular function
164
- ...(0, utils_1.getApiRewrites)(basePath),
164
+ ...(0, utils_1.getApiRewrites)(basePath, apiRoutes),
165
165
  // Preview mode gets forced to the function, to bypass pre-rendered pages, but static files need to be skipped
166
166
  ...(await (0, utils_1.getPreviewRewrites)({ basePath, appDir })));
167
167
  const middleware = await (0, files_1.getMiddleware)(netlifyConfig.build.publish);
@@ -3,10 +3,22 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.getCustomImageResponseHeaders = exports.isNextAuthInstalled = exports.findModuleFromBase = exports.shouldSkip = exports.getPreviewRewrites = exports.getApiRewrites = exports.redirectsForNextRouteWithData = exports.redirectsForNextRoute = exports.isApiRoute = exports.routeToDataRoute = exports.netlifyRoutesForNextRouteWithData = exports.toNetlifyRoute = void 0;
6
+ exports.getCustomImageResponseHeaders = exports.isNextAuthInstalled = exports.findModuleFromBase = exports.shouldSkip = exports.getPreviewRewrites = exports.getApiRewrites = exports.redirectsForNextRouteWithData = exports.redirectsForNextRoute = exports.isApiRoute = exports.routeToDataRoute = exports.netlifyRoutesForNextRouteWithData = exports.toNetlifyRoute = exports.getFunctionNameForPage = void 0;
7
7
  const globby_1 = __importDefault(require("globby"));
8
8
  const pathe_1 = require("pathe");
9
9
  const constants_1 = require("../constants");
10
+ const RESERVED_FILENAME = /[^\w_-]/g;
11
+ /**
12
+ * Given a Next route, generates a valid Netlify function name.
13
+ * If "background" is true then the function name will have `-background`
14
+ * appended to it, meaning that it is executed as a background function.
15
+ */
16
+ const getFunctionNameForPage = (page, background = false) => `${page
17
+ .replace(constants_1.CATCH_ALL_REGEX, '_$1-SPLAT')
18
+ .replace(constants_1.OPTIONAL_CATCH_ALL_REGEX, '-SPLAT')
19
+ .replace(constants_1.DYNAMIC_PARAMETER_REGEX, '_$1-PARAM')
20
+ .replace(RESERVED_FILENAME, '_')}-${background ? 'background' : 'handler'}`;
21
+ exports.getFunctionNameForPage = getFunctionNameForPage;
10
22
  const toNetlifyRoute = (nextRoute) => {
11
23
  const netlifyRoutes = [nextRoute];
12
24
  // If the route is an optional catch-all route, we need to add a second
@@ -77,18 +89,33 @@ const redirectsForNextRouteWithData = ({ route, dataRoute, basePath, to, status
77
89
  force,
78
90
  }));
79
91
  exports.redirectsForNextRouteWithData = redirectsForNextRouteWithData;
80
- const getApiRewrites = (basePath) => [
81
- {
82
- from: `${basePath}/api`,
83
- to: constants_1.HANDLER_FUNCTION_PATH,
84
- status: 200,
85
- },
86
- {
87
- from: `${basePath}/api/*`,
88
- to: constants_1.HANDLER_FUNCTION_PATH,
89
- status: 200,
90
- },
91
- ];
92
+ const getApiRewrites = (basePath, apiRoutes) => {
93
+ const apiRewrites = apiRoutes.map((apiRoute) => {
94
+ const [from] = (0, exports.toNetlifyRoute)(`${basePath}${apiRoute.route}`);
95
+ // Scheduled functions can't be invoked directly, so we 404 them.
96
+ if (apiRoute.config.type === 'experimental-scheduled') {
97
+ return { from, to: '/404.html', status: 404 };
98
+ }
99
+ return {
100
+ from,
101
+ to: `/.netlify/functions/${(0, exports.getFunctionNameForPage)(apiRoute.route, apiRoute.config.type === 'experimental-background')}`,
102
+ status: 200,
103
+ };
104
+ });
105
+ return [
106
+ ...apiRewrites,
107
+ {
108
+ from: `${basePath}/api`,
109
+ to: constants_1.HANDLER_FUNCTION_PATH,
110
+ status: 200,
111
+ },
112
+ {
113
+ from: `${basePath}/api/*`,
114
+ to: constants_1.HANDLER_FUNCTION_PATH,
115
+ status: 200,
116
+ },
117
+ ];
118
+ };
92
119
  exports.getApiRewrites = getApiRewrites;
93
120
  const getPreviewRewrites = async ({ basePath, appDir }) => {
94
121
  const publicFiles = await (0, globby_1.default)('**/*', { cwd: (0, pathe_1.join)(appDir, 'public') });
@@ -136,7 +163,7 @@ const findModuleFromBase = ({ paths, candidates }) => {
136
163
  exports.findModuleFromBase = findModuleFromBase;
137
164
  const isNextAuthInstalled = () => {
138
165
  try {
139
- // eslint-disable-next-line import/no-unassigned-import, import/no-unresolved, n/no-missing-require
166
+ // eslint-disable-next-line import/no-unassigned-import, import/no-extraneous-dependencies, n/no-extraneous-require
140
167
  require('next-auth');
141
168
  return true;
142
169
  }
@@ -42,7 +42,7 @@ const verifyNetlifyBuildVersion = ({ IS_LOCAL, NETLIFY_BUILD_VERSION, failBuild,
42
42
  // 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
43
43
  if (IS_LOCAL && !(0, semver_1.satisfies)(NETLIFY_BUILD_VERSION, REQUIRED_BUILD_VERSION, { includePrerelease: true })) {
44
44
  return failBuild((0, outdent_1.outdent) `
45
- This version of the Essential Next.js plugin requires netlify-cli@6.12.4 or higher. Please upgrade and try again.
45
+ This version of the Next Runtime requires netlify-cli@6.12.4 or higher. Please upgrade and try again.
46
46
  You can do this by running: "npm install -g netlify-cli@latest" or "yarn global add netlify-cli@latest"
47
47
  `);
48
48
  }
@@ -136,7 +136,7 @@ exports.checkZipSize = checkZipSize;
136
136
  const getProblematicUserRewrites = ({ redirects, basePath, }) => {
137
137
  const userRewrites = [];
138
138
  for (const redirect of redirects) {
139
- // This is the first of the plugin-generated redirects so we can stop checking
139
+ // This is the first of the runtime-generated redirects so we can stop checking
140
140
  if (redirect.from === `${basePath}/_next/static/*` && redirect.to === `/static/:splat` && redirect.status === 200) {
141
141
  break;
142
142
  }
@@ -159,7 +159,7 @@ const warnForProblematicUserRewrites = ({ redirects, basePath, }) => {
159
159
  return;
160
160
  }
161
161
  console.log((0, chalk_1.yellowBright)((0, outdent_1.outdent) `
162
- You have the following Netlify rewrite${userRewrites.length === 1 ? '' : 's'} that might cause conflicts with the Next.js plugin:
162
+ You have the following Netlify rewrite${userRewrites.length === 1 ? '' : 's'} that might cause conflicts with the Next.js Runtime:
163
163
 
164
164
  ${(0, chalk_1.reset)(userRewrites.map(({ from, to, status }) => `- ${from} ${to} ${status}`).join('\n'))}
165
165
 
package/lib/index.js CHANGED
@@ -8,6 +8,7 @@ const outdent_1 = require("outdent");
8
8
  const constants_1 = require("./constants");
9
9
  const cache_1 = require("./helpers/cache");
10
10
  const config_1 = require("./helpers/config");
11
+ const dev_1 = require("./helpers/dev");
11
12
  const edge_1 = require("./helpers/edge");
12
13
  const files_1 = require("./helpers/files");
13
14
  const functions_1 = require("./helpers/functions");
@@ -20,7 +21,7 @@ const plugin = {
20
21
  const { publish } = netlifyConfig.build;
21
22
  if ((0, utils_1.shouldSkip)()) {
22
23
  await (0, cache_1.restoreCache)({ cache, publish });
23
- console.log('Not running Essential Next.js plugin');
24
+ console.log('Not running Next Runtime');
24
25
  if ((0, fs_extra_1.existsSync)((0, path_1.join)(constants.INTERNAL_FUNCTIONS_SRC, constants_1.HANDLER_FUNCTION_NAME))) {
25
26
  console.log(`Please ensure you remove any generated functions from ${constants.INTERNAL_FUNCTIONS_SRC}`);
26
27
  }
@@ -34,7 +35,6 @@ const plugin = {
34
35
  netlifyConfig.build.environment.NEXT_PRIVATE_TARGET = 'server';
35
36
  },
36
37
  async onBuild({ constants, netlifyConfig, utils: { build: { failBuild }, }, }) {
37
- var _a;
38
38
  if ((0, utils_1.shouldSkip)()) {
39
39
  return;
40
40
  }
@@ -45,6 +45,28 @@ const plugin = {
45
45
  publish,
46
46
  failBuild,
47
47
  });
48
+ const middlewareManifest = await (0, edge_1.loadMiddlewareManifest)(netlifyConfig);
49
+ let usingEdge = false;
50
+ if ((middlewareManifest === null || middlewareManifest === void 0 ? void 0 : middlewareManifest.functions) && Object.keys(middlewareManifest.functions).length !== 0) {
51
+ usingEdge = true;
52
+ if (process.env.NEXT_DISABLE_NETLIFY_EDGE) {
53
+ failBuild((0, outdent_1.outdent) `
54
+ You are using Next.js experimental edge runtime, but have set NEXT_DISABLE_NETLIFY_EDGE to true. This is not supported.
55
+ To use edge runtime, remove the env var ${(0, chalk_1.bold) `NEXT_DISABLE_NETLIFY_EDGE`}.
56
+ `);
57
+ }
58
+ }
59
+ if ((middlewareManifest === null || middlewareManifest === void 0 ? void 0 : middlewareManifest.middleware) && Object.keys(middlewareManifest.middleware).length !== 0) {
60
+ usingEdge = true;
61
+ if (process.env.NEXT_DISABLE_NETLIFY_EDGE) {
62
+ console.log((0, chalk_1.redBright)((0, outdent_1.outdent) `
63
+ You are using Next.js Middleware without Netlify Edge Functions.
64
+ This is deprecated because it negatively affects performance and will disable ISR and static rendering.
65
+ It also disables advanced middleware features from @netlify/next
66
+ To get the best performance and use Netlify Edge Functions, remove the env var ${(0, chalk_1.bold) `NEXT_DISABLE_NETLIFY_EDGE`}.
67
+ `));
68
+ }
69
+ }
48
70
  if (experimental.images) {
49
71
  experimentalRemotePatterns = experimental.images.remotePatterns || [];
50
72
  }
@@ -63,10 +85,11 @@ const plugin = {
63
85
  }
64
86
  const buildId = (0, fs_extra_1.readFileSync)((0, path_1.join)(publish, 'BUILD_ID'), 'utf8').trim();
65
87
  await (0, config_1.configureHandlerFunctions)({ netlifyConfig, ignore, publish: (0, path_1.relative)(process.cwd(), publish) });
66
- await (0, functions_1.generateFunctions)(constants, appDir);
88
+ const apiRoutes = await (0, functions_1.getApiRouteConfigs)(publish, appDir);
89
+ await (0, functions_1.generateFunctions)(constants, appDir, apiRoutes);
67
90
  await (0, functions_1.generatePagesResolver)({ target, constants });
68
91
  await (0, files_1.movePublicFiles)({ appDir, outdir, publish });
69
- await (0, files_1.patchNextFiles)(basePath);
92
+ await (0, files_1.patchNextFiles)(appDir);
70
93
  if (!process.env.SERVE_STATIC_FILES_FROM_ORIGIN) {
71
94
  await (0, files_1.moveStaticPages)({ target, netlifyConfig, i18n, basePath });
72
95
  }
@@ -86,30 +109,22 @@ const plugin = {
86
109
  netlifyConfig,
87
110
  nextConfig: { basePath, i18n, trailingSlash, appDir },
88
111
  buildId,
112
+ apiRoutes,
89
113
  });
90
- // We call this even if we don't have edge functions enabled because we still use it for images
91
- await (0, edge_1.writeEdgeFunctions)(netlifyConfig);
92
- if (process.env.NEXT_USE_NETLIFY_EDGE) {
114
+ if (usingEdge) {
115
+ await (0, edge_1.writeEdgeFunctions)(netlifyConfig);
116
+ await (0, edge_1.enableEdgeInNextConfig)(publish);
93
117
  console.log((0, outdent_1.outdent) `
94
- ✨ Deploying to ${(0, chalk_1.greenBright) `Netlify Edge Functions`} ✨
118
+ ✨ Deploying middleware and functions to ${(0, chalk_1.greenBright) `Netlify Edge Functions`} ✨
95
119
  This feature is in beta. Please share your feedback here: https://ntl.fyi/next-netlify-edge
96
120
  `);
97
- await (0, edge_1.updateConfig)(publish);
98
- }
99
- const middlewareManifest = await (0, edge_1.loadMiddlewareManifest)(netlifyConfig);
100
- if (!process.env.NEXT_USE_NETLIFY_EDGE && ((_a = middlewareManifest === null || middlewareManifest === void 0 ? void 0 : middlewareManifest.sortedMiddleware) === null || _a === void 0 ? void 0 : _a.length)) {
101
- console.log((0, chalk_1.yellowBright)((0, outdent_1.outdent) `
102
- You are using Next.js Middleware without Netlify Edge Functions.
103
- This will soon be deprecated because it negatively affects performance and will disable ISR and static rendering.
104
- To get the best performance and use Netlify Edge Functions, set the env var ${(0, chalk_1.bold) `NEXT_USE_NETLIFY_EDGE=true`}.
105
- `));
106
121
  }
107
122
  },
108
123
  async onPostBuild({ netlifyConfig: { build: { publish }, redirects, headers, }, utils: { status, cache, functions, build: { failBuild }, }, constants: { FUNCTIONS_DIST }, }) {
109
124
  await (0, cache_1.saveCache)({ cache, publish });
110
125
  if ((0, utils_1.shouldSkip)()) {
111
126
  status.show({
112
- title: 'Essential Next.js plugin did not run',
127
+ title: 'Next Runtime did not run',
113
128
  summary: `Next cache was stored, but all other functions were skipped because ${process.env.NETLIFY_NEXT_PLUGIN_SKIP
114
129
  ? `NETLIFY_NEXT_PLUGIN_SKIP is set`
115
130
  : `NEXT_PLUGIN_FORCE_RUN is set to ${process.env.NEXT_PLUGIN_FORCE_RUN}`}`,
@@ -125,5 +140,24 @@ const plugin = {
125
140
  (0, verification_1.warnForRootRedirects)({ appDir });
126
141
  },
127
142
  };
128
- module.exports = plugin;
143
+ // The types haven't been updated yet
144
+ const nextRuntime = (_inputs, meta = {}) => {
145
+ var _a;
146
+ if (!((_a = meta === null || meta === void 0 ? void 0 : meta.events) === null || _a === void 0 ? void 0 : _a.has('onPreDev'))) {
147
+ return {
148
+ ...plugin,
149
+ onEnd: ({ utils }) => {
150
+ utils.status.show({
151
+ title: 'Please upgrade to the latest version of the Netlify CLI',
152
+ summary: 'To support for the latest Next.js features, please upgrade to the latest version of the Netlify CLI',
153
+ });
154
+ },
155
+ };
156
+ }
157
+ return {
158
+ ...plugin,
159
+ onPreDev: dev_1.onPreDev,
160
+ };
161
+ };
162
+ module.exports = nextRuntime;
129
163
  /* eslint-enable max-lines */
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getApiHandler = void 0;
4
+ // Aliasing like this means the editor may be able to syntax-highlight the string
5
+ const outdent_1 = require("outdent");
6
+ /* eslint-disable @typescript-eslint/no-var-requires */
7
+ const { Server } = require('http');
8
+ const path = require('path');
9
+ // eslint-disable-next-line n/prefer-global/url, n/prefer-global/url-search-params
10
+ const { URLSearchParams, URL } = require('url');
11
+ const { Bridge } = require('@vercel/node-bridge/bridge');
12
+ const { getMultiValueHeaders, getNextServer } = require('./handlerUtils');
13
+ // We return a function and then call `toString()` on it to serialise it as the launcher function
14
+ const makeHandler = (conf, app, pageRoot, page) => {
15
+ var _a;
16
+ // Change working directory into the site root, unless using Nx, which moves the
17
+ // dist directory and handles this itself
18
+ const dir = path.resolve(__dirname, app);
19
+ if (pageRoot.startsWith(dir)) {
20
+ process.chdir(dir);
21
+ }
22
+ // This is just so nft knows about the page entrypoints. It's not actually used
23
+ try {
24
+ // eslint-disable-next-line n/no-missing-require
25
+ require.resolve('./pages.js');
26
+ }
27
+ catch { }
28
+ // React assumes you want development mode if NODE_ENV is unset.
29
+ // eslint-disable-next-line @typescript-eslint/no-extra-semi
30
+ ;
31
+ (_a = process.env).NODE_ENV || (_a.NODE_ENV = 'production');
32
+ // We don't want to write ISR files to disk in the lambda environment
33
+ conf.experimental.isrFlushToDisk = false;
34
+ // This is our flag that we use when patching the source
35
+ // eslint-disable-next-line no-underscore-dangle
36
+ process.env._BYPASS_SSG = 'true';
37
+ for (const [key, value] of Object.entries(conf.env)) {
38
+ process.env[key] = String(value);
39
+ }
40
+ // We memoize this because it can be shared between requests, but don't instantiate it until
41
+ // the first request because we need the host and port.
42
+ let bridge;
43
+ const getBridge = (event) => {
44
+ if (bridge) {
45
+ return bridge;
46
+ }
47
+ // Scheduled functions don't have a URL, but we need to give one so Next knows the route to serve
48
+ const url = event.rawUrl ? new URL(event.rawUrl) : new URL(path, process.env.URL || 'http://n');
49
+ const port = Number.parseInt(url.port) || 80;
50
+ const NextServer = getNextServer();
51
+ const nextServer = new NextServer({
52
+ // We know we're just an API route, so can enable minimal mode
53
+ minimalMode: true,
54
+ conf,
55
+ dir,
56
+ customServer: false,
57
+ hostname: url.hostname,
58
+ port,
59
+ });
60
+ const requestHandler = nextServer.getRequestHandler();
61
+ const server = new Server(async (req, res) => {
62
+ try {
63
+ await requestHandler(req, res);
64
+ }
65
+ catch (error) {
66
+ console.error(error);
67
+ throw new Error('Error handling request. See function logs for details.');
68
+ }
69
+ });
70
+ bridge = new Bridge(server);
71
+ bridge.listen();
72
+ return bridge;
73
+ };
74
+ return async function handler(event, context) {
75
+ // Ensure that paths are encoded - but don't double-encode them
76
+ event.path = event.rawUrl ? new URL(event.rawUrl).pathname : page;
77
+ // Next expects to be able to parse the query from the URL
78
+ const query = new URLSearchParams(event.queryStringParameters).toString();
79
+ event.path = query ? `${event.path}?${query}` : event.path;
80
+ // We know the page
81
+ event.headers['x-matched-path'] = page;
82
+ const { headers, ...result } = await getBridge(event).launcher(event, context);
83
+ // Convert all headers to multiValueHeaders
84
+ const multiValueHeaders = getMultiValueHeaders(headers);
85
+ multiValueHeaders['cache-control'] = ['public, max-age=0, must-revalidate'];
86
+ console.log(`[${event.httpMethod}] ${event.path} (API)`);
87
+ return {
88
+ ...result,
89
+ multiValueHeaders,
90
+ isBase64Encoded: result.encoding === 'base64',
91
+ };
92
+ };
93
+ };
94
+ /**
95
+ * Handlers for API routes are simpler than page routes, but they each have a separate one
96
+ */
97
+ const getApiHandler = ({ page, config, publishDir = '../../../.next', appDir = '../../..', }) =>
98
+ // This is a string, but if you have the right editor plugin it should format as js
99
+ (0, outdent_1.outdent /* javascript */) `
100
+ const { Server } = require("http");
101
+ // We copy the file here rather than requiring from the node module
102
+ const { Bridge } = require("./bridge");
103
+ const { getMaxAge, getMultiValueHeaders, getNextServer } = require('./handlerUtils')
104
+
105
+ ${config.type === 'experimental-scheduled' ? `const { schedule } = require("@netlify/functions")` : ''}
106
+
107
+
108
+ const { config } = require("${publishDir}/required-server-files.json")
109
+ let staticManifest
110
+ const path = require("path");
111
+ const pageRoot = path.resolve(path.join(__dirname, "${publishDir}", "serverless", "pages"));
112
+ const handler = (${makeHandler.toString()})(config, "${appDir}", pageRoot, ${JSON.stringify(page)})
113
+ exports.handler = ${config.type === 'experimental-scheduled' ? `schedule(${JSON.stringify(config.schedule)}, handler);` : 'handler'}
114
+ `;
115
+ exports.getApiHandler = getApiHandler;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getHandler = void 0;
4
+ // Aliasing like this means the editor may be able to syntax-highlight the string
4
5
  const outdent_1 = require("outdent");
5
6
  /* eslint-disable @typescript-eslint/no-var-requires */
6
7
  const { promises } = require('fs');
@@ -119,14 +120,14 @@ const makeHandler = (conf, app, pageRoot, staticManifest = [], mode = 'ssr') =>
119
120
  };
120
121
  const getHandler = ({ isODB = false, publishDir = '../../../.next', appDir = '../../..' }) =>
121
122
  // This is a string, but if you have the right editor plugin it should format as js
122
- (0, outdent_1.outdent) `
123
+ (0, outdent_1.outdent /* javascript */) `
123
124
  const { Server } = require("http");
124
125
  const { promises } = require("fs");
125
126
  // We copy the file here rather than requiring from the node module
126
127
  const { Bridge } = require("./bridge");
127
128
  const { augmentFsModule, getMaxAge, getMultiValueHeaders, getNextServer } = require('./handlerUtils')
128
129
 
129
- const { builder } = require("@netlify/functions");
130
+ ${isODB ? `const { builder } = require("@netlify/functions")` : ''}
130
131
  const { config } = require("${publishDir}/required-server-files.json")
131
132
  let staticManifest
132
133
  try {
@@ -3,12 +3,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.getPageResolver = void 0;
6
+ exports.getSinglePageResolver = exports.getPageResolver = void 0;
7
7
  const path_1 = require("path");
8
8
  const outdent_1 = require("outdent");
9
+ const pathe_1 = require("pathe");
9
10
  const slash_1 = __importDefault(require("slash"));
10
11
  const tiny_glob_1 = __importDefault(require("tiny-glob"));
11
12
  const constants_1 = require("../constants");
13
+ const files_1 = require("../helpers/files");
12
14
  // Generate a file full of require.resolve() calls for all the pages in the
13
15
  // build. This is used by the nft bundler to find all the pages.
14
16
  const getPageResolver = async ({ publish, target }) => {
@@ -31,3 +33,22 @@ const getPageResolver = async ({ publish, target }) => {
31
33
  `;
32
34
  };
33
35
  exports.getPageResolver = getPageResolver;
36
+ /**
37
+ * API routes only need the dependencies for a single entrypoint, so we use the
38
+ * NFT trace file to get the dependencies.
39
+ */
40
+ const getSinglePageResolver = async ({ functionsDir, sourceFile, }) => {
41
+ const dependencies = await (0, files_1.getDependenciesOfFile)(sourceFile);
42
+ // We don't need the actual name, just the relative path.
43
+ const functionDir = (0, pathe_1.resolve)(functionsDir, 'functionName');
44
+ const pageFiles = [sourceFile, ...dependencies]
45
+ .map((file) => `require.resolve('${(0, pathe_1.relative)(functionDir, file)}')`)
46
+ .sort();
47
+ return (0, outdent_1.outdent /* javascript */) `
48
+ // This file is purely to allow nft to know about these pages.
49
+ try {
50
+ ${pageFiles.join('\n ')}
51
+ } catch {}
52
+ `;
53
+ };
54
+ exports.getSinglePageResolver = getSinglePageResolver;
package/manifest.yml CHANGED
@@ -1 +1 @@
1
- name: netlify-plugin-nextjs-experimental
1
+ name: netlify-next-runtime-experimental
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/plugin-nextjs",
3
- "version": "4.16.0",
3
+ "version": "4.17.1-api-routes.0",
4
4
  "description": "Run Next.js seamlessly on Netlify",
5
5
  "main": "lib/index.js",
6
6
  "files": [
@@ -9,10 +9,12 @@
9
9
  "manifest.yml"
10
10
  ],
11
11
  "dependencies": {
12
- "@netlify/functions": "^1.1.0",
13
- "@netlify/ipx": "^1.2.1",
12
+ "@netlify/esbuild": "0.14.25",
13
+ "@netlify/functions": "^1.2.0",
14
+ "@netlify/ipx": "^1.2.2",
14
15
  "@vercel/node-bridge": "^2.1.0",
15
16
  "chalk": "^4.1.2",
17
+ "execa": "^5.1.1",
16
18
  "fs-extra": "^10.0.0",
17
19
  "globby": "^11.0.4",
18
20
  "moize": "^6.1.0",
@@ -22,13 +24,13 @@
22
24
  "p-limit": "^3.1.0",
23
25
  "pathe": "^0.2.0",
24
26
  "pretty-bytes": "^5.6.0",
25
- "slash": "^3.0.0",
26
27
  "semver": "^7.3.5",
28
+ "slash": "^3.0.0",
27
29
  "tiny-glob": "^0.2.9"
28
30
  },
29
31
  "devDependencies": {
30
32
  "@delucis/if-env": "^1.1.2",
31
- "@netlify/build": "^27.11.4",
33
+ "@netlify/build": "^27.15.6",
32
34
  "@types/fs-extra": "^9.0.13",
33
35
  "@types/jest": "^27.4.1",
34
36
  "@types/node": "^17.0.25",
@@ -61,4 +63,4 @@
61
63
  "engines": {
62
64
  "node": ">=12.0.0"
63
65
  }
64
- }
66
+ }
@@ -0,0 +1,89 @@
1
+ import { NextRequest } from 'https://esm.sh/v91/next@12.2.5/deno/dist/server/web/spec-extension/request.js'
2
+ import { NextResponse } from 'https://esm.sh/v91/next@12.2.5/deno/dist/server/web/spec-extension/response.js'
3
+ import { fromFileUrl } from 'https://deno.land/std@0.151.0/path/mod.ts'
4
+ import { buildResponse } from './utils.ts'
5
+
6
+ globalThis.NFRequestContextMap ||= new Map()
7
+ globalThis.__dirname = fromFileUrl(new URL('./', import.meta.url)).slice(0, -1)
8
+
9
+ // Check if a file exists, given a relative path
10
+ const exists = async (relativePath) => {
11
+ const path = fromFileUrl(new URL(relativePath, import.meta.url))
12
+ try {
13
+ await Deno.stat(path)
14
+ return true
15
+ } catch (error) {
16
+ if (error instanceof Deno.errors.NotFound) {
17
+ return false
18
+ }
19
+ throw error
20
+ }
21
+ }
22
+ let idx = 0
23
+
24
+ const handler = async (req, context) => {
25
+ // Uncomment when CLI update lands
26
+ // if (!Deno.env.get('NETLIFY_DEV')) {
27
+ // // Only run in dev
28
+ // return
29
+ // }
30
+
31
+ let middleware
32
+ // Dynamic imports and FS operations aren't allowed when deployed,
33
+ // but that's fine because this is only ever used locally.
34
+ // We don't want to just try importing and use that to test,
35
+ // because that would also throw if there's an error in the middleware,
36
+ // which we would want to surface not ignore.
37
+ if (await exists('../../middleware.js')) {
38
+ // We need to cache-bust the import because otherwise it will claim it
39
+ // doesn't exist if the user creates it after the server starts
40
+ const nextMiddleware = await import(`../../middleware.js#${idx++}`)
41
+ middleware = nextMiddleware.middleware
42
+ } else {
43
+ // No middleware, so we silently return
44
+ return
45
+ }
46
+
47
+ // This is the format expected by Next.js
48
+ const geo = {
49
+ country: context.geo.country?.code,
50
+ region: context.geo.subdivision?.code,
51
+ city: context.geo.city,
52
+ }
53
+
54
+ // A default request id is fine locally
55
+ const requestId = req.headers.get('x-nf-request-id') || 'request-id'
56
+
57
+ globalThis.NFRequestContextMap.set(requestId, {
58
+ request: req,
59
+ context,
60
+ })
61
+
62
+ const request = {
63
+ headers: Object.fromEntries(req.headers.entries()),
64
+ geo,
65
+ method: req.method,
66
+ ip: context.ip,
67
+ body: req.body || undefined,
68
+ }
69
+
70
+ const nextRequest = new NextRequest(req, request)
71
+
72
+ try {
73
+ const response = await middleware(nextRequest)
74
+ return buildResponse({
75
+ result: { response: response || NextResponse.next(), waitUntil: Promise.resolve() },
76
+ request: req,
77
+ context,
78
+ })
79
+ } catch (error) {
80
+ console.error(error)
81
+ return new Response(error.message, { status: 500 })
82
+ } finally {
83
+ if (requestId) {
84
+ globalThis.NFRequestContextMap.delete(requestId)
85
+ }
86
+ }
87
+ }
88
+
89
+ export default handler
@@ -45,6 +45,10 @@ declare global {
45
45
  globalThis.NFRequestContextMap ||= new Map()
46
46
 
47
47
  const handler = async (req: Request, context: Context) => {
48
+ if (Deno.env.get('NETLIFY_DEV')) {
49
+ // Don't run in dev
50
+ return
51
+ }
48
52
  const url = new URL(req.url)
49
53
 
50
54
  const geo = {
@@ -123,17 +123,38 @@ export const buildResponse = async ({
123
123
  request.headers.set('x-nf-next-middleware', 'skip')
124
124
 
125
125
  const rewrite = res.headers.get('x-middleware-rewrite')
126
+
127
+ // Data requests (i.e. requests for /_next/data ) need special handling
128
+ const isDataReq = request.headers.get('x-nextjs-data')
129
+
126
130
  if (rewrite) {
127
131
  const rewriteUrl = new URL(rewrite, request.url)
128
132
  const baseUrl = new URL(request.url)
133
+ const relativeUrl = relativizeURL(rewrite, request.url)
134
+
135
+ // Data requests might be rewritten to an external URL
136
+ // This header tells the client router the redirect target, and if it's external then it will do a full navigation
137
+ if (isDataReq) {
138
+ res.headers.set('x-nextjs-rewrite', relativeUrl)
139
+ }
129
140
  if (rewriteUrl.hostname !== baseUrl.hostname) {
130
141
  // Netlify Edge Functions don't support proxying to external domains, but Next middleware does
131
142
  const proxied = fetch(new Request(rewriteUrl.toString(), request))
132
143
  return addMiddlewareHeaders(proxied, res)
133
144
  }
134
- res.headers.set('x-middleware-rewrite', relativizeURL(rewrite, request.url))
145
+ res.headers.set('x-middleware-rewrite', relativeUrl)
146
+
135
147
  return addMiddlewareHeaders(context.rewrite(rewrite), res)
136
148
  }
149
+
150
+ const redirect = res.headers.get('Location')
151
+
152
+ // Data requests shouldn;t automatically redirect in the browser (they might be HTML pages): they're handled by the router
153
+ if (redirect && isDataReq) {
154
+ res.headers.delete('location')
155
+ res.headers.set('x-nextjs-redirect', relativizeURL(redirect, request.url))
156
+ }
157
+
137
158
  if (res.headers.get('x-middleware-next') === '1') {
138
159
  return addMiddlewareHeaders(context.next(), res)
139
160
  }