@netlify/plugin-nextjs 4.15.0 → 4.17.1-runtime.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.
@@ -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'))) {
@@ -0,0 +1,42 @@
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
+ // Need to patch the files, because build might not have been run
15
+ await (0, files_1.patchNextFiles)((0, path_1.resolve)(netlifyConfig.build.publish, '..'));
16
+ // Clean up old functions
17
+ await (0, fs_extra_1.unlink)((0, path_1.resolve)('.netlify', 'middleware.js')).catch(() => {
18
+ // Ignore if it doesn't exist
19
+ });
20
+ await (0, edge_1.writeDevEdgeFunction)(constants);
21
+ if (!(0, fs_extra_1.existsSync)((0, path_1.resolve)(netlifyConfig.build.base, 'middleware.ts')) &&
22
+ !(0, fs_extra_1.existsSync)((0, path_1.resolve)(netlifyConfig.build.base, 'middleware.js'))) {
23
+ console.log("No middleware found. Create a 'middleware.ts' or 'middleware.js' file in your project root to add custom middleware.");
24
+ }
25
+ else {
26
+ console.log('Watching for changes in Next.js middleware...');
27
+ }
28
+ // Eventually we might want to do this via esbuild's API, but for now the CLI works fine
29
+ const childProcess = (0, execa_1.default)(`esbuild`, [
30
+ `--bundle`,
31
+ `--outdir=${(0, path_1.resolve)('.netlify')}`,
32
+ `--format=esm`,
33
+ '--watch',
34
+ // Watch for both, because it can have either ts or js
35
+ (0, path_1.resolve)(netlifyConfig.build.base, 'middleware.ts'),
36
+ (0, path_1.resolve)(netlifyConfig.build.base, 'middleware.js'),
37
+ ]);
38
+ childProcess.stdout.pipe(process.stdout);
39
+ childProcess.stderr.pipe(process.stderr);
40
+ // Don't return the promise because we don't want to wait for the child process to finish
41
+ };
42
+ 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.ts', target: 'index.ts' });
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 */
@@ -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');
@@ -229,6 +229,7 @@ const moveStaticPages = async ({ netlifyConfig, target, i18n, basePath, }) => {
229
229
  }
230
230
  };
231
231
  exports.moveStaticPages = moveStaticPages;
232
+ const PATCH_WARNING = `/* File patched by Netlify */`;
232
233
  /**
233
234
  * Attempt to patch a source file, preserving a backup
234
235
  */
@@ -237,7 +238,11 @@ const patchFile = async ({ file, replacements, }) => {
237
238
  console.warn('File was not found');
238
239
  return false;
239
240
  }
