@netlify/plugin-nextjs 5.0.0-beta.2 → 5.0.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +5 -2
  2. package/dist/build/cache.js +4 -4
  3. package/dist/build/content/prerendered.js +5 -5
  4. package/dist/build/content/server.js +5 -5
  5. package/dist/build/content/static.js +5 -5
  6. package/dist/build/functions/edge.js +6 -5
  7. package/dist/build/functions/server.js +7 -7
  8. package/dist/build/image-cdn.js +4 -4
  9. package/dist/build/plugin-context.js +4 -4
  10. package/dist/esm-chunks/{chunk-5SZ5JD6J.js → chunk-6HBXWNZS.js} +27 -9
  11. package/dist/esm-chunks/{chunk-S5JXJCXP.js → chunk-DZM5AUXX.js} +4 -4
  12. package/dist/esm-chunks/{chunk-H46DW7YI.js → chunk-EPSI5TTB.js} +2 -2
  13. package/dist/esm-chunks/{chunk-TJKO3X6O.js → chunk-IZY5TPZE.js} +27 -3
  14. package/dist/esm-chunks/{chunk-4AJYXTWN.js → chunk-K233JI4O.js} +2 -2
  15. package/dist/esm-chunks/{chunk-Z7ZMLVTM.js → chunk-KMPIVUVT.js} +4 -4
  16. package/dist/esm-chunks/{chunk-AVWFCGVE.js → chunk-MFN4GH7U.js} +3 -3
  17. package/dist/esm-chunks/{chunk-B6QMRLBH.js → chunk-NE4HYI2D.js} +3 -3
  18. package/dist/esm-chunks/{chunk-RSKIKBZH.js → chunk-WELZ7LFO.js} +2 -2
  19. package/dist/esm-chunks/{chunk-ALO2SSMH.js → chunk-X4Q4MYBW.js} +39 -28
  20. package/dist/esm-chunks/chunk-X7XIMV6B.js +105 -0
  21. package/dist/esm-chunks/{chunk-GGHAQM5D.js → chunk-XA2CZH5Y.js} +7 -3
  22. package/dist/esm-chunks/{chunk-3PTPU5GO.js → chunk-XC7T747I.js} +48 -15
  23. package/dist/esm-chunks/chunk-XIP2W57K.js +39 -0
  24. package/dist/esm-chunks/{chunk-SMSOJ2OS.js → chunk-YG3G7B2X.js} +2 -2
  25. package/dist/index.js +18 -14
  26. package/dist/run/config.js +5 -5
  27. package/dist/run/constants.js +4 -4
  28. package/dist/run/handlers/server.js +16 -14
  29. package/dist/run/headers.js +4 -4
  30. package/dist/run/revalidate.js +4 -4
  31. package/dist/run/systemlog.js +9 -8
  32. package/edge-runtime/lib/headers.ts +5 -0
  33. package/edge-runtime/lib/logging.ts +5 -0
  34. package/edge-runtime/lib/next-request.ts +43 -1
  35. package/edge-runtime/lib/response.ts +81 -19
  36. package/edge-runtime/lib/util.test.ts +39 -0
  37. package/edge-runtime/lib/util.ts +79 -7
  38. package/edge-runtime/middleware.ts +19 -2
  39. package/edge-runtime/next.config.json +1 -0
  40. package/edge-runtime/vendor/import_map.json +2 -1
  41. package/edge-runtime/vendor/v1-7-0--edge-utils.netlify.app/logger/logger.ts +164 -0
  42. package/edge-runtime/vendor/v1-7-0--edge-utils.netlify.app/logger/mod.ts +1 -0
  43. package/edge-runtime/vendor.ts +2 -0
  44. package/package.json +1 -1
  45. package/dist/esm-chunks/chunk-R4NHZWGU.js +0 -32
  46. package/dist/esm-chunks/chunk-YZXA5QBC.js +0 -60
@@ -1,28 +1,55 @@
1
1
 
