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

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.
@@ -7,7 +7,7 @@
7
7
  import {
8
8
  copyFetchContent,
9
9
  copyPrerenderedContent
10
- } from "../../esm-chunks/chunk-G3GM7JNF.js";
10
+ } from "../../esm-chunks/chunk-S5JXJCXP.js";
11
11
  import "../../esm-chunks/chunk-AVWFCGVE.js";
12
12
  import "../../esm-chunks/chunk-RSKIKBZH.js";
13
13
  export {
@@ -8,7 +8,7 @@ import {
8
8
  copyNextDependencies,
9
9
  copyNextServerCode,
10
10
  writeTagsManifest
11
- } from "../../esm-chunks/chunk-G2VRYWGL.js";
11
+ } from "../../esm-chunks/chunk-5SZ5JD6J.js";
12
12
  import "../../esm-chunks/chunk-AVWFCGVE.js";
13
13
  import "../../esm-chunks/chunk-RSKIKBZH.js";
14
14
  export {
@@ -9,7 +9,7 @@ import {
9
9
  copyStaticContent,
10
10
  publishStaticDir,
11
11
  unpublishStaticDir
12
- } from "../../esm-chunks/chunk-62KDS27E.js";
12
+ } from "../../esm-chunks/chunk-Z7ZMLVTM.js";
13
13
  import "../../esm-chunks/chunk-AVWFCGVE.js";
14
14
  import "../../esm-chunks/chunk-RSKIKBZH.js";
15
15
  export {
@@ -6,8 +6,8 @@
6
6
 
7
7
  import {
8
8
  createEdgeHandlers
9
- } from "../../esm-chunks/chunk-XD5TSLZO.js";
10
- import "../../esm-chunks/chunk-HPGTYMVD.js";
9
+ } from "../../esm-chunks/chunk-3PTPU5GO.js";
10
+ import "../../esm-chunks/chunk-TJKO3X6O.js";
11
11
  import "../../esm-chunks/chunk-RSKIKBZH.js";
12
12
  export {
13
13
  createEdgeHandlers
@@ -6,10 +6,10 @@
6
6
 
7
7
  import {
8
8
  createServerHandler
9
- } from "../../esm-chunks/chunk-PEBUKFKH.js";
10
- import "../../esm-chunks/chunk-G2VRYWGL.js";
9
+ } from "../../esm-chunks/chunk-ALO2SSMH.js";
10
+ import "../../esm-chunks/chunk-5SZ5JD6J.js";
11
11
  import "../../esm-chunks/chunk-AVWFCGVE.js";
12
- import "../../esm-chunks/chunk-HPGTYMVD.js";
12
+ import "../../esm-chunks/chunk-TJKO3X6O.js";
13
13
  import "../../esm-chunks/chunk-RSKIKBZH.js";
14
14
  export {
15
15
  createServerHandler
@@ -0,0 +1,13 @@
1
+
2
+ const require = await (async () => {
3
+ const { createRequire } = await import("node:module");
4
+ return createRequire(import.meta.url);
5
+ })();
6
+
7
+ import {
8
+ setImageConfig
9
+ } from "../esm-chunks/chunk-H46DW7YI.js";
10
+ import "../esm-chunks/chunk-RSKIKBZH.js";
11
+ export {
12
+ setImageConfig
13
+ };
@@ -8,7 +8,7 @@ import {
8
8
  EDGE_HANDLER_NAME,
9
9
  PluginContext,
10
10
  SERVER_HANDLER_NAME
11
- } from "../esm-chunks/chunk-HPGTYMVD.js";
11
+ } from "../esm-chunks/chunk-TJKO3X6O.js";
12
12
  import "../esm-chunks/chunk-RSKIKBZH.js";
13
13
  export {
14
14
  EDGE_HANDLER_NAME,
@@ -6,7 +6,7 @@
6
6
 
7
7
  import {
8
8
  EDGE_HANDLER_NAME
9
- } from "./chunk-HPGTYMVD.js";
9
+ } from "./chunk-TJKO3X6O.js";
10
10
 
11
11
  // src/build/functions/edge.ts
12
12
  import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
@@ -15,15 +15,16 @@ var writeEdgeManifest = async (ctx, manifest) => {
15
15
  await mkdir(ctx.edgeFunctionsDir, { recursive: true });
16
16
  await writeFile(join(ctx.edgeFunctionsDir, "manifest.json"), JSON.stringify(manifest, null, 2));
17
17
  };
