@opennextjs/aws 4.1.10 → 4.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build/createImageOptimizationBundle.js +1 -1
- package/dist/build/patch/patches/patchFetchCacheWaitUntil.d.ts +11 -1
- package/dist/build/patch/patches/patchFetchCacheWaitUntil.js +28 -11
- package/dist/core/requestHandler.d.ts +26 -1
- package/dist/core/requestHandler.js +61 -6
- package/dist/core/routeMatchMetadata.d.ts +27 -0
- package/dist/core/routeMatchMetadata.js +146 -0
- package/dist/types/next-types.d.ts +28 -0
- package/package.json +3 -3
|
@@ -74,7 +74,7 @@ export async function createImageOptimizationBundle(options) {
|
|
|
74
74
|
// Sharp provides pre-build binaries for all platforms. https://github.com/lovell/sharp/blob/main/docs/install.md#cross-platform
|
|
75
75
|
// Target should be same as used by Lambda, see https://github.com/sst/sst/blob/ca6f763fdfddd099ce2260202d0ce48c72e211ea/packages/sst/src/constructs/NextjsSite.ts#L114
|
|
76
76
|
// For SHARP_IGNORE_GLOBAL_LIBVIPS see: https://github.com/lovell/sharp/blob/main/docs/install.md#aws-lambda
|
|
77
|
-
const sharpVersion = process.env.SHARP_VERSION ?? "0.35.
|
|
77
|
+
const sharpVersion = process.env.SHARP_VERSION ?? "0.35.5";
|
|
78
78
|
// In development, we want to use the local machine settings
|
|
79
79
|
const isDev = config.imageOptimization?.loader === "fs-dev";
|
|
80
80
|
installDependencies(outputPath, config.imageOptimization?.install ?? {
|
|
@@ -1,3 +1,13 @@
|
|
|
1
1
|
import type { CodePatcher } from "../codePatcher.js";
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Keeps the runtime alive until a fetch cache entry is written.
|
|
4
|
+
*
|
|
5
|
+
* Next writes the fetch cache in the background (`res.arrayBuffer().then(...)`) without a `waitUntil`.
|
|
6
|
+
* The write is registered like the `waitUntil` provided to Next (see `provideNextAfterProvider`):
|
|
7
|
+
* with the runtime `waitUntil` when available, so that writes starting after the request has been
|
|
8
|
+
* drained (background revalidation) are covered, otherwise with the `pendingPromiseRunner`.
|
|
9
|
+
*
|
|
10
|
+
* The fix evaluates to the original promise, Next 15.4+ awaits it.
|
|
11
|
+
*/
|
|
12
|
+
export declare const rule = "\nrule:\n kind: call_expression\n pattern: $PROMISE\n all:\n # `x.arrayBuffer().then(...).catch(...).finally(...)`.\n # Arguments are not searched so that enclosing calls (`getTracer().trace(...)` up to Next 15.4)\n # and the patched code do not match.\n - has: { pattern: $_.arrayBuffer().then, stopBy: { kind: arguments } }\n - has: { pattern: \"Buffer.from\", stopBy: end }\n - has: { pattern: $_.FETCH, stopBy: end }\n - any:\n - inside: { kind: sequence_expression, inside: { kind: return_statement } }\n - inside: { kind: expression_statement, precedes: { kind: return_statement } }\n # Next 15.4+: `const cacheSetPromise = cloned1.arrayBuffer().then(...)`\n - inside: { kind: variable_declarator }\n\nfix: |\n ((promise) => {\n const store = globalThis.__openNextAls?.getStore();\n if (store?.waitUntil) {\n store.waitUntil(promise);\n } else {\n store?.pendingPromiseRunner.add(promise);\n }\n return promise;\n })($PROMISE)\n";
|
|
3
13
|
export declare const patchFetchCacheSetMissingWaitUntil: CodePatcher;
|
|
@@ -1,25 +1,42 @@
|
|
|
1
1
|
import { getCrossPlatformPathRegex } from "../../../utils/regex.js";
|
|
2
2
|
import { createPatchCode } from "../astCodePatcher.js";
|
|
3
|
+
/**
|
|
4
|
+
* Keeps the runtime alive until a fetch cache entry is written.
|
|
5
|
+
*
|
|
6
|
+
* Next writes the fetch cache in the background (`res.arrayBuffer().then(...)`) without a `waitUntil`.
|
|
7
|
+
* The write is registered like the `waitUntil` provided to Next (see `provideNextAfterProvider`):
|
|
8
|
+
* with the runtime `waitUntil` when available, so that writes starting after the request has been
|
|
9
|
+
* drained (background revalidation) are covered, otherwise with the `pendingPromiseRunner`.
|
|
10
|
+
*
|
|
11
|
+
* The fix evaluates to the original promise, Next 15.4+ awaits it.
|
|
12
|
+
*/
|
|
3
13
|
export const rule = `
|
|
4
14
|
rule:
|
|
5
15
|
kind: call_expression
|
|
6
16
|
pattern: $PROMISE
|
|
7
17
|
all:
|
|
8
|
-
|
|
18
|
+
# \`x.arrayBuffer().then(...).catch(...).finally(...)\`.
|
|
19
|
+
# Arguments are not searched so that enclosing calls (\`getTracer().trace(...)\` up to Next 15.4)
|
|
20
|
+
# and the patched code do not match.
|
|
21
|
+
- has: { pattern: $_.arrayBuffer().then, stopBy: { kind: arguments } }
|
|
9
22
|
- has: { pattern: "Buffer.from", stopBy: end }
|
|
10
|
-
- any:
|
|
11
|
-
- inside:
|
|
12
|
-
kind: sequence_expression
|
|
13
|
-
inside:
|
|
14
|
-
kind: return_statement
|
|
15
|
-
- inside:
|
|
16
|
-
kind: expression_statement
|
|
17
|
-
precedes:
|
|
18
|
-
kind: return_statement
|
|
19
23
|
- has: { pattern: $_.FETCH, stopBy: end }
|
|
24
|
+
- any:
|
|
25
|
+
- inside: { kind: sequence_expression, inside: { kind: return_statement } }
|
|
26
|
+
- inside: { kind: expression_statement, precedes: { kind: return_statement } }
|
|
27
|
+
# Next 15.4+: \`const cacheSetPromise = cloned1.arrayBuffer().then(...)\`
|
|
28
|
+
- inside: { kind: variable_declarator }
|
|
20
29
|
|
|
21
30
|
fix: |
|
|
22
|
-
|
|
31
|
+
((promise) => {
|
|
32
|
+
const store = globalThis.__openNextAls?.getStore();
|
|
33
|
+
if (store?.waitUntil) {
|
|
34
|
+
store.waitUntil(promise);
|
|
35
|
+
} else {
|
|
36
|
+
store?.pendingPromiseRunner.add(promise);
|
|
37
|
+
}
|
|
38
|
+
return promise;
|
|
39
|
+
})($PROMISE)
|
|
23
40
|
`;
|
|
24
41
|
export const patchFetchCacheSetMissingWaitUntil = {
|
|
25
42
|
name: "patch-fetch-cache-set-missing-wait-until",
|
|
@@ -1,3 +1,28 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { OpenNextNodeResponse } from "../http/index.js";
|
|
2
|
+
import { IncomingMessage } from "../http/index.js";
|
|
3
|
+
import type { InternalEvent, InternalResult, RoutingResult } from "../types/open-next.js";
|
|
2
4
|
import type { OpenNextHandlerOptions } from "../types/overrides.js";
|
|
3
5
|
export declare function openNextHandler(internalEvent: InternalEvent, options?: OpenNextHandlerOptions): Promise<InternalResult>;
|
|
6
|
+
/**
|
|
7
|
+
* Renders the next resolved route after a `fallback: false` route threw a `NoFallbackError`.
|
|
8
|
+
*
|
|
9
|
+
* Mirrors the Next.js router server, which retries the next matching route:
|
|
10
|
+
* - Up to six attempts are made, including the initial render, before rendering a 500.
|
|
11
|
+
* - A 404 is rendered when no route candidate is left.
|
|
12
|
+
* - Another `NoFallbackError` retries the following candidate, any other error renders a 500.
|
|
13
|
+
*
|
|
14
|
+
* Next.js 16.4+ only renders the most specific route unless the route to render is passed as a
|
|
15
|
+
* `match` request meta (see `getRouteMatchMetadata`). A 400 is rendered when the params of that
|
|
16
|
+
* match can't be decoded, like Next.js does for a `DecodeError`. Older versions select the route
|
|
17
|
+
* from `invokeOutput` and receive exactly the same metadata as before.
|
|
18
|
+
*
|
|
19
|
+
* Errors are rendered as responses, this function does not throw.
|
|
20
|
+
*
|
|
21
|
+
* @param req The request reused for each rendering attempt
|
|
22
|
+
* @param res The response populated by Next.js
|
|
23
|
+
* @param routingResult The ordered route candidates and internal request
|
|
24
|
+
* @param metadata The request metadata of the initial attempt, mutated by Next.js (i.e. it contains the stale `match`)
|
|
25
|
+
* @param index The next candidate index, counting the initial render as index 0
|
|
26
|
+
* @return A promise that resolves after rendering a route or an error response
|
|
27
|
+
*/
|
|
28
|
+
export declare function handleNoFallbackError(req: IncomingMessage, res: OpenNextNodeResponse, routingResult: RoutingResult, metadata: Record<string, unknown>, index?: number): Promise<void>;
|
|
@@ -2,9 +2,11 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
2
2
|
import { IncomingMessage } from "../http/index.js";
|
|
3
3
|
import { ISR_HEADER } from "../utils/cacheHeaders.js";
|
|
4
4
|
import { runWithOpenNextRequestContext } from "../utils/promise.js";
|
|
5
|
+
import { compareSemver } from "../utils/semver.js";
|
|
5
6
|
import { NextConfig } from "../adapters/config/index.js";
|
|
6
7
|
import { debug, error } from "../adapters/logger";
|
|
7
8
|
import { patchAsyncStorage } from "./patchAsyncStorage";
|
|
9
|
+
import { getRouteMatchMetadata } from "./routeMatchMetadata";
|
|
8
10
|
import { constructNextUrl, convertRes, convertToQuery, createServerResponse, } from "./routing/util";
|
|
9
11
|
import routingHandler, { INTERNAL_EVENT_REQUEST_ID, INTERNAL_HEADER_REWRITE_STATUS_CODE, INTERNAL_HEADER_INITIAL_URL, INTERNAL_HEADER_RESOLVED_ROUTES, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_HEADER_PREFIX_LEN, } from "./routingHandler";
|
|
10
12
|
import { requestHandler, setNextjsPrebundledReact } from "./util";
|
|
@@ -227,8 +229,33 @@ async function processRequest(req, res, routingResult) {
|
|
|
227
229
|
}
|
|
228
230
|
}
|
|
229
231
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
+
// Rendering attempts (including the initial one) when falling through `fallback: false` routes.
|
|
233
|
+
// The Next.js router server allows `handleIndex` 0..5 (`if (handleIndex > 5)`) since 13.4.13.
|
|
234
|
+
const MAX_RENDER_ATTEMPTS = 6;
|
|
235
|
+
/**
|
|
236
|
+
* Renders the next resolved route after a `fallback: false` route threw a `NoFallbackError`.
|
|
237
|
+
*
|
|
238
|
+
* Mirrors the Next.js router server, which retries the next matching route:
|
|
239
|
+
* - Up to six attempts are made, including the initial render, before rendering a 500.
|
|
240
|
+
* - A 404 is rendered when no route candidate is left.
|
|
241
|
+
* - Another `NoFallbackError` retries the following candidate, any other error renders a 500.
|
|
242
|
+
*
|
|
243
|
+
* Next.js 16.4+ only renders the most specific route unless the route to render is passed as a
|
|
244
|
+
* `match` request meta (see `getRouteMatchMetadata`). A 400 is rendered when the params of that
|
|
245
|
+
* match can't be decoded, like Next.js does for a `DecodeError`. Older versions select the route
|
|
246
|
+
* from `invokeOutput` and receive exactly the same metadata as before.
|
|
247
|
+
*
|
|
248
|
+
* Errors are rendered as responses, this function does not throw.
|
|
249
|
+
*
|
|
250
|
+
* @param req The request reused for each rendering attempt
|
|
251
|
+
* @param res The response populated by Next.js
|
|
252
|
+
* @param routingResult The ordered route candidates and internal request
|
|
253
|
+
* @param metadata The request metadata of the initial attempt, mutated by Next.js (i.e. it contains the stale `match`)
|
|
254
|
+
* @param index The next candidate index, counting the initial render as index 0
|
|
255
|
+
* @return A promise that resolves after rendering a route or an error response
|
|
256
|
+
*/
|
|
257
|
+
export async function handleNoFallbackError(req, res, routingResult, metadata, index = 1) {
|
|
258
|
+
if (index >= MAX_RENDER_ATTEMPTS) {
|
|
232
259
|
await tryRenderError("500", res, routingResult.internalEvent);
|
|
233
260
|
return;
|
|
234
261
|
}
|
|
@@ -236,11 +263,29 @@ async function handleNoFallbackError(req, res, routingResult, metadata, index =
|
|
|
236
263
|
await tryRenderError("404", res, routingResult.internalEvent);
|
|
237
264
|
return;
|
|
238
265
|
}
|
|
266
|
+
const route = routingResult.resolvedRoutes[index].route;
|
|
239
267
|
try {
|
|
268
|
+
// Skip the call itself on older versions: decoding here can change their error handling.
|
|
269
|
+
let routeMatchMetadata = {};
|
|
270
|
+
if (compareSemver(globalThis.nextVersion, ">=", "16.4.0")) {
|
|
271
|
+
try {
|
|
272
|
+
routeMatchMetadata = getRouteMatchMetadata(route, routingResult.internalEvent.rawPath);
|
|
273
|
+
}
|
|
274
|
+
catch (e) {
|
|
275
|
+
// Only match decoding is a bad request; rendering errors still use the 500 path.
|
|
276
|
+
if (!(e instanceof URIError)) {
|
|
277
|
+
throw e;
|
|
278
|
+
}
|
|
279
|
+
await tryRenderError("400", res, routingResult.internalEvent);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
240
283
|
await requestHandler({
|
|
241
284
|
...routingResult,
|
|
242
|
-
invokeOutput:
|
|
285
|
+
invokeOutput: route,
|
|
243
286
|
...metadata,
|
|
287
|
+
// Must come after `metadata` to override the stale `match` of the previous attempt.
|
|
288
|
+
...routeMatchMetadata,
|
|
244
289
|
})(req, res);
|
|
245
290
|
}
|
|
246
291
|
catch (e) {
|
|
@@ -253,6 +298,16 @@ async function handleNoFallbackError(req, res, routingResult, metadata, index =
|
|
|
253
298
|
}
|
|
254
299
|
}
|
|
255
300
|
}
|
|
301
|
+
/**
|
|
302
|
+
* Renders a Next.js error response with fresh request metadata.
|
|
303
|
+
*
|
|
304
|
+
* Falls back to a JSON 500 response if rendering the error page fails.
|
|
305
|
+
*
|
|
306
|
+
* @param type The HTTP error status to render
|
|
307
|
+
* @param res The response populated by Next.js
|
|
308
|
+
* @param internalEvent The request supplying headers, body, and remote address
|
|
309
|
+
* @return A promise that resolves after writing the error response
|
|
310
|
+
*/
|
|
256
311
|
async function tryRenderError(type, res, internalEvent) {
|
|
257
312
|
try {
|
|
258
313
|
const _req = new IncomingMessage({
|
|
@@ -262,11 +317,11 @@ async function tryRenderError(type, res, internalEvent) {
|
|
|
262
317
|
body: internalEvent.body,
|
|
263
318
|
remoteAddress: internalEvent.remoteAddress,
|
|
264
319
|
});
|
|
265
|
-
//
|
|
320
|
+
// Bypass route matching and directly render the requested error response.
|
|
266
321
|
const requestMetadata = {
|
|
267
322
|
// By setting invokePath and invokeQuery we can bypass some of the routing logic in Next.js
|
|
268
|
-
invokePath: type
|
|
269
|
-
invokeStatus: type
|
|
323
|
+
invokePath: `/${type}`,
|
|
324
|
+
invokeStatus: Number(type),
|
|
270
325
|
middlewareInvoke: false,
|
|
271
326
|
};
|
|
272
327
|
await requestHandler(requestMetadata)(_req, res);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { RouteMatch } from "../types/next-types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Returns the request metadata selecting `route` for `pathname`.
|
|
4
|
+
*
|
|
5
|
+
* Next.js 16.4 removed the route matcher manager from the base server: it now only renders the
|
|
6
|
+
* most specific route matching the pathname, and `invokeOutput` can skip that route but no
|
|
7
|
+
* longer selects another one. The Next.js router server forwards the route to render as a
|
|
8
|
+
* `match` request metadata instead, which is required to retry the next route after a
|
|
9
|
+
* `NoFallbackError` (i.e. a `fallback: false` page which was not prerendered).
|
|
10
|
+
*
|
|
11
|
+
* Next.js mutates the metadata of the previous attempt, which then contains the `match` of the
|
|
12
|
+
* route which threw the `NoFallbackError`. `match` is always returned (possibly `undefined`) so
|
|
13
|
+
* that it overrides this stale match even when the route can't be matched.
|
|
14
|
+
*
|
|
15
|
+
* Only used for Next.js 16.4+. Older versions retain their matcher-based retry path,
|
|
16
|
+
* including parameter-decoding error handling, without calling this helper.
|
|
17
|
+
*
|
|
18
|
+
* @param route The route pattern selected for the retry, i.e. `/blog/[slug]`
|
|
19
|
+
* @param pathname The encoded internal pathname after routing, which may include the basePath, a locale prefix and
|
|
20
|
+
* a trailing slash
|
|
21
|
+
* @return Metadata selecting the route, or `{ match: undefined }` when the route is unknown or doesn't match the
|
|
22
|
+
* pathname (Next.js then renders a 404)
|
|
23
|
+
* @throws {URIError} If a captured parameter contains malformed percent encoding
|
|
24
|
+
*/
|
|
25
|
+
export declare function getRouteMatchMetadata(route: string, pathname: string): {
|
|
26
|
+
match: RouteMatch | undefined;
|
|
27
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { AppPathRoutesManifest, AppPathsManifest, NEXT_DIR, NextConfig, PagesManifest, RoutesManifest, } from "../adapters/config/index.js";
|
|
3
|
+
import { normalizeLocalePath, selectAppPageEntry, } from "../utils/routeCacheKey.js";
|
|
4
|
+
// `[slug]`, `[...slug]` or `[[...slug]]`, possibly after an interception marker like `(.)`
|
|
5
|
+
const DYNAMIC_SEGMENT_REGEX = /\[{1,2}(\.\.\.)?([^\]]+)\]{1,2}$/;
|
|
6
|
+
/**
|
|
7
|
+
* Returns the request metadata selecting `route` for `pathname`.
|
|
8
|
+
*
|
|
9
|
+
* Next.js 16.4 removed the route matcher manager from the base server: it now only renders the
|
|
10
|
+
* most specific route matching the pathname, and `invokeOutput` can skip that route but no
|
|
11
|
+
* longer selects another one. The Next.js router server forwards the route to render as a
|
|
12
|
+
* `match` request metadata instead, which is required to retry the next route after a
|
|
13
|
+
* `NoFallbackError` (i.e. a `fallback: false` page which was not prerendered).
|
|
14
|
+
*
|
|
15
|
+
* Next.js mutates the metadata of the previous attempt, which then contains the `match` of the
|
|
16
|
+
* route which threw the `NoFallbackError`. `match` is always returned (possibly `undefined`) so
|
|
17
|
+
* that it overrides this stale match even when the route can't be matched.
|
|
18
|
+
*
|
|
19
|
+
* Only used for Next.js 16.4+. Older versions retain their matcher-based retry path,
|
|
20
|
+
* including parameter-decoding error handling, without calling this helper.
|
|
21
|
+
*
|
|
22
|
+
* @param route The route pattern selected for the retry, i.e. `/blog/[slug]`
|
|
23
|
+
* @param pathname The encoded internal pathname after routing, which may include the basePath, a locale prefix and
|
|
24
|
+
* a trailing slash
|
|
25
|
+
* @return Metadata selecting the route, or `{ match: undefined }` when the route is unknown or doesn't match the
|
|
26
|
+
* pathname (Next.js then renders a 404)
|
|
27
|
+
* @throws {URIError} If a captured parameter contains malformed percent encoding
|
|
28
|
+
*/
|
|
29
|
+
export function getRouteMatchMetadata(route, pathname) {
|
|
30
|
+
const definition = getRouteDefinition(route);
|
|
31
|
+
const params = definition
|
|
32
|
+
? getRouteParams(route, normalizePathname(pathname))
|
|
33
|
+
: null;
|
|
34
|
+
return {
|
|
35
|
+
match: definition && params !== null ? { definition, params } : undefined,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Resolves a route to its Next.js definition from the manifests.
|
|
40
|
+
*
|
|
41
|
+
* App pages take precedence over app route handlers, then pages (`/api` and `/api/*` are pages API routes).
|
|
42
|
+
* For app pages, the canonical entry is selected like Next.js when a route has parallel slots or route groups.
|
|
43
|
+
*
|
|
44
|
+
* @param route The route pattern selected for the retry, i.e. `/blog/[slug]`
|
|
45
|
+
* @return The route definition, or `undefined` when the route is in none of the manifests
|
|
46
|
+
*/
|
|
47
|
+
function getRouteDefinition(route) {
|
|
48
|
+
const appPaths = Object.keys(AppPathRoutesManifest).filter((appPath) => AppPathRoutesManifest[appPath] === route);
|
|
49
|
+
const appPages = appPaths.filter((appPath) => appPath.endsWith("/page"));
|
|
50
|
+
// Select the same canonical entry as Next.js and the route cache owner.
|
|
51
|
+
const appPage = selectAppPageEntry(route, appPages);
|
|
52
|
+
if (appPage) {
|
|
53
|
+
return {
|
|
54
|
+
kind: "APP_PAGE",
|
|
55
|
+
pathname: route,
|
|
56
|
+
page: appPage,
|
|
57
|
+
filename: getFilename(AppPathsManifest[appPage]),
|
|
58
|
+
appPaths: appPages,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const appRoute = appPaths.find((appPath) => appPath.endsWith("/route"));
|
|
62
|
+
if (appRoute) {
|
|
63
|
+
return {
|
|
64
|
+
kind: "APP_ROUTE",
|
|
65
|
+
pathname: route,
|
|
66
|
+
page: appRoute,
|
|
67
|
+
filename: getFilename(AppPathsManifest[appRoute]),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
if (PagesManifest[route]) {
|
|
71
|
+
return {
|
|
72
|
+
kind: route === "/api" || route.startsWith("/api/") ? "PAGES_API" : "PAGES",
|
|
73
|
+
pathname: route,
|
|
74
|
+
page: route,
|
|
75
|
+
filename: getFilename(PagesManifest[route]),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Resolves a manifest entry to the absolute path of the compiled server module.
|
|
82
|
+
*
|
|
83
|
+
* @param manifestPath The path relative to `.next/server`, as stored in the manifests
|
|
84
|
+
* @return The absolute module path, or an empty string when the entry is missing
|
|
85
|
+
*/
|
|
86
|
+
function getFilename(manifestPath) {
|
|
87
|
+
return manifestPath ? path.join(NEXT_DIR, "server", manifestPath) : "";
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Normalizes a pathname so it can be matched against route patterns.
|
|
91
|
+
*
|
|
92
|
+
* Strips the basePath, then the locale (case-insensitive), then the trailing slash, which are not part of route
|
|
93
|
+
* patterns. The pathname is not decoded: params are decoded after matching, like Next.js.
|
|
94
|
+
*
|
|
95
|
+
* @param pathname The internal pathname of the request
|
|
96
|
+
* @return The pathname without basePath, locale prefix and trailing slash
|
|
97
|
+
*/
|
|
98
|
+
function normalizePathname(pathname) {
|
|
99
|
+
const basePath = NextConfig.basePath;
|
|
100
|
+
let normalized = pathname;
|
|
101
|
+
if (basePath &&
|
|
102
|
+
(normalized === basePath || normalized.startsWith(`${basePath}/`))) {
|
|
103
|
+
normalized = normalized.slice(basePath.length) || "/";
|
|
104
|
+
}
|
|
105
|
+
normalized = normalizeLocalePath(normalized, NextConfig.i18n?.locales);
|
|
106
|
+
return normalized !== "/" && normalized.endsWith("/")
|
|
107
|
+
? normalized.slice(0, -1)
|
|
108
|
+
: normalized;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Extracts the decoded params of a route for a normalized pathname.
|
|
112
|
+
*
|
|
113
|
+
* Static routes have `undefined` params. Missing optional catch-all params are omitted.
|
|
114
|
+
*
|
|
115
|
+
* @param route The route pattern, i.e. `/blog/[slug]`
|
|
116
|
+
* @param pathname The normalized pathname to match against the route
|
|
117
|
+
* @return The params, `undefined` for a matching static route, or `null` when the route doesn't match
|
|
118
|
+
* @throws {URIError} If a captured parameter contains malformed percent encoding, like Next.js
|
|
119
|
+
*/
|
|
120
|
+
function getRouteParams(route, pathname) {
|
|
121
|
+
const dynamicRoute = RoutesManifest.routes.dynamic.find(({ page }) => page === route);
|
|
122
|
+
if (!dynamicRoute) {
|
|
123
|
+
return pathname === route ? undefined : null;
|
|
124
|
+
}
|
|
125
|
+
const result = new RegExp(dynamicRoute.regex).exec(pathname);
|
|
126
|
+
if (!result) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
// The regex has one capture group per dynamic segment, in order
|
|
130
|
+
const params = {};
|
|
131
|
+
let group = 1;
|
|
132
|
+
for (const segment of route.split("/")) {
|
|
133
|
+
const dynamicSegment = DYNAMIC_SEGMENT_REGEX.exec(segment);
|
|
134
|
+
if (!dynamicSegment) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const [, isCatchAll, name] = dynamicSegment;
|
|
138
|
+
const value = result[group++];
|
|
139
|
+
if (value !== undefined) {
|
|
140
|
+
params[name] = isCatchAll
|
|
141
|
+
? value.split("/").map(decodeURIComponent)
|
|
142
|
+
: decodeURIComponent(value);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return params;
|
|
146
|
+
}
|
|
@@ -87,6 +87,34 @@ export interface RouteDefinition {
|
|
|
87
87
|
page: string;
|
|
88
88
|
regex: string;
|
|
89
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Kind of a Next.js route.
|
|
92
|
+
* https://github.com/vercel/next.js/blob/v16.4.0/packages/next/src/server/route-kind.ts
|
|
93
|
+
*/
|
|
94
|
+
export type RouteKind = "PAGES" | "PAGES_API" | "APP_PAGE" | "APP_ROUTE";
|
|
95
|
+
/**
|
|
96
|
+
* Subset of the Next.js route definition read when rendering a `match` request meta.
|
|
97
|
+
* Not to be confused with the routes manifest `RouteDefinition` above.
|
|
98
|
+
* https://github.com/vercel/next.js/blob/v16.4.0/packages/next/src/server/route-definitions/route-definition.ts
|
|
99
|
+
*/
|
|
100
|
+
export interface RouteMatchDefinition {
|
|
101
|
+
kind: RouteKind;
|
|
102
|
+
pathname: string;
|
|
103
|
+
page: string;
|
|
104
|
+
filename: string;
|
|
105
|
+
/** Only for `APP_PAGE` routes */
|
|
106
|
+
appPaths?: string[];
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The Next.js `match` request meta, the route selected to render a request.
|
|
110
|
+
* OpenNext only passes it to Next.js 16.4+, which no longer selects the route from `invokeOutput`.
|
|
111
|
+
* https://github.com/vercel/next.js/blob/v16.4.0/packages/next/src/server/route-matches/route-match.ts
|
|
112
|
+
*/
|
|
113
|
+
export interface RouteMatch {
|
|
114
|
+
definition: RouteMatchDefinition;
|
|
115
|
+
/** `undefined` for static routes */
|
|
116
|
+
params?: Record<string, string | string[]>;
|
|
117
|
+
}
|
|
90
118
|
export interface DataRouteDefinition {
|
|
91
119
|
page: string;
|
|
92
120
|
dataRouteRegex: string;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"access": "public"
|
|
4
4
|
},
|
|
5
5
|
"name": "@opennextjs/aws",
|
|
6
|
-
"version": "4.1.
|
|
6
|
+
"version": "4.1.11",
|
|
7
7
|
"bin": {
|
|
8
8
|
"open-next": "./dist/index.js"
|
|
9
9
|
},
|
|
@@ -56,12 +56,12 @@
|
|
|
56
56
|
"@types/express": "5.0.6",
|
|
57
57
|
"@types/node": "20.17.6",
|
|
58
58
|
"concurrently": "^9.2.4",
|
|
59
|
-
"next": "16.
|
|
59
|
+
"next": "16.4.1",
|
|
60
60
|
"tsc-alias": "^1.9.7",
|
|
61
61
|
"typescript": "5.9.3"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
|
-
"next": ">=15.5.
|
|
64
|
+
"next": ">=15.5.28 <16 || >=16.4.1"
|
|
65
65
|
},
|
|
66
66
|
"bugs": {
|
|
67
67
|
"url": "https://github.com/opennextjs/opennextjs-aws/issues"
|