Skip to content

Commit 62a2bac

Browse files
committed
fixup! take 2
1 parent 848d6aa commit 62a2bac

File tree

8 files changed

+138
-88
lines changed

8 files changed

+138
-88
lines changed

.changeset/late-dodos-attack.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@opennextjs/aws": minor
3+
---
4+
5+
Add an asset resolver

packages/open-next/src/core/createMainHandler.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { debug } from "../adapters/logger";
44
import { generateUniqueId } from "../adapters/util";
55
import { openNextHandler } from "./requestHandler";
66
import {
7+
resolveAssetResolver,
78
resolveCdnInvalidation,
89
resolveConverter,
910
resolveIncrementalCache,
@@ -38,6 +39,12 @@ export async function createMainHandler() {
3839

3940
globalThis.tagCache = await resolveTagCache(thisFunction.override?.tagCache);
4041

42+
if (config.middleware?.external !== true) {
43+
globalThis.assetResolver = await resolveAssetResolver(
44+
globalThis.openNextConfig.middleware?.assetResolver,
45+
);
46+
}
47+
4148
globalThis.proxyExternalRequest = await resolveProxyRequest(
4249
thisFunction.override?.proxyExternalRequest,
4350
);

packages/open-next/src/core/requestHandler.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,9 @@ export async function openNextHandler(
8080
};
8181

8282
//#override withRouting
83-
routingResult = await routingHandler(internalEvent);
83+
routingResult = await routingHandler(internalEvent, {
84+
assetResolver: globalThis.assetResolver,
85+
});
8486
//#endOverride
8587

8688
const headers =

packages/open-next/src/core/routing/matcher.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,11 @@ export function getNextConfigHeaders(
170170
return requestHeaders;
171171
}
172172

173+
/**
174+
* TODO: This method currently only check for the first match.
175+
* It should check for all matches for `beforeFiles` and `afterFiles` rewrite
176+
* See https://nextjs.org/docs/app/api-reference/config/next-config-js/rewrites
177+
*/
173178
export function handleRewrites<T extends RewriteDefinition>(
174179
event: InternalEvent,
175180
rewrites: T[],

packages/open-next/src/core/routingHandler.ts

Lines changed: 101 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -48,24 +48,31 @@ const geoHeaderToNextHeader = {
4848
"x-open-next-longitude": "x-vercel-ip-longitude",
4949
};
5050

51+
/**
52+
* Adds the middleware headers to an event or result.
53+
*
54+
* @param eventOrResult
55+
* @param middlewareHeaders
56+
*/
5157
function applyMiddlewareHeaders(
52-
eventHeaders: Record<string, string | string[]>,
58+
eventOrResult: InternalEvent | InternalResult,
5359
middlewareHeaders: Record<string, string | string[] | undefined>,
54-
setPrefix = true,
5560
) {
56-
const keyPrefix = setPrefix ? MIDDLEWARE_HEADER_PREFIX : "";
61+
// Use the `MIDDLEWARE_HEADER_PREFIX` prefix for events, they will be processed by the request handler later.
62+
// Results do not go through the request handler and should not be prefixed.
63+
const isResult = isInternalResult(eventOrResult);
64+
const headers = eventOrResult.headers;
65+
const keyPrefix = isResult ? "" : MIDDLEWARE_HEADER_PREFIX;
5766
Object.entries(middlewareHeaders).forEach(([key, value]) => {
5867
if (value) {
59-
eventHeaders[keyPrefix + key] = Array.isArray(value)
60-
? value.join(",")
61-
: value;
68+
headers[keyPrefix + key] = Array.isArray(value) ? value.join(",") : value;
6269
}
6370
});
6471
}
6572

6673
export default async function routingHandler(
6774
event: InternalEvent,
68-
{ assetResolver }: { assetResolver?: AssetResolver } = {},
75+
{ assetResolver }: { assetResolver?: AssetResolver },
6976
): Promise<InternalResult | RoutingResult> {
7077
try {
7178
// Add Next geo headers
@@ -89,16 +96,17 @@ export default async function routingHandler(
8996
}
9097
}
9198

92-
const nextHeaders = getNextConfigHeaders(event, ConfigHeaders);
99+
// Headers from the Next config and middleware (the latest are applied further down).
100+
let headers: Record<string, string | string[] | undefined> =
101+
getNextConfigHeaders(event, ConfigHeaders);
93102

94-
const internalEventOrResult = fixDataPage(event, BuildId);
95-
if ("statusCode" in internalEventOrResult) {
96-
return internalEventOrResult;
97-
}
103+
let eventOrResult = fixDataPage(event, BuildId);
98104

99-
let internalEvent: InternalEvent | InternalResult = internalEventOrResult;
105+
if (isInternalResult(eventOrResult)) {
106+
return eventOrResult;
107+
}
100108

101-
const redirect = handleRedirects(internalEvent, RoutesManifest.redirects);
109+
const redirect = handleRedirects(eventOrResult, RoutesManifest.redirects);
102110
if (redirect) {
103111
// We need to encode the value in the Location header to make sure it is valid according to RFC
104112
// https://stackoverflow.com/a/7654605/16587222
@@ -110,68 +118,89 @@ export default async function routingHandler(
110118
}
111119

112120
const middlewareEventOrResult = await handleMiddleware(
113-
internalEvent,
121+
eventOrResult,
114122
// We need to pass the initial search without any decoding
115123
// TODO: we'd need to refactor InternalEvent to include the initial querystring directly
116124
// Should be done in another PR because it is a breaking change
117125
new URL(event.url).search,
118126
);
119-
if ("statusCode" in middlewareEventOrResult) {
127+
if (isInternalResult(middlewareEventOrResult)) {
120128
return middlewareEventOrResult;
121129
}
122130

123-
const middlewareResponseHeaders = middlewareEventOrResult.responseHeaders;
131+
headers = {
132+
...middlewareEventOrResult.responseHeaders,
133+
...headers,
134+
};
124135
let isExternalRewrite = middlewareEventOrResult.isExternalRewrite ?? false;
125-
internalEvent = middlewareEventOrResult;
136+
eventOrResult = middlewareEventOrResult;
126137

127138
if (!isExternalRewrite) {
128139
// First rewrite to be applied
129-
const beforeRewrites = handleRewrites(
130-
internalEvent,
140+
const beforeRewrite = handleRewrites(
141+
eventOrResult,
131142
RoutesManifest.rewrites.beforeFiles,
132143
);
133-
internalEvent = beforeRewrites.internalEvent;
134-
isExternalRewrite = beforeRewrites.isExternalRewrite;
144+
eventOrResult = beforeRewrite.internalEvent;
145+
isExternalRewrite = beforeRewrite.isExternalRewrite;
146+
// Check for matching public files after `beforeFiles` rewrites
147+
if (!isExternalRewrite) {
148+
const assetResult =
149+
await assetResolver?.getMaybeAssetResult?.(eventOrResult);
150+
if (assetResult) {
151+
applyMiddlewareHeaders(assetResult, headers);
152+
return assetResult;
153+
}
154+
}
135155
}
136-
const foundStaticRoute = staticRouteMatcher(internalEvent.rawPath);
156+
const foundStaticRoute = staticRouteMatcher(eventOrResult.rawPath);
137157
const isStaticRoute = !isExternalRewrite && foundStaticRoute.length > 0;
138158

139159
if (!(isStaticRoute || isExternalRewrite)) {
140160
// Second rewrite to be applied
141-
const afterRewrites = handleRewrites(
142-
internalEvent,
161+
const afterRewrite = handleRewrites(
162+
eventOrResult,
143163
RoutesManifest.rewrites.afterFiles,
144164
);
145-
internalEvent = afterRewrites.internalEvent;
146-
isExternalRewrite = afterRewrites.isExternalRewrite;
165+
eventOrResult = afterRewrite.internalEvent;
166+
isExternalRewrite = afterRewrite.isExternalRewrite;
167+
// Check for matching public files after `afterFiles` rewrites
168+
if (!isExternalRewrite) {
169+
const assetResult =
170+
await assetResolver?.getMaybeAssetResult?.(eventOrResult);
171+
if (assetResult) {
172+
applyMiddlewareHeaders(assetResult, headers);
173+
return assetResult;
174+
}
175+
}
147176
}
148177

149178
let isISR = false;
150179
// We want to run this just before the dynamic route check
151180
// We can skip it if its an external rewrite
152181
if (!isExternalRewrite) {
153182
const fallbackResult = handleFallbackFalse(
154-
internalEvent,
183+
eventOrResult,
155184
PrerenderManifest,
156185
);
157-
internalEvent = fallbackResult.event;
186+
eventOrResult = fallbackResult.event;
158187
isISR = fallbackResult.isISR;
159188
}
160189

161-
const foundDynamicRoute = dynamicRouteMatcher(internalEvent.rawPath);
190+
const foundDynamicRoute = dynamicRouteMatcher(eventOrResult.rawPath);
162191
const isDynamicRoute = !isExternalRewrite && foundDynamicRoute.length > 0;
163192

164193
if (!(isDynamicRoute || isStaticRoute || isExternalRewrite)) {
165194
// Fallback rewrite to be applied
166195
const fallbackRewrites = handleRewrites(
167-
internalEvent,
196+
eventOrResult,
168197
RoutesManifest.rewrites.fallback,
169198
);
170-
internalEvent = fallbackRewrites.internalEvent;
199+
eventOrResult = fallbackRewrites.internalEvent;
171200
isExternalRewrite = fallbackRewrites.isExternalRewrite;
172201
}
173202

174-
const isNextImageRoute = internalEvent.rawPath.startsWith("/_next/image");
203+
const isNextImageRoute = eventOrResult.rawPath.startsWith("/_next/image");
175204

176205
const isRouteFoundBeforeAllRewrites =
177206
isStaticRoute || isDynamicRoute || isExternalRewrite;
@@ -183,31 +212,24 @@ export default async function routingHandler(
183212
isRouteFoundBeforeAllRewrites ||
184213
isNextImageRoute ||
185214
// We need to check again once all rewrites have been applied
186-
staticRouteMatcher(internalEvent.rawPath).length > 0 ||
187-
dynamicRouteMatcher(internalEvent.rawPath).length > 0
215+
staticRouteMatcher(eventOrResult.rawPath).length > 0 ||
216+
dynamicRouteMatcher(eventOrResult.rawPath).length > 0
188217
)
189218
) {
190-
const assetEventOrResult =
191-
await assetResolver?.onRouteNotFound(internalEvent);
192-
193-
if (assetEventOrResult && "statusCode" in assetEventOrResult) {
194-
applyMiddlewareHeaders(
195-
assetEventOrResult.headers,
196-
{
197-
...middlewareResponseHeaders,
198-
...nextHeaders,
199-
},
200-
false,
201-
);
202-
return assetEventOrResult;
219+
// Check for matching public file when no routes are matched
220+
const assetResult =
221+
await assetResolver?.getMaybeAssetResult?.(eventOrResult);
222+
if (assetResult) {
223+
applyMiddlewareHeaders(assetResult, headers);
224+
return assetResult;
203225
}
204226

205-
internalEvent = assetEventOrResult ?? {
206-
...internalEvent,
227+
eventOrResult = {
228+
...eventOrResult,
207229
rawPath: "/404",
208-
url: constructNextUrl(internalEvent.url, "/404"),
230+
url: constructNextUrl(eventOrResult.url, "/404"),
209231
headers: {
210-
...internalEvent.headers,
232+
...eventOrResult.headers,
211233
"x-middleware-response-cache-control":
212234
"private, no-cache, no-store, max-age=0, must-revalidate",
213235
},
@@ -216,28 +238,27 @@ export default async function routingHandler(
216238

217239
if (
218240
globalThis.openNextConfig.dangerous?.enableCacheInterception &&
219-
!("statusCode" in internalEvent)
241+
!isInternalResult(eventOrResult)
220242
) {
221243
debug("Cache interception enabled");
222-
internalEvent = await cacheInterceptor(internalEvent);
223-
if ("statusCode" in internalEvent) {
224-
applyMiddlewareHeaders(
225-
internalEvent.headers,
226-
{
227-
...middlewareResponseHeaders,
228-
...nextHeaders,
229-
},
230-
false,
231-
);
232-
return internalEvent;
244+
eventOrResult = await cacheInterceptor(eventOrResult);
245+
if (isInternalResult(eventOrResult)) {
246+
applyMiddlewareHeaders(eventOrResult, headers);
247+
return eventOrResult;
233248
}
234249
}
235250

251+
// Check for matching public file when some routes are matched
252+
// An asset could override a dynamic route.
253+
const assetResult =
254+
await assetResolver?.getMaybeAssetResult?.(eventOrResult);
255+
if (assetResult) {
256+
applyMiddlewareHeaders(assetResult, headers);
257+
return assetResult;
258+
}
259+
236260
// We apply the headers from the middleware response last
237-
applyMiddlewareHeaders(internalEvent.headers, {
238-
...middlewareResponseHeaders,
239-
...nextHeaders,
240-
});
261+
applyMiddlewareHeaders(eventOrResult, headers);
241262

242263
const resolvedRoutes: ResolvedRoute[] = [
243264
...foundStaticRoute,
@@ -247,14 +268,14 @@ export default async function routingHandler(
247268
debug("resolvedRoutes", resolvedRoutes);
248269

249270
return {
250-
internalEvent,
271+
internalEvent: eventOrResult,
251272
isExternalRewrite,
252273
origin: false,
253274
isISR,
254275
resolvedRoutes,
255276
initialURL: event.url,
256277
locale: NextConfig.i18n
257-
? detectLocale(internalEvent, NextConfig.i18n)
278+
? detectLocale(eventOrResult, NextConfig.i18n)
258279
: undefined,
259280
};
260281
} catch (e) {
@@ -284,3 +305,13 @@ export default async function routingHandler(
284305
};
285306
}
286307
}
308+
309+
/**
310+
* @param eventOrResult
311+
* @returns Whether the event is an instance of `InternalResult`
312+
*/
313+
function isInternalResult(
314+
eventOrResult: InternalEvent | InternalResult,
315+
): eventOrResult is InternalResult {
316+
return eventOrResult != null && "statusCode" in eventOrResult;
317+
}
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import type { InternalEvent } from "types/open-next";
21
import type { AssetResolver } from "types/overrides";
32

3+
/**
4+
* A dummy asset resolver.
5+
*
6+
* It never overrides the result with an asset.
7+
*/
48
const resolver: AssetResolver = {
59
name: "dummy",
6-
// @returns `undefined` to preserve the routing layer default behavior (404 page)
7-
onRouteNotFound: (_event: InternalEvent) => undefined,
810
};
911

1012
export default resolver;

packages/open-next/src/types/global.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { AsyncLocalStorage } from "node:async_hooks";
22
import type { OutgoingHttpHeaders } from "node:http";
33

44
import type {
5+
AssetResolver,
56
CDNInvalidationHandler,
67
IncrementalCache,
78
ProxyExternalRequest,
@@ -214,6 +215,13 @@ declare global {
214215
*/
215216
var cdnInvalidationHandler: CDNInvalidationHandler;
216217

218+
/**
219+
* The function called to resolve assets.
220+
* Available in main functions
221+
* Defined in `createMainHandler` when the middleware is internal
222+
*/
223+
var assetResolver: AssetResolver | undefined;
224+
217225
/**
218226
* A function to preload the routes.
219227
* This needs to be defined on globalThis because it can be used by custom overrides.

0 commit comments

Comments
 (0)