18
- var writeHandlerFile = async (ctx, { name }) => {
18
+ var writeHandlerFile = async (ctx, { matchers, name }) => {
19
19
  const handlerName = getHandlerName({ name });
20
- await cp(
21
- join(ctx.pluginDir, "edge-runtime"),
22
- join(ctx.edgeFunctionsDir, handlerName, "edge-runtime"),
23
- { recursive: true }
24
- );
20
+ const handlerDirectory = join(ctx.edgeFunctionsDir, handlerName);
21
+ const handlerRuntimeDirectory = join(handlerDirectory, "edge-runtime");
22
+ await cp(join(ctx.pluginDir, "edge-runtime"), handlerRuntimeDirectory, {
23
+ recursive: true
24
+ });
25
+ await writeFile(join(handlerRuntimeDirectory, "matchers.json"), JSON.stringify(matchers));
25
26
  await writeFile(
26
- join(ctx.edgeFunctionsDir, handlerName, `${handlerName}.js`),
27
+ join(handlerDirectory, `${handlerName}.js`),
27
28
  `
28
29
  import {handleMiddleware} from './edge-runtime/middleware.ts';
29
30
  import handler from './server/${name}.js';
@@ -19,19 +19,26 @@ import { dirname, join } from "node:path";
19
19
  var copyNextServerCode = async (ctx) => {
20
20
  const srcDir = join(ctx.publishDir, "standalone/.next");
21
21
  const destDir = join(ctx.serverHandlerDir, ".next");
22
- const paths = await (0, import_fast_glob.default)([`*`, `server/*`, `server/chunks/*`, `server/+(app|pages)/**/*.js`], {
23
- cwd: srcDir,
24
- extglob: true
25
- });
22
+ const paths = await (0, import_fast_glob.default)(
23
+ [`*`, `server/*`, `server/chunks/*`, `server/edge-chunks/*`, `server/+(app|pages)/**/*.js`],
24
+ {
25
+ cwd: srcDir,
26
+ extglob: true
27
+ }
28
+ );
26
29
  await Promise.all(
27
30
  paths.map(async (path) => {
31
+ const srcPath = join(srcDir, path);
28
32
  const destPath = join(destDir, path);
29
33
  if (path === "server/middleware-manifest.json") {
30
- await mkdir(dirname(destPath), { recursive: true });
31
- await writeFile(destPath, getEmptyMiddlewareManifest());
34
+ try {
35
+ await replaceMiddlewareManifest(srcPath, destPath);
36
+ } catch (error) {
37
+ throw new Error("Could not patch middleware manifest file", { cause: error });
38
+ }
32
39
  return;
33
40
  }
34
- await cp(join(srcDir, path), destPath, { recursive: true });
41
+ await cp(srcPath, destPath, { recursive: true });
35
42
  })
36
43
  );
37
44
  };
@@ -73,7 +80,9 @@ var writeTagsManifest = async (ctx) => {
73
80
  const meta = JSON.parse(file);
74
81
  tags = meta.headers["x-next-cache-tags"];
75
82
  } catch {
76
- console.log(`Unable to read cache tags for: ${path}`);
83
+ if (!definition.dataRoute?.endsWith("/default.rsc")) {
84
+ console.log(`Unable to read cache tags for: ${path}`);
85
+ }
77
86
  }
78
87
  }
79
88
  if (definition.dataRoute?.endsWith(".json")) {
@@ -90,14 +99,16 @@ var writeTagsManifest = async (ctx) => {
90
99
  "utf-8"
91
100
  );
92
101
  };
93
- var getEmptyMiddlewareManifest = () => {
94
- const manifest = {
95
- sortedMiddleware: [],
96
- middleware: {},
97
- functions: {},
98
- version: 2
102
+ var replaceMiddlewareManifest = async (sourcePath, destPath) => {
103
+ await mkdir(dirname(destPath), { recursive: true });
104
+ const data = await readFile(sourcePath, "utf8");
105
+ const manifest = JSON.parse(data);
106
+ const newManifest = {
107
+ ...manifest,
108
+ middleware: {}
99
109
  };
100
- return JSON.stringify(manifest);
110
+ const newData = JSON.stringify(newManifest);
111
+ await writeFile(destPath, newData);
101
112
  };
102
113
 
103
114
  export {
@@ -8,13 +8,13 @@ import {
8
8
  copyNextDependencies,
9
9
  copyNextServerCode,
10
10
  writeTagsManifest
11
- } from "./chunk-G2VRYWGL.js";
11
+ } from "./chunk-5SZ5JD6J.js";
12
12
  import {
13
13
  require_out
14
14
  } from "./chunk-AVWFCGVE.js";
15
15
  import {
16
16
  SERVER_HANDLER_NAME
17
- } from "./chunk-HPGTYMVD.js";
17
+ } from "./chunk-TJKO3X6O.js";
18
18
  import {
19
19
  __toESM
20
20
  } from "./chunk-RSKIKBZH.js";
@@ -0,0 +1,28 @@
1
+
2
+ const require = await (async () => {
3
+ const { createRequire } = await import("node:module");
4
+ return createRequire(import.meta.url);
5
+ })();
6
+
7
+
8
+ // src/build/image-cdn.ts
9
+ var setImageConfig = async (ctx) => {
10
+ const {
11
+ images: { path: imageEndpointPath, loader: imageLoader }
12
+ } = await ctx.getBuildConfig();
13
+ if (imageLoader === "default") {
14
+ ctx.netlifyConfig.redirects.push({
15
+ from: imageEndpointPath,
16
+ // w and q are too short to be used as params with id-length rule
17
+ // but we are forced to do so because of the next/image loader decides on their names
18
+ // eslint-disable-next-line id-length
19
+ query: { url: ":url", w: ":width", q: ":quality" },
20
+ to: "/.netlify/images?url=:url&w=:width&q=:quality",
21
+ status: 200
22
+ });
23
+ }
24
+ };
25
+
26
+ export {
27
+ setImageConfig
28
+ };
@@ -45,6 +45,8 @@ var copyPrerenderedContent = async (ctx) => {
45
45
  const key = routeToFilePath(route);
46
46
  let value;
47
47
  switch (true) {
48
+ case (meta.dataRoute?.endsWith("/default.rsc") && !existsSync(join(ctx.publishDir, "server/app", `${key}.html`))):
49
+ return;
48
50
  case meta.dataRoute?.endsWith(".json"):
49
51
  value = await buildPagesCacheValue(join(ctx.publishDir, "server/pages", key));
50
52
  break;
@@ -13,6 +13,8 @@ import { Buffer } from "buffer";
13
13
  import { env } from "process";
14
14
  import { Buffer as Buffer2 } from "buffer";
15
15
  import { Buffer as Buffer3 } from "buffer";
16
+ import stream from "stream";
17
+ import { promisify } from "util";
16
18
  var getEnvironmentContext = () => {
17
19
  const context = globalThis.netlifyBlobsContext || env.NETLIFY_BLOBS_CONTEXT;
18
20
  if (typeof context !== "string" || !context) {
@@ -382,6 +384,9 @@ var Store = class _Store {
382
384
  };
383
385
  }
384
386
  static validateKey(key) {
387
+ if (key === "") {
388
+ throw new Error("Blob key must not be empty.");
389
+ }
385
390
  if (key.startsWith("/") || key.startsWith("%2F")) {
386
391
  throw new Error("Blob key must not start with forward slash (/).");
387
392
  }
@@ -466,6 +471,7 @@ var getDeployStore = (options = {}) => {
466
471
  const client = new Client(clientOptions);
467
472
  return new Store({ client, deployID });
468
473
  };
474
+ var pipeline = promisify(stream.pipeline);
469
475
 
470
476
  // src/run/headers.ts
471
477
  var generateNetlifyVaryValues = ({ headers, languages, cookies }) => {
@@ -532,9 +538,9 @@ var adjustDateHeader = async (headers, request) => {
532
538
  headers.set("x-nextjs-date", headers.get("date") ?? lastModifiedDate.toUTCString());
533
539
  headers.set("date", lastModifiedDate.toUTCString());
534
540
  };
535
- var setCacheControlHeaders = (headers) => {
541
+ var setCacheControlHeaders = (headers, request) => {
536
542
  const cacheControl = headers.get("cache-control");
537
- if (cacheControl !== null && !headers.has("cdn-cache-control") && !headers.has("netlify-cdn-cache-control")) {
543
+ if (cacheControl !== null && ["GET", "HEAD"].includes(request.method) && !headers.has("cdn-cache-control") && !headers.has("netlify-cdn-cache-control")) {
538
544
  const privateCacheControl = omitHeaderValues(cacheControl, [
539
545
  "s-maxage",
540
546
  "stale-while-revalidate"
@@ -552,10 +558,25 @@ var setCacheTagsHeaders = (headers, request, manifest) => {
552
558
  const tags = manifest[path];
553
559
  headers.set("cache-tag", tags);
554
560
  };
561
+ var NEXT_CACHE_TO_CACHE_STATUS = {
562
+ HIT: `hit`,
563
+ MISS: `miss,`
564
+ };
565
+ var handleNextCacheHeader = (headers) => {
566
+ const nextCache = headers.get("x-nextjs-cache");
567
+ if (typeof nextCache === "string") {
568
+ if (nextCache in NEXT_CACHE_TO_CACHE_STATUS) {
569
+ const cacheStatus = NEXT_CACHE_TO_CACHE_STATUS[nextCache];
570
+ headers.set("cache-status", `"Next.js"; ${cacheStatus}`);
571
+ }
572
+ headers.delete("x-nextjs-cache");
573
+ }
574
+ };
555
575
 
556
576
  export {
557
577
  setVaryHeaders,
558
578
  adjustDateHeader,
559
579
  setCacheControlHeaders,
560
- setCacheTagsHeaders
580
+ setCacheTagsHeaders,
581
+ handleNextCacheHeader
561
582
  };
@@ -17,6 +17,7 @@ var SERVER_HANDLER_NAME = "___netlify-server-handler";
17
17
  var EDGE_HANDLER_NAME = "___netlify-edge-handler";
18
18
  var PluginContext = class {
19
19
  utils;
20
+ netlifyConfig;
20
21
  pluginName;
21
22
  pluginVersion;
22
23
  constants;
@@ -70,6 +71,7 @@ var PluginContext = class {
70
71
  this.pluginVersion = this.packageJSON.version;
71
72
  this.constants = options.constants;
72
73
  this.utils = options.utils;
74
+ this.netlifyConfig = options.netlifyConfig;
73
75
  }
74
76
  /** Resolves a path correctly with mono repository awareness */
75
77
  resolve(...args) {
@@ -87,6 +89,16 @@ var PluginContext = class {
87
89
  await readFile(join(this.publishDir, "server/middleware-manifest.json"), "utf-8")
88
90
  );
89
91
  }
92
+ /** Get Next Config from build output **/
93
+ async getBuildConfig() {
94
+ return JSON.parse(await readFile(join(this.publishDir, "required-server-files.json"), "utf-8")).config;
95
+ }
96
+ /**
97
+ * Get Next.js routes manifest from the build output
98
+ */
99
+ async getRoutesManifest() {
100
+ return JSON.parse(await readFile(join(this.publishDir, "routes-manifest.json"), "utf-8"));
101
+ }
90
102
  /**
91
103
  * Write a cache entry to the blob upload directory using
92
104
  * base64 keys to avoid collisions with directories
@@ -0,0 +1,60 @@
1
+
2
+ const require = await (async () => {
3
+ const { createRequire } = await import("node:module");
4
+ return createRequire(import.meta.url);
5
+ })();
6
+
7
+
8
+ // src/run/systemlog.ts
9
+ var systemLogTag = "__nfSystemLog";
10
+ var serializeError = (error) => {
11
+ const cause = error?.cause instanceof Error ? serializeError(error.cause) : error.cause;
12
+ return {
13
+ error: error.message,
14
+ error_cause: cause,
15
+ error_stack: error.stack
16
+ };
17
+ };
18
+ var StructuredLogger = class _StructuredLogger {
19
+ fields;
20
+ message;
21
+ constructor(message, fields) {
22
+ this.fields = fields ?? {};
23
+ this.message = message ?? "";
24
+ }
25
+ // TODO: add sampling
26
+ doLog(logger2, message) {
27
+ logger2(systemLogTag, JSON.stringify({ msg: message, fields: this.fields }));
28
+ }
29
+ log(message) {
30
+ this.doLog(console.log, message);
31
+ }
32
+ info(message) {
33
+ this.doLog(console.info, message);
34
+ }
35
+ debug(message) {
36
+ this.doLog(console.debug, message);
37
+ }
38
+ warn(message) {
39
+ this.doLog(console.warn, message);
40
+ }
41
+ error(message) {
42
+ this.doLog(console.error, message);
43
+ }
44
+ withError(error) {
45
+ const fields = error instanceof Error ? serializeError(error) : { error };
46
+ return this.withFields(fields);
47
+ }
48
+ withFields(fields) {
49
+ return new _StructuredLogger(this.message, {
50
+ ...this.fields,
51
+ ...fields
52
+ });
53
+ }
54
+ };
55
+ var logger = new StructuredLogger();
56
+
57
+ export {
58
+ StructuredLogger,
59
+ logger
60
+ };
@@ -38,11 +38,12 @@ var copyStaticContent = async (ctx) => {
38
38
  var copyStaticAssets = async (ctx) => {
39
39
  try {
40
40
  await rm(ctx.staticDir, { recursive: true, force: true });
41
+ const { basePath } = await ctx.getRoutesManifest();
41
42
  if (existsSync(ctx.resolve("public"))) {
42
- await cp(ctx.resolve("public"), ctx.staticDir, { recursive: true });
43
+ await cp(ctx.resolve("public"), join(ctx.staticDir, basePath), { recursive: true });
43
44
  }
44
45
  if (existsSync(join(ctx.publishDir, "static"))) {
45
- await cp(join(ctx.publishDir, "static"), join(ctx.staticDir, "_next/static"), {
46
+ await cp(join(ctx.publishDir, "static"), join(ctx.staticDir, basePath, "_next/static"), {
46
47
  recursive: true
47
48
  });
48
49
  }
package/dist/index.js CHANGED
@@ -4,31 +4,34 @@
4
4
  return createRequire(import.meta.url);
5
5
  })();
6
6
 
7
- import {
8
- createServerHandler
9
- } from "./esm-chunks/chunk-PEBUKFKH.js";
10
7
  import {
11
8
  copyFetchContent,
12
9
  copyPrerenderedContent
13
- } from "./esm-chunks/chunk-G3GM7JNF.js";
14
- import "./esm-chunks/chunk-G2VRYWGL.js";
10
+ } from "./esm-chunks/chunk-S5JXJCXP.js";
15
11
  import {
16
12
  copyStaticAssets,
17
13
  copyStaticContent,
18
14
  publishStaticDir,
19
15
  unpublishStaticDir
20
- } from "./esm-chunks/chunk-62KDS27E.js";
16
+ } from "./esm-chunks/chunk-Z7ZMLVTM.js";
17
+ import {
18
+ createEdgeHandlers
19
+ } from "./esm-chunks/chunk-3PTPU5GO.js";
20
+ import {
21
+ createServerHandler
22
+ } from "./esm-chunks/chunk-ALO2SSMH.js";
23
+ import "./esm-chunks/chunk-5SZ5JD6J.js";
21
24
  import "./esm-chunks/chunk-AVWFCGVE.js";
22
25
  import {
23
26
  restoreBuildCache,
24
27
  saveBuildCache
25
28
  } from "./esm-chunks/chunk-GGHAQM5D.js";
26
29
  import {
27
- createEdgeHandlers
28
- } from "./esm-chunks/chunk-XD5TSLZO.js";
30
+ setImageConfig
31
+ } from "./esm-chunks/chunk-H46DW7YI.js";
29
32
  import {
30
33
  PluginContext
31
- } from "./esm-chunks/chunk-HPGTYMVD.js";
34
+ } from "./esm-chunks/chunk-TJKO3X6O.js";
32
35
  import "./esm-chunks/chunk-RSKIKBZH.js";
33
36
 
34
37
  // src/index.ts
@@ -42,6 +45,7 @@ var onBuild = async (options) => {
42
45
  if (!existsSync(ctx.publishDir)) {
43
46
  ctx.failBuild("Publish directory not found, please check your netlify.toml");
44
47
  }
48
+ await setImageConfig(ctx);
45
49
  await saveBuildCache(ctx);
46
50
  await Promise.all([
47
51
  copyStaticAssets(ctx),
@@ -174,8 +174,8 @@ var require_stream = __commonJS({
174
174
  exports2.stream = void 0;
175
175
  var node_stream_1 = require("node:stream");
176
176
  var node_util_1 = require("node:util");
177
- var pipeline = (0, node_util_1.promisify)(node_stream_1.pipeline);
178
- var stream = (handler) => awslambda.streamifyResponse(async (event, responseStream, context) => {
177
+ var pipeline2 = (0, node_util_1.promisify)(node_stream_1.pipeline);
178
+ var stream2 = (handler) => awslambda.streamifyResponse(async (event, responseStream, context) => {
179
179
  const { body, ...httpResponseMetadata } = await handler(event, context);
180
180
  const responseBody = awslambda.HttpResponseStream.from(responseStream, httpResponseMetadata);
181
181
  if (typeof body === "undefined") {
@@ -184,10 +184,10 @@ var require_stream = __commonJS({
184
184
  responseBody.write(body);
185
185
  responseBody.end();
186
186
  } else {
187
- await pipeline(body, responseBody);
187
+ await pipeline2(body, responseBody);
188
188
  }
189
189
  });
190
- exports2.stream = stream;
190
+ exports2.stream = stream2;
191
191
  }
192
192
  });
193
193
 
@@ -249,7 +249,6 @@ var require_main = __commonJS({
249
249
  var cache_exports = {};
250
250
  __export(cache_exports, {
251
251
  NetlifyCacheHandler: () => NetlifyCacheHandler,
252
- blobStore: () => blobStore,
253
252
  default: () => cache_default
254
253
  });
255
254
  module.exports = __toCommonJS(cache_exports);
@@ -262,6 +261,8 @@ var import_buffer = require("buffer");
262
261
  var import_process = require("process");
263
262
  var import_buffer2 = require("buffer");
264
263
  var import_buffer3 = require("buffer");
264
+ var import_stream = __toESM(require("stream"), 1);
265
+ var import_util = require("util");
265
266
  var getEnvironmentContext = () => {
266
267
  const context = globalThis.netlifyBlobsContext || import_process.env.NETLIFY_BLOBS_CONTEXT;
267
268
  if (typeof context !== "string" || !context) {
@@ -631,6 +632,9 @@ var Store = class _Store {
631
632
  };
632
633
  }
633
634
  static validateKey(key) {
635
+ if (key === "") {
636
+ throw new Error("Blob key must not be empty.");
637
+ }
634
638
  if (key.startsWith("/") || key.startsWith("%2F")) {
635
639
  throw new Error("Blob key must not start with forward slash (/).");
636
640
  }
@@ -715,11 +719,11 @@ var getDeployStore = (options = {}) => {
715
719
  const client = new Client(clientOptions);
716
720
  return new Store({ client, deployID });
717
721
  };
722
+ var pipeline = (0, import_util.promisify)(import_stream.default.pipeline);
718
723
 
719
724
  // src/run/handlers/cache.cts
720
725
  var import_functions = __toESM(require_main());
721
726
  var import_constants = require("next/dist/lib/constants.js");
722
- var blobStore = getDeployStore();
723
727
  var prerenderManifest = JSON.parse(
724
728
  (0, import_node_fs.readFileSync)((0, import_posix.join)(process.cwd(), ".next/prerender-manifest.json"), "utf-8")
725
729
  );
@@ -729,17 +733,20 @@ function toRoute(cacheKey) {
729
733
  function encodeBlobKey(key) {
730
734
  return import_node_buffer.Buffer.from(key.replace(/^\//, "")).toString("base64");
731
735
  }
736
+ var fetchBeforeNextPatchedIt = globalThis.fetch;
732
737
  var NetlifyCacheHandler = class {
733
738
  options;
734
739
  revalidatedTags;
740
+ blobStore;
735
741
  constructor(options) {
736
742
  this.options = options;
737
743
  this.revalidatedTags = options.revalidatedTags;
744
+ this.blobStore = getDeployStore({ fetch: fetchBeforeNextPatchedIt });
738
745
  }
739
746
  async get(...args) {
740
747
  const [key, ctx = {}] = args;
741
748
  console.debug(`[NetlifyCacheHandler.get]: ${key}`);
742
- const blob = await blobStore.get(encodeBlobKey(key), {
749
+ const blob = await this.blobStore.get(encodeBlobKey(key), {
743
750
  type: "json"
744
751
  });
745
752
  if (!blob) {
@@ -783,7 +790,7 @@ var NetlifyCacheHandler = class {
783
790
  async set(...args) {
784
791
  const [key, data] = args;
785
792
  console.debug(`[NetlifyCacheHandler.set]: ${key}`);
786
- await blobStore.setJSON(encodeBlobKey(key), {
793
+ await this.blobStore.setJSON(encodeBlobKey(key), {
787
794
  lastModified: Date.now(),
788
795
  value: data
789
796
  });
@@ -795,7 +802,7 @@ var NetlifyCacheHandler = class {
795
802
  revalidatedAt: Date.now()
796
803
  };
797
804
  try {
798
- await blobStore.setJSON(encodeBlobKey(tag), data);
805
+ await this.blobStore.setJSON(encodeBlobKey(tag), data);
799
806
  } catch (error) {
800
807
  console.warn(`Failed to update tag manifest for ${tag}`, error);
801
808
  }
@@ -811,7 +818,7 @@ var NetlifyCacheHandler = class {
811
818
  const cacheTags = [...tags, ...softTags];
812
819
  const allManifests = await Promise.all(
813
820
  cacheTags.map(async (tag) => {
814
- const res = await blobStore.get(encodeBlobKey(tag), { type: "json" }).then((value) => ({ [tag]: value })).catch(console.error);
821
+ const res = await this.blobStore.get(encodeBlobKey(tag), { type: "json" }).then((value) => ({ [tag]: value })).catch(console.error);
815
822
  return res || { [tag]: null };
816
823
  })
817
824
  );
@@ -844,6 +851,5 @@ var NetlifyCacheHandler = class {
844
851
  var cache_default = NetlifyCacheHandler;
845
852
  // Annotate the CommonJS export names for ESM import in node:
846
853
  0 && (module.exports = {
847
- NetlifyCacheHandler,
848
- blobStore
854
+ NetlifyCacheHandler
849
855
  });
@@ -4,16 +4,20 @@
4
4
  return createRequire(import.meta.url);
5
5
  })();
6
6
 
7
+ import {
8
+ logger
9
+ } from "../../esm-chunks/chunk-YZXA5QBC.js";
7
10
  import {
8
11
  getTagsManifest
9
12
  } from "../../esm-chunks/chunk-R4NHZWGU.js";
10
13
  import "../../esm-chunks/chunk-4AJYXTWN.js";
11
14
  import {
12
15
  adjustDateHeader,
16
+ handleNextCacheHeader,
13
17
  setCacheControlHeaders,
14
18
  setCacheTagsHeaders,
15
19
  setVaryHeaders
16
- } from "../../esm-chunks/chunk-ZBX3SNQG.js";
20
+ } from "../../esm-chunks/chunk-SMSOJ2OS.js";
17
21
  import {
18
22
  nextResponseProxy
19
23
  } from "../../esm-chunks/chunk-B6QMRLBH.js";
@@ -3260,9 +3264,10 @@ var server_default = async (request) => {
3260
3264
  setRunConfig(nextConfig);
3261
3265
  tagsManifest = await getTagsManifest();
3262
3266
  const { getMockedRequestHandlers } = await import("../next.cjs");
3267
+ const url = new URL(request.url);
3263
3268
  [nextHandler] = await getMockedRequestHandlers({
3264
- port: 3e3,
3265
- hostname: "localhost",
3269
+ port: Number(url.port) || 443,
3270
+ hostname: url.hostname,
3266
3271
  dir: process.cwd(),
3267
3272
  isDev: false
3268
3273
  });
@@ -3273,15 +3278,21 @@ var server_default = async (request) => {
3273
3278
  try {
3274
3279
  await nextHandler(req, resProxy);
3275
3280
  } catch (error) {
3281
+ logger.withError(error).error("next handler error");
3276
3282
  console.error(error);
3277
3283
  resProxy.statusCode = 500;
3278
3284
  resProxy.end("Internal Server Error");
3279
3285
  }
3280
3286
  const response = await toComputeResponse(resProxy);
3281
3287
  await adjustDateHeader(response.headers, request);
3282
- setCacheControlHeaders(response.headers);
3288
+ setCacheControlHeaders(response.headers, request);
3283
3289
  setCacheTagsHeaders(response.headers, request, tagsManifest);
3284
3290
  setVaryHeaders(response.headers, request, nextConfig);
3291
+ handleNextCacheHeader(response.headers);
3292
+ if (response.status > 300 && response.status < 400 || response.status >= 500) {
3293
+ const body = await response.text();
3294
+ return new Response(body || null, response);
3295
+ }
3285
3296
  return response;
3286
3297
  };
3287
3298
  export {
@@ -6,13 +6,15 @@
6
6
 
7
7
  import {
8
8
  adjustDateHeader,
9
+ handleNextCacheHeader,
9
10
  setCacheControlHeaders,
10
11
  setCacheTagsHeaders,
11
12
  setVaryHeaders
12
- } from "../esm-chunks/chunk-ZBX3SNQG.js";
13
+ } from "../esm-chunks/chunk-SMSOJ2OS.js";
13
14
  import "../esm-chunks/chunk-RSKIKBZH.js";
14
15
  export {
15
16
  adjustDateHeader,
17
+ handleNextCacheHeader,
16
18
  setCacheControlHeaders,
17
19
  setCacheTagsHeaders,
18
20
  setVaryHeaders
package/dist/run/next.cjs CHANGED
@@ -564,6 +564,8 @@ var import_buffer = require("buffer");
564
564
  var import_process = require("process");
565
565
  var import_buffer2 = require("buffer");
566
566
  var import_buffer3 = require("buffer");
567
+ var import_stream = __toESM(require("stream"), 1);
568
+ var import_util = require("util");
567
569
  var getEnvironmentContext = () => {
568
570
  const context = globalThis.netlifyBlobsContext || import_process.env.NETLIFY_BLOBS_CONTEXT;
569
571
  if (typeof context !== "string" || !context) {
@@ -933,6 +935,9 @@ var Store = class _Store {
933
935
  };
934
936
  }
935
937
  static validateKey(key) {
938
+ if (key === "") {
939
+ throw new Error("Blob key must not be empty.");
940
+ }
936
941
  if (key.startsWith("/") || key.startsWith("%2F")) {
937
942
  throw new Error("Blob key must not start with forward slash (/).");
938
943
  }
@@ -1017,6 +1022,7 @@ var getDeployStore = (options = {}) => {
1017
1022
  const client = new Client(clientOptions);
1018
1023
  return new Store({ client, deployID });
1019
1024
  };
1025
+ var pipeline = (0, import_util.promisify)(import_stream.default.pipeline);
1020
1026
 
1021
1027
  // src/run/next.cts
1022
1028
  var import_fs_monkey = __toESM(require_lib());
@@ -0,0 +1,15 @@
1
+
2
+ const require = await (async () => {
3
+ const { createRequire } = await import("node:module");
4
+ return createRequire(import.meta.url);
5
+ })();
6
+
7
+ import {
8
+ StructuredLogger,
9
+ logger
10
+ } from "../esm-chunks/chunk-YZXA5QBC.js";
11
+ import "../esm-chunks/chunk-RSKIKBZH.js";
12
+ export {
13
+ StructuredLogger,
14
+ logger
15
+ };
@@ -139,6 +139,7 @@ export const buildResponse = async ({
139
139
  }
140
140
 
141
141
  if (res.headers.get('x-middleware-next') === '1') {
142
+ res.headers.delete('x-middleware-next')
142
143
  return addMiddlewareHeaders(context.next(), res)
143
144
  }
144
145
  return res
@@ -0,0 +1,451 @@
1
+ /**
2
+ * Various router utils ported to Deno from Next.js source
3
+ * Licence: https://github.com/vercel/next.js/blob/7280c3ced186bb9a7ae3d7012613ef93f20b0fa9/license.md
4
+ *
5
+ * Some types have been re-implemented to be more compatible with Deno or avoid chains of dependent files
6
+ */
7
+
8
+ import type { Key } from '../vendor/deno.land/x/path_to_regexp@v6.2.1/index.ts'
9
+
10
+ import { compile, pathToRegexp } from '../vendor/deno.land/x/path_to_regexp@v6.2.1/index.ts'
11
+ import { getCookies } from '../vendor/deno.land/std@0.175.0/http/cookie.ts'
12
+
13
+ /*
14
+ ┌─────────────────────────────────────────────────────────────────────────┐
15
+ │ Inlined/re-implemented types │
16
+ └─────────────────────────────────────────────────────────────────────────┘
17
+ */
18
+ export interface ParsedUrlQuery {
19
+ [key: string]: string | string[]
20
+ }
21
+
22
+ export interface Params {
23
+ [param: string]: any
24
+ }
25
+
26
+ export type RouteHas =
27
+ | {
28
+ type: 'header' | 'query' | 'cookie'
29
+ key: string
30
+ value?: string
31
+ }
32
+ | {
33
+ type: 'host'
34
+ key?: undefined
35
+ value: string
36
+ }
37
+
38
+ export type Rewrite = {
39
+ source: string
40
+ destination: string
41
+ basePath?: false
42
+ locale?: false
43
+ has?: RouteHas[]
44
+ missing?: RouteHas[]
45
+ regex: string
46
+ }
47
+
48
+ export type Header = {
49
+ source: string
50
+ basePath?: false
51
+ locale?: false
52
+ headers: Array<{ key: string; value: string }>
53
+ has?: RouteHas[]
54
+ missing?: RouteHas[]
55
+ regex: string
56
+ }
57
+
58
+ export type Redirect = {
59
+ source: string
60
+ destination: string
61
+ basePath?: false
62
+ locale?: false
63
+ has?: RouteHas[]
64
+ missing?: RouteHas[]
65
+ statusCode?: number
66
+ permanent?: boolean
67
+ regex: string
68
+ }
69
+
70
+ export type DynamicRoute = {
71
+ page: string
72
+ regex: string
73
+ namedRegex?: string
74
+ routeKeys?: { [key: string]: string }
75
+ }
76
+
77
+ export type RoutesManifest = {
78
+ basePath: string
79
+ redirects: Redirect[]
80
+ headers: Header[]
81
+ rewrites: {
82
+ beforeFiles: Rewrite[]
83
+ afterFiles: Rewrite[]
84
+ fallback: Rewrite[]
85
+ }
86
+ dynamicRoutes: DynamicRoute[]
87
+ }
88
+
89
+ /*
90
+ ┌─────────────────────────────────────────────────────────────────────────┐
91
+ │ packages/next/src/shared/lib/escape-regexp.ts │
92
+ └─────────────────────────────────────────────────────────────────────────┘
93
+ */
94
+ // regexp is based on https://github.com/sindresorhus/escape-string-regexp
95
+ const reHasRegExp = /[|\\{}()[\]^$+*?.-]/
96
+ const reReplaceRegExp = /[|\\{}()[\]^$+*?.-]/g
97
+
98
+ export function escapeStringRegexp(str: string) {
99
+ // see also: https://github.com/lodash/lodash/blob/2da024c3b4f9947a48517639de7560457cd4ec6c/escapeRegExp.js#L23
100
+ if (reHasRegExp.test(str)) {
101
+ return str.replace(reReplaceRegExp, '\\$&')
102
+ }
103
+
104
+ return str
105
+ }
106
+
107
+ /*
108
+ ┌─────────────────────────────────────────────────────────────────────────┐
109
+ │ packages/next/src/shared/lib/router/utils/querystring.ts │
110
+ └─────────────────────────────────────────────────────────────────────────┘
111
+ */
112
+ export function searchParamsToUrlQuery(searchParams: URLSearchParams): ParsedUrlQuery {
113
+ const query: ParsedUrlQuery = {}
114
+
115
+ searchParams.forEach((value, key) => {
116
+ if (typeof query[key] === 'undefined') {
117
+ query[key] = value
118
+ } else if (Array.isArray(query[key])) {
119
+ ;(query[key] as string[]).push(value)
120
+ } else {
121
+ query[key] = [query[key] as string, value]
122
+ }
123
+ })
124
+
125
+ return query
126
+ }
127
+
128
+ /*
129
+ ┌─────────────────────────────────────────────────────────────────────────┐
130
+ │ packages/next/src/shared/lib/router/utils/parse-url.ts │
131
+ └─────────────────────────────────────────────────────────────────────────┘
132
+ */
133
+ interface ParsedUrl {
134
+ hash: string
135
+ hostname?: string | null
136
+ href: string
137
+ pathname: string
138
+ port?: string | null
139
+ protocol?: string | null
140
+ query: ParsedUrlQuery
141
+ search: string
142
+ }
143
+
144
+ export function parseUrl(url: string): ParsedUrl {
145
+ const parsedURL = url.startsWith('/') ? new URL(url, 'http://n') : new URL(url)
146
+
147
+ return {
148
+ hash: parsedURL.hash,
149
+ hostname: parsedURL.hostname,
150
+ href: parsedURL.href,
151
+ pathname: parsedURL.pathname,
152
+ port: parsedURL.port,
153
+ protocol: parsedURL.protocol,
154
+ query: searchParamsToUrlQuery(parsedURL.searchParams),
155
+ search: parsedURL.search,
156
+ }
157
+ }
158
+
159
+ /*
160
+ ┌─────────────────────────────────────────────────────────────────────────┐
161
+ │ packages/next/src/shared/lib/router/utils/prepare-destination.ts │
162
+ │ — Changed to use WHATWG Fetch `Request` instead of │
163
+ │ `http.IncomingMessage`. │
164
+ └─────────────────────────────────────────────────────────────────────────┘
165
+ */
166
+ export function matchHas(
167
+ req: Pick<Request, 'headers' | 'url'>,
168
+ query: Params,
169
+ has: RouteHas[] = [],
170
+ missing: RouteHas[] = [],
171
+ ): false | Params {
172
+ const params: Params = {}
173
+ const cookies = getCookies(req.headers)
174
+ const url = new URL(req.url)
175
+ const hasMatch = (hasItem: RouteHas) => {
176
+ let value: undefined | string | null
177
+ let key = hasItem.key
178
+
179
+ switch (hasItem.type) {
180
+ case 'header': {
181
+ key = hasItem.key.toLowerCase()
182
+ value = req.headers.get(key)
183
+ break
184
+ }
185
+ case 'cookie': {
186
+ value = cookies[hasItem.key]
187
+ break
188
+ }
189
+ case 'query': {
190
+ value = query[hasItem.key]
191
+ break
192
+ }
193
+ case 'host': {
194
+ value = url.hostname
195
+ break
196
+ }
197
+ default: {
198
+ break
199
+ }
200
+ }
201
+ if (!hasItem.value && value && key) {
202
+ params[getSafeParamName(key)] = value
203
+
204
+ return true
205
+ } else if (value) {
206
+ const matcher = new RegExp(`^${hasItem.value}$`)
207
+ const matches = Array.isArray(value)
208
+ ? value.slice(-1)[0].match(matcher)
209
+ : value.match(matcher)
210
+
211
+ if (matches) {
212
+ if (Array.isArray(matches)) {
213
+ if (matches.groups) {
214
+ Object.keys(matches.groups).forEach((groupKey) => {
215
+ params[groupKey] = matches.groups![groupKey]
216
+ })
217
+ } else if (hasItem.type === 'host' && matches[0]) {
218
+ params.host = matches[0]
219
+ }
220
+ }
221
+ return true
222
+ }
223
+ }
224
+ return false
225
+ }
226
+
227
+ const allMatch = has.every((item) => hasMatch(item)) && !missing.some((item) => hasMatch(item))
228
+
229
+ if (allMatch) {
230
+ return params
231
+ }
232
+ return false
233
+ }
234
+
235
+ export function compileNonPath(value: string, params: Params): string {
236
+ if (!value.includes(':')) {
237
+ return value
238
+ }
239
+
240
+ for (const key of Object.keys(params)) {
241
+ if (value.includes(`:${key}`)) {
242
+ value = value
243
+ .replace(new RegExp(`:${key}\\*`, 'g'), `:${key}--ESCAPED_PARAM_ASTERISKS`)
244
+ .replace(new RegExp(`:${key}\\?`, 'g'), `:${key}--ESCAPED_PARAM_QUESTION`)
245
+ .replace(new RegExp(`:${key}\\+`, 'g'), `:${key}--ESCAPED_PARAM_PLUS`)
246
+ .replace(new RegExp(`:${key}(?!\\w)`, 'g'), `--ESCAPED_PARAM_COLON${key}`)
247
+ }
248
+ }
249
+ value = value
250
+ .replace(/(:|\*|\?|\+|\(|\)|\{|\})/g, '\\$1')
251
+ .replace(/--ESCAPED_PARAM_PLUS/g, '+')
252
+ .replace(/--ESCAPED_PARAM_COLON/g, ':')
253
+ .replace(/--ESCAPED_PARAM_QUESTION/g, '?')
254
+ .replace(/--ESCAPED_PARAM_ASTERISKS/g, '*')
255
+ // the value needs to start with a forward-slash to be compiled
256
+ // correctly
257
+ return compile(`/${value}`, { validate: false })(params).slice(1)
258
+ }
259
+
260
+ export function prepareDestination(args: {
261
+ appendParamsToQuery: boolean
262
+ destination: string
263
+ params: Params
264
+ query: ParsedUrlQuery
265
+ }) {
266
+ const query = Object.assign({}, args.query)
267
+ delete query.__nextLocale
268
+ delete query.__nextDefaultLocale
269
+ delete query.__nextDataReq
270
+
271
+ let escapedDestination = args.destination
272
+
273
+ for (const param of Object.keys({ ...args.params, ...query })) {
274
+ escapedDestination = escapeSegment(escapedDestination, param)
275
+ }
276
+
277
+ const parsedDestination: ParsedUrl = parseUrl(escapedDestination)
278
+ const destQuery = parsedDestination.query
279
+ const destPath = unescapeSegments(`${parsedDestination.pathname!}${parsedDestination.hash || ''}`)
280
+ const destHostname = unescapeSegments(parsedDestination.hostname || '')
281
+ const destPathParamKeys: Key[] = []
282
+ const destHostnameParamKeys: Key[] = []
283
+ pathToRegexp(destPath, destPathParamKeys)
284
+ pathToRegexp(destHostname, destHostnameParamKeys)
285
+
286
+ const destParams: (string | number)[] = []
287
+
288
+ destPathParamKeys.forEach((key) => destParams.push(key.name))
289
+ destHostnameParamKeys.forEach((key) => destParams.push(key.name))
290
+
291
+ const destPathCompiler = compile(
292
+ destPath,
293
+ // we don't validate while compiling the destination since we should
294
+ // have already validated before we got to this point and validating
295
+ // breaks compiling destinations with named pattern params from the source
296
+ // e.g. /something:hello(.*) -> /another/:hello is broken with validation
297
+ // since compile validation is meant for reversing and not for inserting
298
+ // params from a separate path-regex into another
299
+ { validate: false },
300
+ )
301
+
302
+ const destHostnameCompiler = compile(destHostname, { validate: false })
303
+
304
+ // update any params in query values
305
+ for (const [key, strOrArray] of Object.entries(destQuery)) {
306
+ // the value needs to start with a forward-slash to be compiled
307
+ // correctly
308
+ if (Array.isArray(strOrArray)) {
309
+ destQuery[key] = strOrArray.map((value) =>
310
+ compileNonPath(unescapeSegments(value), args.params),
311
+ )
312
+ } else {
313
+ destQuery[key] = compileNonPath(unescapeSegments(strOrArray), args.params)
314
+ }
315
+ }
316
+
317
+ // add path params to query if it's not a redirect and not
318
+ // already defined in destination query or path
319
+ const paramKeys = Object.keys(args.params).filter((name) => name !== 'nextInternalLocale')
320
+
321
+ if (args.appendParamsToQuery && !paramKeys.some((key) => destParams.includes(key))) {
322
+ for (const key of paramKeys) {
323
+ if (!(key in destQuery)) {
324
+ destQuery[key] = args.params[key]
325
+ }
326
+ }
327
+ }
328
+
329
+ let newUrl
330
+
331
+ try {
332
+ newUrl = destPathCompiler(args.params)
333
+
334
+ const [pathname, hash] = newUrl.split('#')
335
+ parsedDestination.hostname = destHostnameCompiler(args.params)
336
+ parsedDestination.pathname = pathname
337
+ parsedDestination.hash = `${hash ? '#' : ''}${hash || ''}`
338
+ delete (parsedDestination as any).search
339
+ } catch (err: any) {
340
+ if (err.message.match(/Expected .*? to not repeat, but got an array/)) {
341
+ throw new Error(
342
+ `To use a multi-match in the destination you must add \`*\` at the end of the param name to signify it should repeat. https://nextjs.org/docs/messages/invalid-multi-match`,
343
+ )
344
+ }
345
+ throw err
346
+ }
347
+
348
+ // Query merge order lowest priority to highest
349
+ // 1. initial URL query values
350
+ // 2. path segment values
351
+ // 3. destination specified query values
352
+ parsedDestination.query = {
353
+ ...query,
354
+ ...parsedDestination.query,
355
+ }
356
+
357
+ return {
358
+ newUrl,
359
+ destQuery,
360
+ parsedDestination,
361
+ }
362
+ }
363
+
364
+ /**
365
+ * Ensure only a-zA-Z are used for param names for proper interpolating
366
+ * with path-to-regexp
367
+ */
368
+ function getSafeParamName(paramName: string) {
369
+ let newParamName = ''
370
+
371
+ for (let i = 0; i < paramName.length; i++) {
372
+ const charCode = paramName.charCodeAt(i)
373
+
374
+ if (
375
+ (charCode > 64 && charCode < 91) || // A-Z
376
+ (charCode > 96 && charCode < 123) // a-z
377
+ ) {
378
+ newParamName += paramName[i]
379
+ }
380
+ }
381
+ return newParamName
382
+ }
383
+
384
+ function escapeSegment(str: string, segmentName: string) {
385
+ return str.replace(
386
+ new RegExp(`:${escapeStringRegexp(segmentName)}`, 'g'),
387
+ `__ESC_COLON_${segmentName}`,
388
+ )
389
+ }
390
+
391
+ function unescapeSegments(str: string) {
392
+ return str.replace(/__ESC_COLON_/gi, ':')
393
+ }
394
+
395
+ /*
396
+ ┌─────────────────────────────────────────────────────────────────────────┐
397
+ │ packages/next/src/shared/lib/router/utils/is-dynamic.ts │
398
+ └─────────────────────────────────────────────────────────────────────────┘
399
+ */
400
+ // Identify /[param]/ in route string
401
+ const TEST_ROUTE = /\/\[[^/]+?\](?=\/|$)/
402
+
403
+ export function isDynamicRoute(route: string): boolean {
404
+ return TEST_ROUTE.test(route)
405
+ }
406
+
407
+ /*
408
+ ┌─────────────────────────────────────────────────────────────────────────┐
409
+ │ packages/next/shared/lib/router/utils/middleware-route-matcher.ts │
410
+ └─────────────────────────────────────────────────────────────────────────┘
411
+ */
412
+ export interface MiddlewareRouteMatch {
413
+ (
414
+ pathname: string | null | undefined,
415
+ request: Pick<Request, 'headers' | 'url'>,
416
+ query: Params,
417
+ ): boolean
418
+ }
419
+
420
+ export interface MiddlewareMatcher {
421
+ regexp: string
422
+ locale?: false
423
+ has?: RouteHas[]
424
+ missing?: RouteHas[]
425
+ }
426
+
427
+ export function getMiddlewareRouteMatcher(matchers: MiddlewareMatcher[]): MiddlewareRouteMatch {
428
+ return (
429
+ pathname: string | null | undefined,
430
+ req: Pick<Request, 'headers' | 'url'>,
431
+ query: Params,
432
+ ) => {
433
+ for (const matcher of matchers) {
434
+ const routeMatch = new RegExp(matcher.regexp).exec(pathname!)
435
+ if (!routeMatch) {
436
+ continue
437
+ }
438
+
439
+ if (matcher.has || matcher.missing) {
440
+ const hasParams = matchHas(req, query, matcher.has, matcher.missing)
441
+ if (!hasParams) {
442
+ continue
443
+ }
444
+ }
445
+
446
+ return true
447
+ }
448
+
449
+ return false
450
+ }
451
+ }
@@ -0,0 +1 @@
1
+ []
@@ -1,11 +1,20 @@
1
1
  import type { Context } from '@netlify/edge-functions'