240
- const content = await (0, fs_extra_1.readFile)(file, 'utf8');
241
+ let content = await (0, fs_extra_1.readFile)(file, 'utf8');
242
+ // If the file has already been patched, patch the backed-up original instead
243
+ if (content.includes(PATCH_WARNING) && (0, fs_extra_1.existsSync)(`${file}.orig`)) {
244
+ content = await (0, fs_extra_1.readFile)(`${file}.orig`, 'utf8');
245
+ }
241
246
  const newContent = replacements.reduce((acc, [from, to]) => {
242
247
  if (acc.includes(to)) {
243
248
  console.log('Already patched. Skipping.');
@@ -250,7 +255,7 @@ const patchFile = async ({ file, replacements, }) => {
250
255
  return false;
251
256
  }
252
257
  await (0, fs_extra_1.writeFile)(`${file}.orig`, content);
253
- await (0, fs_extra_1.writeFile)(file, newContent);
258
+ await (0, fs_extra_1.writeFile)(file, `${newContent}\n${PATCH_WARNING}`);
254
259
  console.log('Done');
255
260
  return true;
256
261
  };
@@ -271,19 +276,19 @@ const baseServerReplacements = [
271
276
  const nextServerReplacements = [
272
277
  [
273
278
  `getMiddlewareManifest() {\n if (this.minimalMode) return null;`,
274
- `getMiddlewareManifest() {\n if (this.minimalMode || process.env.NEXT_USE_NETLIFY_EDGE) return null;`,
279
+ `getMiddlewareManifest() {\n if (this.minimalMode || !process.env.NEXT_DISABLE_NETLIFY_EDGE) return null;`,
275
280
  ],
276
281
  [
277
282
  `generateCatchAllMiddlewareRoute(devReady) {\n if (this.minimalMode) return []`,
278
- `generateCatchAllMiddlewareRoute(devReady) {\n if (this.minimalMode || process.env.NEXT_USE_NETLIFY_EDGE) return [];`,
283
+ `generateCatchAllMiddlewareRoute(devReady) {\n if (this.minimalMode || !process.env.NEXT_DISABLE_NETLIFY_EDGE) return [];`,
279
284
  ],
280
285
  [
281
286
  `generateCatchAllMiddlewareRoute() {\n if (this.minimalMode) return undefined;`,
282
- `generateCatchAllMiddlewareRoute() {\n if (this.minimalMode || process.env.NEXT_USE_NETLIFY_EDGE) return undefined;`,
287
+ `generateCatchAllMiddlewareRoute() {\n if (this.minimalMode || !process.env.NEXT_DISABLE_NETLIFY_EDGE) return undefined;`,
283
288
  ],
284
289
  [
285
290
  `getMiddlewareManifest() {\n if (this.minimalMode) {`,
286
- `getMiddlewareManifest() {\n if (!this.minimalMode && !process.env.NEXT_USE_NETLIFY_EDGE) {`,
291
+ `getMiddlewareManifest() {\n if (!this.minimalMode && process.env.NEXT_DISABLE_NETLIFY_EDGE) {`,
287
292
  ],
288
293
  ];
289
294
  const patchNextFiles = async (root) => {
@@ -136,7 +136,7 @@ const findModuleFromBase = ({ paths, candidates }) => {
136
136
  exports.findModuleFromBase = findModuleFromBase;
137
137
  const isNextAuthInstalled = () => {
138
138
  try {
139
- // eslint-disable-next-line import/no-unassigned-import, import/no-unresolved, n/no-missing-require
139
+ // eslint-disable-next-line import/no-unassigned-import, import/no-extraneous-dependencies, n/no-extraneous-require
140
140
  require('next-auth');
141
141
  return true;
142
142
  }
@@ -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 (Object.keys(middlewareManifest === null || middlewareManifest === void 0 ? void 0 : 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 (Object.keys(middlewareManifest === null || middlewareManifest === void 0 ? void 0 : 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
  }
@@ -87,29 +109,20 @@ const plugin = {
87
109
  nextConfig: { basePath, i18n, trailingSlash, appDir },
88
110
  buildId,
89
111
  });
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) {
112
+ if (usingEdge) {
113
+ await (0, edge_1.writeEdgeFunctions)(netlifyConfig);
114
+ await (0, edge_1.enableEdgeInNextConfig)(publish);
93
115
  console.log((0, outdent_1.outdent) `
94
- ✨ Deploying to ${(0, chalk_1.greenBright) `Netlify Edge Functions`} ✨
116
+ ✨ Deploying middleware and functions to ${(0, chalk_1.greenBright) `Netlify Edge Functions`} ✨
95
117
  This feature is in beta. Please share your feedback here: https://ntl.fyi/next-netlify-edge
96
118
  `);
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
119
  }
107
120
  },
108
121
  async onPostBuild({ netlifyConfig: { build: { publish }, redirects, headers, }, utils: { status, cache, functions, build: { failBuild }, }, constants: { FUNCTIONS_DIST }, }) {
109
122
  await (0, cache_1.saveCache)({ cache, publish });
110
123
  if ((0, utils_1.shouldSkip)()) {
111
124
  status.show({
112
- title: 'Essential Next.js plugin did not run',
125
+ title: 'Next Runtime did not run',
113
126
  summary: `Next cache was stored, but all other functions were skipped because ${process.env.NETLIFY_NEXT_PLUGIN_SKIP
114
127
  ? `NETLIFY_NEXT_PLUGIN_SKIP is set`
115
128
  : `NEXT_PLUGIN_FORCE_RUN is set to ${process.env.NEXT_PLUGIN_FORCE_RUN}`}`,
@@ -123,8 +136,26 @@ const plugin = {
123
136
  (0, config_1.generateCustomHeaders)(nextConfig, headers);
124
137
  (0, verification_1.warnForProblematicUserRewrites)({ basePath, redirects });
125
138
  (0, verification_1.warnForRootRedirects)({ appDir });
126
- await (0, files_1.unpatchNextFiles)(basePath);
127
139
  },
128
140
  };
129
- module.exports = plugin;
141
+ // The types haven't been updated yet
142
+ const nextRuntime = (_inputs, meta = {}) => {
143
+ var _a;
144
+ if (!((_a = meta === null || meta === void 0 ? void 0 : meta.events) === null || _a === void 0 ? void 0 : _a.has('onPreDev'))) {
145
+ return {
146
+ ...plugin,
147
+ onEnd: ({ utils }) => {
148
+ utils.status.show({
149
+ title: 'Please upgrade to the latest version of the Netlify CLI',
150
+ summary: 'To support for the latest Next.js features, please upgrade to the latest version of the Netlify CLI',
151
+ });
152
+ },
153
+ };
154
+ }
155
+ return {
156
+ ...plugin,
157
+ onPreDev: dev_1.onPreDev,
158
+ };
159
+ };
160
+ module.exports = nextRuntime;
130
161
  /* eslint-enable max-lines */
@@ -81,6 +81,13 @@ const makeHandler = (conf, app, pageRoot, staticManifest = [], mode = 'ssr') =>
81
81
  // Next expects to be able to parse the query from the URL
82
82
  const query = new URLSearchParams(event.queryStringParameters).toString();
83
83
  event.path = query ? `${event.path}?${query}` : event.path;
84
+ const graphToken = event.netlifyGraphToken;
85
+ if (graphToken && requestMode !== 'ssr') {
86
+ // Prefix with underscore to help us determine the origin of the token
87
+ // allows us to write better error messages
88
+ // eslint-disable-next-line no-underscore-dangle
89
+ process.env._NETLIFY_GRAPH_TOKEN = graphToken;
90
+ }
84
91
  const { headers, ...result } = await getBridge(event).launcher(event, context);
85
92
  // Convert all headers to multiValueHeaders
86
93
  const multiValueHeaders = getMultiValueHeaders(headers);
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.15.0",
3
+ "version": "4.17.1-runtime.1",
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.0.0",
13
- "@netlify/ipx": "^1.2.0",
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.14.0",
32
34
  "@types/fs-extra": "^9.0.13",
33
35
  "@types/jest": "^27.4.1",
34
36
  "@types/node": "^17.0.25",
@@ -51,14 +53,13 @@
51
53
  },
52
54
  "repository": {
53
55
  "type": "git",
54
- "url": "git+https://github.com/netlify/netlify-plugin-nextjs.git"
56
+ "url": "git+https://github.com/netlify/next-runtime.git"
55
57
  },
56
- "author": "",
57
58
  "license": "MIT",
58
59
  "bugs": {
59
- "url": "https://github.com/netlify/netlify-plugin-nextjs/issues"
60
+ "url": "https://github.com/netlify/next-runtime/issues"
60
61
  },
61
- "homepage": "https://github.com/netlify/netlify-plugin-nextjs#readme",
62
+ "homepage": "https://github.com/netlify/next-runtime#readme",
62
63
  "engines": {
63
64
  "node": ">=12.0.0"
64
65
  }
@@ -0,0 +1,155 @@
1
+ import type { Context } from 'https://edge.netlify.com'
2
+ import { NextRequest, NextResponse } from 'https://esm.sh/next/server'
3
+ import { fromFileUrl } from 'https://deno.land/std/path/mod.ts'
4
+ import { buildResponse } from './utils.ts'
5
+
6
+ export interface FetchEventResult {
7
+ response: Response
8
+ waitUntil: Promise<unknown>
9
+ }
10
+
11
+ interface I18NConfig {
12
+ defaultLocale: string
13
+ domains?: DomainLocale[]
14
+ localeDetection?: false
15
+ locales: string[]
16
+ }
17
+
18
+ interface DomainLocale {
19
+ defaultLocale: string
20
+ domain: string
21
+ http?: true
22
+ locales?: string[]
23
+ }
24
+ export interface NextRequestInit extends RequestInit {
25
+ geo?: {
26
+ city?: string
27
+ country?: string
28
+ region?: string
29
+ }
30
+ ip?: string
31
+ nextConfig?: {
32
+ basePath?: string
33
+ i18n?: I18NConfig | null
34
+ trailingSlash?: boolean
35
+ }
36
+ }
37
+
38
+ export interface RequestData {
39
+ geo?: {
40
+ city?: string
41
+ country?: string
42
+ region?: string
43
+ latitude?: string
44
+ longitude?: string
45
+ }
46
+ headers: Record<string, string>
47
+ ip?: string
48
+ method: string
49
+ nextConfig?: {
50
+ basePath?: string
51
+ i18n?: Record<string, unknown>
52
+ trailingSlash?: boolean
53
+ }
54
+ page?: {
55
+ name?: string
56
+ params?: { [key: string]: string }
57
+ }
58
+ url: string
59
+ body?: ReadableStream<Uint8Array>
60
+ }
61
+
62
+ export interface RequestContext {
63
+ request: Request
64
+ context: Context
65
+ }
66
+
67
+ declare global {
68
+ // deno-lint-ignore no-var
69
+ var NFRequestContextMap: Map<string, RequestContext>
70
+ // deno-lint-ignore no-var
71
+ var __dirname: string
72
+ }
73
+
74
+ globalThis.NFRequestContextMap ||= new Map()
75
+ globalThis.__dirname = fromFileUrl(new URL('./', import.meta.url)).slice(0, -1)
76
+
77
+ // Check if a file exists, given a relative path
78
+ const exists = async (relativePath: string) => {
79
+ const path = fromFileUrl(new URL(relativePath, import.meta.url))
80
+ try {
81
+ await Deno.stat(path)
82
+ return true
83
+ } catch (error) {
84
+ if (error instanceof Deno.errors.NotFound) {
85
+ return false
86
+ }
87
+ throw error
88
+ }
89
+ }
90
+
91
+ const handler = async (req: Request, context: Context) => {
92
+ // Uncomment when CLI update lands
93
+ // if (!Deno.env.get('NETLIFY_DEV')) {
94
+ // // Only run in dev
95
+ // return
96
+ // }
97
+
98
+ let middleware
99
+ // Dynamic imports and FS operations aren't allowed when deployed,
100
+ // but that's fine because this is only ever used locally.
101
+ // We don't want to just try importing and use that to test,
102
+ // because that would also throw if there's an error in the middleware,
103
+ // which we would want to surface not ignore.
104
+ if (await exists('../../middleware.js')) {
105
+ // These will be user code
106
+ const nextMiddleware = await import('../../middleware.js')
107
+ middleware = nextMiddleware.middleware
108
+ } else {
109
+ // No middleware, so we silently return
110
+ return
111
+ }
112
+
113
+ // This is the format expected by Next.js
114
+ const geo: NextRequestInit['geo'] = {
115
+ country: context.geo.country?.code,
116
+ region: context.geo.subdivision?.code,
117
+ city: context.geo.city,
118
+ }
119
+
120
+ // A default request id is fine locally
121
+ const requestId = req.headers.get('x-nf-request-id') || 'request-id'
122
+
123
+ globalThis.NFRequestContextMap.set(requestId, {
124
+ request: req,
125
+ context,
126
+ })
127
+
128
+ const request: NextRequestInit = {
129
+ headers: Object.fromEntries(req.headers.entries()),
130
+ geo,
131
+ method: req.method,
132
+ ip: context.ip,
133
+ body: req.body || undefined,
134
+ }
135
+
136
+ const nextRequest: NextRequest = new NextRequest(req, request)
137
+
138
+ try {
139
+ const response = await middleware(nextRequest)
140
+ return buildResponse({
141
+ result: { response: response || NextResponse.next(), waitUntil: Promise.resolve() },
142
+ request: req,
143
+ context,
144
+ })
145
+ } catch (error) {
146
+ console.error(error)
147
+ return new Response(error.message, { status: 500 })
148
+ } finally {
149
+ if (requestId) {
150
+ globalThis.NFRequestContextMap.delete(requestId)
151
+ }
152
+ }
153
+ }
154
+
155
+ 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
  }