2
- const require = await (async () => {
3
- const { createRequire } = await import("node:module");
2
+ var require = await (async () => {
3
+ var { createRequire } = await import("node:module");
4
4
  return createRequire(import.meta.url);
5
5
  })();
6
6
 
7
+ import {
8
+ require_out
9
+ } from "./chunk-MFN4GH7U.js";
7
10
  import {
8
11
  EDGE_HANDLER_NAME
9
- } from "./chunk-TJKO3X6O.js";
12
+ } from "./chunk-IZY5TPZE.js";
13
+ import {
14
+ __toESM
15
+ } from "./chunk-WELZ7LFO.js";
10
16
 
11
17
  // src/build/functions/edge.ts
18
+ var import_fast_glob = __toESM(require_out(), 1);
12
19
  import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
13
20
  import { dirname, join } from "node:path";
14
21
  var writeEdgeManifest = async (ctx, manifest) => {
15
22
  await mkdir(ctx.edgeFunctionsDir, { recursive: true });
16
23
  await writeFile(join(ctx.edgeFunctionsDir, "manifest.json"), JSON.stringify(manifest, null, 2));
17
24
  };
25
+ var copyRuntime = async (ctx, handlerDirectory) => {
26
+ const files = await (0, import_fast_glob.glob)("edge-runtime/**/*", {
27
+ cwd: ctx.pluginDir,
28
+ ignore: ["**/*.test.ts"],
29
+ dot: true
30
+ });
31
+ await Promise.all(
32
+ files.map(
33
+ (path) => cp(join(ctx.pluginDir, path), join(handlerDirectory, path), { recursive: true })
34
+ )
35
+ );
36
+ };
18
37
  var writeHandlerFile = async (ctx, { matchers, name }) => {
38
+ const nextConfig = await ctx.getBuildConfig();
19
39
  const handlerName = getHandlerName({ name });
20
40
  const handlerDirectory = join(ctx.edgeFunctionsDir, handlerName);
21
41
  const handlerRuntimeDirectory = join(handlerDirectory, "edge-runtime");
22
- await cp(join(ctx.pluginDir, "edge-runtime"), handlerRuntimeDirectory, {
23
- recursive: true
24
- });
42
+ await copyRuntime(ctx, handlerDirectory);
25
43
  await writeFile(join(handlerRuntimeDirectory, "matchers.json"), JSON.stringify(matchers));
44
+ const minimalNextConfig = {
45
+ basePath: nextConfig.basePath,
46
+ i18n: nextConfig.i18n,
47
+ trailingSlash: nextConfig.trailingSlash
48
+ };
49
+ await writeFile(
50
+ join(handlerRuntimeDirectory, "next.config.json"),
51
+ JSON.stringify(minimalNextConfig)
52
+ );
26
53
  await writeFile(
27
54
  join(handlerDirectory, `${handlerName}.js`),
28
55
  `
@@ -34,7 +61,7 @@ var writeHandlerFile = async (ctx, { matchers, name }) => {
34
61
  };
35
62
  var copyHandlerDependencies = async (ctx, { name, files }) => {
36
63
  const edgeRuntimePath = join(ctx.pluginDir, "edge-runtime");
37
- const srcDir = ctx.resolve(".next/standalone/.next");
64
+ const srcDir = join(ctx.standaloneDir, ".next");
38
65
  const shimPath = join(edgeRuntimePath, "shim/index.js");
39
66
  const shim = await readFile(shimPath, "utf8");
40
67
  const imports = `import './edge-runtime-webpack.js';`;
@@ -58,13 +85,19 @@ var createEdgeHandler = async (ctx, definition) => {
58
85
  await writeHandlerFile(ctx, definition);
59
86
  };
60
87
  var getHandlerName = ({ name }) => `${EDGE_HANDLER_NAME}-${name.replace(/\W/g, "-")}`;
61
- var buildHandlerDefinition = (ctx, { name, matchers, page }) => ({
62
- function: getHandlerName({ name }),
63
- name: name === "middleware" ? "Next.js Middleware Handler" : `Next.js Edge Handler: ${page}`,
64
- pattern: matchers[0].regexp,
65
- cache: name === "middleware" ? void 0 : "manual",
66
- generator: `${ctx.pluginName}@${ctx.pluginVersion}`
67
- });
88
+ var buildHandlerDefinition = (ctx, { name, matchers, page }) => {
89
+ const fun = getHandlerName({ name });
90
+ const funName = name === "middleware" ? "Next.js Middleware Handler" : `Next.js Edge Handler: ${page}`;
91
+ const cache = name === "middleware" ? void 0 : "manual";
92
+ const generator = `${ctx.pluginName}@${ctx.pluginVersion}`;
93
+ return matchers.map((matcher) => ({
94
+ function: fun,
95
+ name: funName,
96
+ pattern: matcher.regexp,
97
+ cache,
98
+ generator
99
+ }));
100
+ };
68
101
  var createEdgeHandlers = async (ctx) => {
69
102
  await rm(ctx.edgeFunctionsDir, { recursive: true, force: true });
70
103
  const nextManifest = await ctx.getMiddlewareManifest();
@@ -73,7 +106,7 @@ var createEdgeHandlers = async (ctx) => {
73
106
  // ...Object.values(nextManifest.functions)
74
107
  ];
75
108
  await Promise.all(nextDefinitions.map((def) => createEdgeHandler(ctx, def)));
76
- const netlifyDefinitions = nextDefinitions.map((def) => buildHandlerDefinition(ctx, def));
109
+ const netlifyDefinitions = nextDefinitions.flatMap((def) => buildHandlerDefinition(ctx, def));
77
110
  const netlifyManifest = {
78
111
  version: 1,
79
112
  functions: netlifyDefinitions
@@ -0,0 +1,39 @@
1
+
2
+ var require = await (async () => {
3
+ var { createRequire } = await import("node:module");
4
+ return createRequire(import.meta.url);
5
+ })();
6
+
7
+ import {
8
+ PLUGIN_DIR
9
+ } from "./chunk-K233JI4O.js";
10
+
11
+ // src/run/config.ts
12
+ import { existsSync } from "node:fs";
13
+ import { readFile } from "node:fs/promises";
14
+ import { join, resolve } from "node:path";
15
+ var getRunConfig = async () => {
16
+ return JSON.parse(await readFile(resolve(".next/required-server-files.json"), "utf-8")).config;
17
+ };
18
+ var setRunConfig = (config) => {
19
+ const cacheHandler = join(PLUGIN_DIR, "dist/run/handlers/cache.cjs");
20
+ if (!existsSync(cacheHandler)) {
21
+ throw new Error(`Cache handler not found at ${cacheHandler}`);
22
+ }
23
+ config.experimental = {
24
+ ...config.experimental,
25
+ incrementalCacheHandlerPath: cacheHandler
26
+ };
27
+ config.cacheHandler = cacheHandler;
28
+ config.cacheMaxMemorySize = 0;
29
+ process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(config);
30
+ };
31
+ var getTagsManifest = async () => {
32
+ return JSON.parse(await readFile(resolve(PLUGIN_DIR, ".netlify/tags-manifest.json"), "utf-8"));
33
+ };
34
+
35
+ export {
36
+ getRunConfig,
37
+ setRunConfig,
38
+ getTagsManifest
39
+ };
@@ -1,6 +1,6 @@
1
1
 
2
- const require = await (async () => {
3
- const { createRequire } = await import("node:module");
2
+ var require = await (async () => {
3
+ var { createRequire } = await import("node:module");
4
4
  return createRequire(import.meta.url);
5
5
  })();
6
6
 
package/dist/index.js CHANGED
@@ -1,44 +1,46 @@
1
1
 
2
- const require = await (async () => {
3
- const { createRequire } = await import("node:module");
2
+ var require = await (async () => {
3
+ var { createRequire } = await import("node:module");
4
4
  return createRequire(import.meta.url);
5
5
  })();
6
6
 
7
7
  import {
8
8
  copyFetchContent,
9
9
  copyPrerenderedContent
10
- } from "./esm-chunks/chunk-S5JXJCXP.js";
10
+ } from "./esm-chunks/chunk-DZM5AUXX.js";
11
11
  import {
12
12
  copyStaticAssets,
13
13
  copyStaticContent,
14
14
  publishStaticDir,
15
15
  unpublishStaticDir
16
- } from "./esm-chunks/chunk-Z7ZMLVTM.js";
16
+ } from "./esm-chunks/chunk-KMPIVUVT.js";
17
17
  import {
18
18
  createEdgeHandlers
19
- } from "./esm-chunks/chunk-3PTPU5GO.js";
19
+ } from "./esm-chunks/chunk-XC7T747I.js";
20
20
  import {
21
21
  createServerHandler
22
- } from "./esm-chunks/chunk-ALO2SSMH.js";
23
- import "./esm-chunks/chunk-5SZ5JD6J.js";
24
- import "./esm-chunks/chunk-AVWFCGVE.js";
22
+ } from "./esm-chunks/chunk-X4Q4MYBW.js";
23
+ import "./esm-chunks/chunk-6HBXWNZS.js";
24
+ import "./esm-chunks/chunk-MFN4GH7U.js";
25
25
  import {
26
26
  restoreBuildCache,
27
27
  saveBuildCache
28
- } from "./esm-chunks/chunk-GGHAQM5D.js";
28
+ } from "./esm-chunks/chunk-XA2CZH5Y.js";
29
29
  import {
30
30
  setImageConfig
31
- } from "./esm-chunks/chunk-H46DW7YI.js";
31
+ } from "./esm-chunks/chunk-EPSI5TTB.js";
32
32
  import {
33
33
  PluginContext
34
- } from "./esm-chunks/chunk-TJKO3X6O.js";
35
- import "./esm-chunks/chunk-RSKIKBZH.js";
34
+ } from "./esm-chunks/chunk-IZY5TPZE.js";
35
+ import "./esm-chunks/chunk-WELZ7LFO.js";
36
36
 
37
37
  // src/index.ts
38
38
  import { existsSync } from "node:fs";
39
39
  var onPreBuild = async (options) => {
40
40
  process.env.NEXT_PRIVATE_STANDALONE = "true";
41
- await restoreBuildCache(new PluginContext(options));
41
+ if (!options.constants.IS_LOCAL) {
42
+ await restoreBuildCache(new PluginContext(options));
43
+ }
42
44
  };
43
45
  var onBuild = async (options) => {
44
46
  const ctx = new PluginContext(options);
@@ -46,7 +48,9 @@ var onBuild = async (options) => {
46
48
  ctx.failBuild("Publish directory not found, please check your netlify.toml");
47
49
  }
48
50
  await setImageConfig(ctx);
49
- await saveBuildCache(ctx);
51
+ if (!options.constants.IS_LOCAL) {
52
+ await saveBuildCache(ctx);
53
+ }
50
54
  await Promise.all([
51
55
  copyStaticAssets(ctx),
52
56
  copyStaticContent(ctx),
@@ -1,6 +1,6 @@
1
1
 
2
- const require = await (async () => {
3
- const { createRequire } = await import("node:module");
2
+ var require = await (async () => {
3
+ var { createRequire } = await import("node:module");
4
4
  return createRequire(import.meta.url);
5
5
  })();
6
6
 
@@ -8,9 +8,9 @@ import {
8
8
  getRunConfig,
9
9
  getTagsManifest,
10
10
  setRunConfig
11
- } from "../esm-chunks/chunk-R4NHZWGU.js";
12
- import "../esm-chunks/chunk-4AJYXTWN.js";
13
- import "../esm-chunks/chunk-RSKIKBZH.js";
11
+ } from "../esm-chunks/chunk-XIP2W57K.js";
12
+ import "../esm-chunks/chunk-K233JI4O.js";
13
+ import "../esm-chunks/chunk-WELZ7LFO.js";
14
14
  export {
15
15
  getRunConfig,
16
16
  getTagsManifest,
@@ -1,14 +1,14 @@
1
1
 
2
- const require = await (async () => {
3
- const { createRequire } = await import("node:module");
2
+ var require = await (async () => {
3
+ var { createRequire } = await import("node:module");
4
4
  return createRequire(import.meta.url);
5
5
  })();
6
6
 
7
7
  import {
8
8
  MODULE_DIR,
9
9
  PLUGIN_DIR
10
- } from "../esm-chunks/chunk-4AJYXTWN.js";
11
- import "../esm-chunks/chunk-RSKIKBZH.js";
10
+ } from "../esm-chunks/chunk-K233JI4O.js";
11
+ import "../esm-chunks/chunk-WELZ7LFO.js";
12
12
  export {
13
13
  MODULE_DIR,
14
14
  PLUGIN_DIR
@@ -1,30 +1,30 @@
1
1
 
2
- const require = await (async () => {
3
- const { createRequire } = await import("node:module");
2
+ var require = await (async () => {
3
+ var { createRequire } = await import("node:module");
4
4
  return createRequire(import.meta.url);
5
5
  })();
6
6
 
7
7
  import {
8
- logger
9
- } from "../../esm-chunks/chunk-YZXA5QBC.js";
8
+ import_internal
9
+ } from "../../esm-chunks/chunk-X7XIMV6B.js";
10
10
  import {
11
11
  getTagsManifest
12
- } from "../../esm-chunks/chunk-R4NHZWGU.js";
13
- import "../../esm-chunks/chunk-4AJYXTWN.js";
12
+ } from "../../esm-chunks/chunk-XIP2W57K.js";
13
+ import "../../esm-chunks/chunk-K233JI4O.js";
14
14
  import {
15
15
  adjustDateHeader,
16
16
  handleNextCacheHeader,
17
17
  setCacheControlHeaders,
18
18
  setCacheTagsHeaders,
19
19
  setVaryHeaders
20
- } from "../../esm-chunks/chunk-SMSOJ2OS.js";
20
+ } from "../../esm-chunks/chunk-YG3G7B2X.js";
21
21
  import {
22
22
  nextResponseProxy
23
- } from "../../esm-chunks/chunk-B6QMRLBH.js";
23
+ } from "../../esm-chunks/chunk-NE4HYI2D.js";
24
24
  import {
25
25
  __commonJS,
26
26
  __toESM
27
- } from "../../esm-chunks/chunk-RSKIKBZH.js";
27
+ } from "../../esm-chunks/chunk-WELZ7LFO.js";
28
28
 
29
29
  // node_modules/node-inspect-extracted/dist/inspect.js
30
30
  var require_inspect = __commonJS({
@@ -1966,6 +1966,7 @@ var ComputeJsIncomingMessage = class extends Readable {
1966
1966
  this._consuming = true;
1967
1967
  }
1968
1968
  if (this._stream == null) {
1969
+ this.complete = true;
1969
1970
  this.push(null);
1970
1971
  return;
1971
1972
  }
@@ -1973,6 +1974,7 @@ var ComputeJsIncomingMessage = class extends Readable {
1973
1974
  try {
1974
1975
  const data = await reader.read();
1975
1976
  if (data.done) {
1977
+ this.complete = true;
1976
1978
  this.push(null);
1977
1979
  } else {
1978
1980
  this.push(data.value);
@@ -2425,13 +2427,13 @@ var ComputeJsOutgoingMessage = class extends Writable {
2425
2427
  }
2426
2428
  const { statusCode: statusCodeText, statusMessage } = (_b2 = statusLineResult.groups) !== null && _b2 !== void 0 ? _b2 : {};
2427
2429
  const statusCode = parseInt(statusCodeText, 10);
2428
- const headers = {};
2430
+ const headers = [];
2429
2431
  for (const headerLine of headerLines) {
2430
2432
  if (headerLine !== "") {
2431
2433
  const pos = headerLine.indexOf(": ");
2432
2434
  const k = headerLine.slice(0, pos);
2433
2435
  const v = headerLine.slice(pos + 2);
2434
- headers[k] = v;
2436
+ headers.push([k, v]);
2435
2437
  }
2436
2438
  }
2437
2439
  this._headerSent = true;
@@ -3186,8 +3188,8 @@ var ComputeJsServerResponse = class _ComputeJsServerResponse extends ComputeJsOu
3186
3188
  }
3187
3189
  _toComputeResponse(status, statusText, sentHeaders, initialDataChunks, finished) {
3188
3190
  const headers = new Headers();
3189
- for (const [key, value] of Object.entries(sentHeaders)) {
3190
- headers.append(key, value);
3191
+ for (const [header, value] of sentHeaders) {
3192
+ headers.append(header, value);
3191
3193
  }
3192
3194
  const _this = this;
3193
3195
  const body = this._hasBody ? new ReadableStream({
@@ -3278,7 +3280,7 @@ var server_default = async (request) => {
3278
3280
  try {
3279
3281
  await nextHandler(req, resProxy);
3280
3282
  } catch (error) {
3281
- logger.withError(error).error("next handler error");
3283
+ import_internal.systemLogger.withError(error).error("next handler error");
3282
3284
  console.error(error);
3283
3285
  resProxy.statusCode = 500;
3284
3286
  resProxy.end("Internal Server Error");
@@ -1,6 +1,6 @@
1
1
 
2
- const require = await (async () => {
3
- const { createRequire } = await import("node:module");
2
+ var require = await (async () => {
3
+ var { createRequire } = await import("node:module");
4
4
  return createRequire(import.meta.url);
5
5
  })();
6
6
 
@@ -10,8 +10,8 @@ import {
10
10
  setCacheControlHeaders,
11
11
  setCacheTagsHeaders,
12
12
  setVaryHeaders
13
- } from "../esm-chunks/chunk-SMSOJ2OS.js";
14
- import "../esm-chunks/chunk-RSKIKBZH.js";
13
+ } from "../esm-chunks/chunk-YG3G7B2X.js";
14
+ import "../esm-chunks/chunk-WELZ7LFO.js";
15
15
  export {
16
16
  adjustDateHeader,
17
17
  handleNextCacheHeader,
@@ -1,13 +1,13 @@
1
1
 
2
- const require = await (async () => {
3
- const { createRequire } = await import("node:module");
2
+ var require = await (async () => {
3
+ var { createRequire } = await import("node:module");
4
4
  return createRequire(import.meta.url);
5
5
  })();
6
6
 
7
7
  import {
8
8
  nextResponseProxy
9
- } from "../esm-chunks/chunk-B6QMRLBH.js";
10
- import "../esm-chunks/chunk-RSKIKBZH.js";
9
+ } from "../esm-chunks/chunk-NE4HYI2D.js";
10
+ import "../esm-chunks/chunk-WELZ7LFO.js";
11
11
  export {
12
12
  nextResponseProxy
13
13
  };
@@ -1,15 +1,16 @@
1
1
 
2
- const require = await (async () => {
3
- const { createRequire } = await import("node:module");
2
+ var require = await (async () => {
3
+ var { createRequire } = await import("node:module");
4
4
  return createRequire(import.meta.url);
5
5
  })();
6
6
 
7
7
  import {
8
- StructuredLogger,
9
- logger
10
- } from "../esm-chunks/chunk-YZXA5QBC.js";
11
- import "../esm-chunks/chunk-RSKIKBZH.js";
8
+ import_internal
9
+ } from "../esm-chunks/chunk-X7XIMV6B.js";
10
+ import "../esm-chunks/chunk-WELZ7LFO.js";
11
+ var export_LogLevel = import_internal.LogLevel;
12
+ var export_logger = import_internal.systemLogger;
12
13
  export {
13
- StructuredLogger,
14
- logger
14
+ export_LogLevel as LogLevel,
15
+ export_logger as logger
15
16
  };
@@ -1,3 +1,8 @@
1
+ export const InternalHeaders = {
2
+ NFDebugLogging: 'x-nf-debug-logging',
3
+ NFRequestID: 'x-nf-request-id',
4
+ }
5
+
1
6
  // Next 13 supports request header mutations and has the side effect of prepending header values with 'x-middleware-request'
2
7
  // as part of invoking NextResponse.next() in the middleware. We need to remove that before sending the response the user
3
8
  // as the code that removes it in Next isn't run based on how we handle the middleware
@@ -0,0 +1,5 @@
1
+ export {
2
+ logger,
3
+ LogLevel,
4
+ StructuredLogger,
5
+ } from '../vendor/v1-7-0--edge-utils.netlify.app/logger/mod.ts'
@@ -1,5 +1,7 @@
1
1
  import type { Context } from '@netlify/edge-functions'
2
2
 
3
+ import { normalizeDataUrl, removeBasePath, normalizeLocalePath } from './util.ts'
4
+
3
5
  interface I18NConfig {
4
6
  defaultLocale: string
5
7
  localeDetection?: false
@@ -29,6 +31,43 @@ export interface RequestData {
29
31
  }
30
32
  url: string
31
33
  body?: ReadableStream<Uint8Array>
34
+ detectedLocale?: string
35
+ }
36
+
37
+ const normalizeRequestURL = (
38
+ originalURL: string,
39
+ nextConfig?: RequestData['nextConfig'],
40
+ ): { url: string; detectedLocale?: string } => {
41
+ const url = new URL(originalURL)
42
+
43
+ url.pathname = removeBasePath(url.pathname, nextConfig?.basePath)
44
+
45
+ let detectedLocale: string | undefined
46
+
47
+ if (nextConfig?.i18n) {
48
+ const { pathname, detectedLocale: detected } = normalizeLocalePath(
49
+ url.pathname,
50
+ nextConfig?.i18n?.locales,
51
+ )
52
+ url.pathname = pathname
53
+ detectedLocale = detected
54
+ }
55
+
56
+ // We want to run middleware for data requests and expose the URL of the
57
+ // corresponding pages, so we have to normalize the URLs before running
58
+ // the handler.
59
+ url.pathname = normalizeDataUrl(url.pathname)
60
+
61
+ // Normalizing the trailing slash based on the `trailingSlash` configuration
62
+ // property from the Next.js config.
63
+ if (nextConfig?.trailingSlash && url.pathname !== '/' && !url.pathname.endsWith('/')) {
64
+ url.pathname = `${url.pathname}/`
65
+ }
66
+
67
+ return {
68
+ url: url.toString(),
69
+ detectedLocale,
70
+ }
32
71
  }
33
72
 
34
73
  export const buildNextRequest = (
@@ -47,13 +86,16 @@ export const buildNextRequest = (
47
86
  timezone,
48
87
  }
49
88
 
89
+ const { detectedLocale, url: normalizedUrl } = normalizeRequestURL(url, nextConfig)
90
+
50
91
  return {
51
92
  headers: Object.fromEntries(headers.entries()),
52
93
  geo,
53
- url,
94
+ url: normalizedUrl,
54
95
  method,
55
96
  ip: context.ip,
56
97
  body: body ?? undefined,
57
98
  nextConfig,
99
+ detectedLocale,
58
100
  }
59
101
  }
@@ -2,23 +2,37 @@ import type { Context } from '@netlify/edge-functions'
2
2
  import { HTMLRewriter } from '../vendor/deno.land/x/html_rewriter@v0.1.0-pre.17/index.ts'
3
3
 
4
4
  import { updateModifiedHeaders } from './headers.ts'
5
- import { normalizeDataUrl, relativizeURL } from './util.ts'
5
+ import type { StructuredLogger } from './logging.ts'
6
+ import { normalizeDataUrl, normalizeLocalePath, relativizeURL, rewriteDataPath } from './util.ts'
6
7
  import { addMiddlewareHeaders, isMiddlewareRequest, isMiddlewareResponse } from './middleware.ts'
8
+ import { RequestData } from './next-request.ts'
7
9
 
8
10
  export interface FetchEventResult {
9
11
  response: Response
10
12
  waitUntil: Promise<any>
11
13
  }
12
14
 
15
+ interface BuildResponseOptions {
16
+ context: Context
17
+ logger: StructuredLogger
18
+ request: Request
19
+ result: FetchEventResult
20
+ nextConfig?: RequestData['nextConfig']
21
+ requestLocale?: string
22
+ }
23
+
13
24
  export const buildResponse = async ({
14
- result,
15
- request,
16
25
  context,
17
- }: {
18
- result: FetchEventResult
19
- request: Request
20
- context: Context
21
- }) => {
26
+ logger,
27
+ request,
28
+ result,
29
+ nextConfig,
30
+ requestLocale,
31
+ }: BuildResponseOptions): Promise<Response | void> => {
32
+ logger
33
+ .withFields({ is_nextresponse_next: result.response.headers.has('x-middleware-next') })
34
+ .debug('Building Next.js response')
35
+
22
36
  updateModifiedHeaders(request.headers, result.response.headers)
23
37
 
24
38
  // They've returned the MiddlewareRequest directly, so we'll call `next()` for them.
@@ -99,32 +113,79 @@ export const buildResponse = async ({
99
113
  const rewrite = res.headers.get('x-middleware-rewrite')
100
114
 
101
115
  // Data requests (i.e. requests for /_next/data ) need special handling
102
- const isDataReq = request.headers.get('x-nextjs-data')
116
+ const isDataReq = request.headers.has('x-nextjs-data')
103
117
 
104
118
  if (rewrite) {
119
+ logger.withFields({ rewrite_url: rewrite }).debug('Found middleware rewrite')
120
+
105
121
  const rewriteUrl = new URL(rewrite, request.url)
106
122
  const baseUrl = new URL(request.url)
123
+ if (rewriteUrl.toString() === baseUrl.toString()) {
124
+ logger.withFields({ rewrite_url: rewrite }).debug('Rewrite url is same as original url')
125
+ return
126
+ }
127
+
107
128
  const relativeUrl = relativizeURL(rewrite, request.url)
129
+ const originalPath = new URL(request.url, `http://n`).pathname
108
130
 
109
- // Data requests might be rewritten to an external URL
110
- // This header tells the client router the redirect target, and if it's external then it will do a full navigation
111
131
  if (isDataReq) {
132
+ // Data requests might be rewritten to an external URL
133
+ // This header tells the client router the redirect target, and if it's external then it will do a full navigation
134
+
112
135
  res.headers.set('x-nextjs-rewrite', relativeUrl)
113
136
  }
137
+
114
138
  if (rewriteUrl.origin !== baseUrl.origin) {
115
- // Netlify Edge Functions don't support proxying to external domains, but Next middleware does
116
- const proxied = fetch(new Request(rewriteUrl.toString(), request))
117
- return addMiddlewareHeaders(proxied, res)
139
+ logger.withFields({ rewrite_url: rewrite }).debug('Rewriting to external url')
140
+ let proxyRequest: Request
141
+
142
+ // Remove Netlify internal headers
143
+ const headers = new Headers(
144
+ [...request.headers.entries()].filter(([key]) => !key.startsWith('x-nf-')),
145
+ )
146
+ if (request.body && !request.bodyUsed) {
147
+ // This is not ideal, but streaming to an external URL doesn't work
148
+ const body = await request.arrayBuffer()
149
+ proxyRequest = new Request(rewriteUrl, {
150
+ headers,
151
+ method: request.method,
152
+ body,
153
+ })
154
+ } else {
155
+ proxyRequest = new Request(rewriteUrl, {
156
+ headers,
157
+ method: request.method,
158
+ })
159
+ }
160
+ return addMiddlewareHeaders(fetch(proxyRequest), res)
161
+ } else if (isDataReq) {
162
+ rewriteUrl.pathname = rewriteDataPath({
163
+ dataUrl: originalPath,
164
+ newRoute: rewriteUrl.pathname,
165
+ basePath: nextConfig?.basePath,
166
+ })
118
167
  }
119
168
  res.headers.set('x-middleware-rewrite', relativeUrl)
120
-
121
- request.headers.set('x-original-path', new URL(request.url, `http://n`).pathname)
122
169
  request.headers.set('x-middleware-rewrite', rewrite)
123
-
124
- return addMiddlewareHeaders(context.rewrite(rewrite), res)
170
+ return addMiddlewareHeaders(fetch(new Request(rewriteUrl, request)), res)
125
171
  }
126
172
 
127
- const redirect = res.headers.get('Location')
173
+ let redirect = res.headers.get('location')
174
+
175
+ // If we are redirecting a request that had a locale in the URL, we need to add it back in
176
+ if (redirect && requestLocale) {
177
+ const redirectUrl = new URL(redirect, request.url)
178
+
179
+ const normalizedRedirect = normalizeLocalePath(redirectUrl.pathname, nextConfig?.i18n?.locales)
180
+
181
+ const locale = normalizedRedirect.detectedLocale ?? requestLocale
182
+ // Pages router API routes don't have a locale in the URL
183
+ if (locale && !redirectUrl.pathname.startsWith(`/api/`)) {
184
+ redirectUrl.pathname = `/${locale}${normalizedRedirect.pathname}`
185
+ redirect = redirectUrl.toString()
186
+ res.headers.set('location', redirect)
187
+ }
188
+ }
128
189
 
129
190
  // Data requests shouldn't automatically redirect in the browser (they might be HTML pages): they're handled by the router
130
191
  if (redirect && isDataReq) {
@@ -142,5 +203,6 @@ export const buildResponse = async ({
142
203
  res.headers.delete('x-middleware-next')
143
204
  return addMiddlewareHeaders(context.next(), res)
144
205
  }
206
+
145
207
  return res
146
208
  }