2
2
 
3
+ import matchers from './matchers.json' assert { type: 'json' }
4
+
3
5
  import { buildNextRequest, RequestData } from './lib/next-request.ts'
4
6
  import { buildResponse } from './lib/response.ts'
5
7
  import { FetchEventResult } from './lib/response.ts'
8
+ import {
9
+ type MiddlewareRouteMatch,
10
+ getMiddlewareRouteMatcher,
11
+ searchParamsToUrlQuery,
12
+ } from './lib/routing.ts'
6
13
 
7
14
  type NextHandler = (params: { request: RequestData }) => Promise<FetchEventResult>
8
15
 
16
+ const matchesMiddleware: MiddlewareRouteMatch = getMiddlewareRouteMatcher(matchers || [])
17
+
9
18
  /**
10
19
  * Runs a Next.js middleware as a Netlify Edge Function. It translates a web
11
20
  * platform Request into a NextRequest instance on the way in, and translates
@@ -20,13 +29,17 @@ export async function handleMiddleware(
20
29
  context: Context,
21
30
  nextHandler: NextHandler,
22
31
  ) {
23
- // Don't run in dev
24
- if (Netlify.env.has('NETLIFY_DEV')) {
32
+ const nextRequest = buildNextRequest(request, context)
33
+ const url = new URL(request.url)
34
+
35
+ // While we have already checked the path when mapping to the edge function,
36
+ // Next.js supports extra rules that we need to check here too, because we
37
+ // might be running an edge function for a path we should not. If we find
38
+ // that's the case, short-circuit the execution.
39
+ if (!matchesMiddleware(url.pathname, request, searchParamsToUrlQuery(url.searchParams))) {
25
40
  return
26
41
  }
27
42
 
28
- const nextRequest = buildNextRequest(request, context)
29
-
30
43
  try {
31
44
  const result = await nextHandler({ request: nextRequest })
32
45
  const response = await buildResponse({ result, request: request, context })
@@ -2,6 +2,9 @@
2
2
  // a Webpack bundle. You should not import this file from anywhere in the
3
3
  // application.
4
4
  import { AsyncLocalStorage } from 'node:async_hooks'
5
+ import process from 'node:process'
6
+
7
+ globalThis.process = process
5
8
 
6
9
  globalThis.AsyncLocalStorage = AsyncLocalStorage
7
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/plugin-nextjs",
3
- "version": "5.0.0-beta.0",
3
+ "version": "5.0.0-beta.2",
4
4
  "description": "Run Next.js seamlessly on Netlify",
5
5
  "main": "./dist/index.js",
6
6
  "type": "module",