Skip to content

Commit 60848d9

Browse files
authored
Add an asset resolver override (#917)
1 parent f2c86ca commit 60848d9

File tree

11 files changed

+177
-72
lines changed

11 files changed

+177
-72
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/adapters/middleware.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type { OpenNextHandlerOptions } from "types/overrides";
99
import { debug, error } from "../adapters/logger";
1010
import { createGenericHandler } from "../core/createGenericHandler";
1111
import {
12+
resolveAssetResolver,
1213
resolveIncrementalCache,
1314
resolveOriginResolver,
1415
resolveProxyRequest,
@@ -29,25 +30,22 @@ const defaultHandler = async (
2930
internalEvent: InternalEvent,
3031
options?: OpenNextHandlerOptions,
3132
): Promise<InternalResult | MiddlewareResult> => {
32-
const originResolver = await resolveOriginResolver(
33-
globalThis.openNextConfig.middleware?.originResolver,
34-
);
33+
const config = globalThis.openNextConfig.middleware;
34+
const originResolver = await resolveOriginResolver(config?.originResolver);
3535

3636
const externalRequestProxy = await resolveProxyRequest(
37-
globalThis.openNextConfig.middleware?.override?.proxyExternalRequest,
37+
config?.override?.proxyExternalRequest,
3838
);
3939

40+
const assetResolver = await resolveAssetResolver(config?.assetResolver);
41+
4042
//#override includeCacheInMiddleware
41-
globalThis.tagCache = await resolveTagCache(
42-
globalThis.openNextConfig.middleware?.override?.tagCache,
43-
);
43+
globalThis.tagCache = await resolveTagCache(config?.override?.tagCache);
4444

45-
globalThis.queue = await resolveQueue(
46-
globalThis.openNextConfig.middleware?.override?.queue,
47-
);
45+
globalThis.queue = await resolveQueue(config?.override?.queue);
4846

4947
globalThis.incrementalCache = await resolveIncrementalCache(
50-
globalThis.openNextConfig.middleware?.override?.incrementalCache,
48+
config?.override?.incrementalCache,
5149
);
5250
//#endOverride
5351

@@ -61,7 +59,7 @@ const defaultHandler = async (
6159
requestId,
6260
},
6361
async () => {
64-
const result = await routingHandler(internalEvent);
62+
const result = await routingHandler(internalEvent, { assetResolver });
6563
if ("internalEvent" in result) {
6664
debug("Middleware intercepted event", internalEvent);
6765
if (!result.isExternalRewrite) {

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/resolve.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,20 @@ export async function resolveOriginResolver(
116116
return m_1.default;
117117
}
118118

119+
/**
120+
* @returns
121+
* @__PURE__
122+
*/
123+
export async function resolveAssetResolver(
124+
assetResolver: RemoveUndefined<OpenNextConfig["middleware"]>["assetResolver"],
125+
) {
126+
if (typeof assetResolver === "function") {
127+
return assetResolver();
128+
}
129+
const m_1 = await import("../overrides/assetResolver/dummy.js");
130+
return m_1.default;
131+
}
132+
119133
/**
120134
* @__PURE__
121135
*/

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: 85 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type {
1212
RoutingResult,
1313
} from "types/open-next";
1414

15+
import type { AssetResolver } from "types/overrides";
1516
import { debug, error } from "../adapters/logger";
1617
import { cacheInterceptor } from "./routing/cacheInterceptor";
1718
import { detectLocale } from "./routing/i18n";
@@ -47,23 +48,31 @@ const geoHeaderToNextHeader = {
4748
"x-open-next-longitude": "x-vercel-ip-longitude",
4849
};
4950

51+
/**
52+
* Adds the middleware headers to an event or result.
53+
*
54+
* @param eventOrResult
55+
* @param middlewareHeaders
56+
*/
5057
function applyMiddlewareHeaders(
51-
eventHeaders: Record<string, string | string[]>,
58+
eventOrResult: InternalEvent | InternalResult,
5259
middlewareHeaders: Record<string, string | string[] | undefined>,
53-
setPrefix = true,
5460
) {
55-
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;
5666
Object.entries(middlewareHeaders).forEach(([key, value]) => {
5767
if (value) {
58-
eventHeaders[keyPrefix + key] = Array.isArray(value)
59-
? value.join(",")
60-
: value;
68+
headers[keyPrefix + key] = Array.isArray(value) ? value.join(",") : value;
6169
}
6270
});
6371
}
6472

6573
export default async function routingHandler(
6674
event: InternalEvent,
75+
{ assetResolver }: { assetResolver?: AssetResolver },
6776
): Promise<InternalResult | RoutingResult> {
6877
try {
6978
// Add Next geo headers
@@ -87,14 +96,17 @@ export default async function routingHandler(
8796
}
8897
}
8998

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

92-
let internalEvent = fixDataPage(event, BuildId);
93-
if ("statusCode" in internalEvent) {
94-
return internalEvent;
103+
let eventOrResult = fixDataPage(event, BuildId);
104+
105+
if (isInternalResult(eventOrResult)) {
106+
return eventOrResult;
95107
}
96108

97-
const redirect = handleRedirects(internalEvent, RoutesManifest.redirects);
109+
const redirect = handleRedirects(eventOrResult, RoutesManifest.redirects);
98110
if (redirect) {
99111
// We need to encode the value in the Location header to make sure it is valid according to RFC
100112
// https://stackoverflow.com/a/7654605/16587222
@@ -105,70 +117,84 @@ export default async function routingHandler(
105117
return redirect;
106118
}
107119

108-
const eventOrResult = await handleMiddleware(
109-
internalEvent,
120+
const middlewareEventOrResult = await handleMiddleware(
121+
eventOrResult,
110122
// We need to pass the initial search without any decoding
111123
// TODO: we'd need to refactor InternalEvent to include the initial querystring directly
112124
// Should be done in another PR because it is a breaking change
113125
new URL(event.url).search,
114126
);
115-
const isResult = "statusCode" in eventOrResult;
116-
if (isResult) {
117-
return eventOrResult;
127+
if (isInternalResult(middlewareEventOrResult)) {
128+
return middlewareEventOrResult;
118129
}
119-
const middlewareResponseHeaders = eventOrResult.responseHeaders;
120-
let isExternalRewrite = eventOrResult.isExternalRewrite ?? false;
121-
// internalEvent is `InternalEvent | MiddlewareEvent`
122-
internalEvent = eventOrResult;
130+
131+
headers = {
132+
...middlewareEventOrResult.responseHeaders,
133+
...headers,
134+
};
135+
let isExternalRewrite = middlewareEventOrResult.isExternalRewrite ?? false;
136+
eventOrResult = middlewareEventOrResult;
123137

124138
if (!isExternalRewrite) {
125139
// First rewrite to be applied
126-
const beforeRewrites = handleRewrites(
127-
internalEvent,
140+
const beforeRewrite = handleRewrites(
141+
eventOrResult,
128142
RoutesManifest.rewrites.beforeFiles,
129143
);
130-
internalEvent = beforeRewrites.internalEvent;
131-
isExternalRewrite = beforeRewrites.isExternalRewrite;
144+
eventOrResult = beforeRewrite.internalEvent;
145+
isExternalRewrite = beforeRewrite.isExternalRewrite;
146+
// Check for matching public files after `beforeFiles` rewrites
147+
// See:
148+
// - https://nextjs.org/docs/app/api-reference/file-conventions/middleware#execution-order
149+
// - https://nextjs.org/docs/app/api-reference/config/next-config-js/rewrites
150+
if (!isExternalRewrite) {
151+
const assetResult =
152+
await assetResolver?.maybeGetAssetResult?.(eventOrResult);
153+
if (assetResult) {
154+
applyMiddlewareHeaders(assetResult, headers);
155+
return assetResult;
156+
}
157+
}
132158
}
133-
const foundStaticRoute = staticRouteMatcher(internalEvent.rawPath);
159+
const foundStaticRoute = staticRouteMatcher(eventOrResult.rawPath);
134160
const isStaticRoute = !isExternalRewrite && foundStaticRoute.length > 0;
135161

136162
if (!(isStaticRoute || isExternalRewrite)) {
137163
// Second rewrite to be applied
138-
const afterRewrites = handleRewrites(
139-
internalEvent,
164+
const afterRewrite = handleRewrites(
165+
eventOrResult,
140166
RoutesManifest.rewrites.afterFiles,
141167
);
142-
internalEvent = afterRewrites.internalEvent;
143-
isExternalRewrite = afterRewrites.isExternalRewrite;
168+
eventOrResult = afterRewrite.internalEvent;
169+
isExternalRewrite = afterRewrite.isExternalRewrite;
144170
}
145171

146172
let isISR = false;
147173
// We want to run this just before the dynamic route check
148174
// We can skip it if its an external rewrite
149175
if (!isExternalRewrite) {
150176
const fallbackResult = handleFallbackFalse(
151-
internalEvent,
177+
eventOrResult,
152178
PrerenderManifest,
153179
);
154-
internalEvent = fallbackResult.event;
180+
eventOrResult = fallbackResult.event;
155181
isISR = fallbackResult.isISR;
156182
}
157183

158-
const foundDynamicRoute = dynamicRouteMatcher(internalEvent.rawPath);
184+
const foundDynamicRoute = dynamicRouteMatcher(eventOrResult.rawPath);
159185
const isDynamicRoute = !isExternalRewrite && foundDynamicRoute.length > 0;
160186

161187
if (!(isDynamicRoute || isStaticRoute || isExternalRewrite)) {
162188
// Fallback rewrite to be applied
163189
const fallbackRewrites = handleRewrites(
164-
internalEvent,
190+
eventOrResult,
165191
RoutesManifest.rewrites.fallback,
166192
);
167-
internalEvent = fallbackRewrites.internalEvent;
193+
eventOrResult = fallbackRewrites.internalEvent;
168194
isExternalRewrite = fallbackRewrites.isExternalRewrite;
169195
}
170196

171-
const isNextImageRoute = internalEvent.rawPath.startsWith("/_next/image");
197+
const isNextImageRoute = eventOrResult.rawPath.startsWith("/_next/image");
172198

173199
const isRouteFoundBeforeAllRewrites =
174200
isStaticRoute || isDynamicRoute || isExternalRewrite;
@@ -180,16 +206,16 @@ export default async function routingHandler(
180206
isRouteFoundBeforeAllRewrites ||
181207
isNextImageRoute ||
182208
// We need to check again once all rewrites have been applied
183-
staticRouteMatcher(internalEvent.rawPath).length > 0 ||
184-
dynamicRouteMatcher(internalEvent.rawPath).length > 0
209+
staticRouteMatcher(eventOrResult.rawPath).length > 0 ||
210+
dynamicRouteMatcher(eventOrResult.rawPath).length > 0
185211
)
186212
) {
187-
internalEvent = {
188-
...internalEvent,
213+
eventOrResult = {
214+
...eventOrResult,
189215
rawPath: "/404",
190-
url: constructNextUrl(internalEvent.url, "/404"),
216+
url: constructNextUrl(eventOrResult.url, "/404"),
191217
headers: {
192-
...internalEvent.headers,
218+
...eventOrResult.headers,
193219
"x-middleware-response-cache-control":
194220
"private, no-cache, no-store, max-age=0, must-revalidate",
195221
},
@@ -198,28 +224,18 @@ export default async function routingHandler(
198224

199225
if (
200226
globalThis.openNextConfig.dangerous?.enableCacheInterception &&
201-
!("statusCode" in internalEvent)
227+
!isInternalResult(eventOrResult)
202228
) {
203229
debug("Cache interception enabled");
204-
internalEvent = await cacheInterceptor(internalEvent);
205-
if ("statusCode" in internalEvent) {
206-
applyMiddlewareHeaders(
207-
internalEvent.headers,
208-
{
209-
...middlewareResponseHeaders,
210-
...nextHeaders,
211-
},
212-
false,
213-
);
214-
return internalEvent;
230+
eventOrResult = await cacheInterceptor(eventOrResult);
231+
if (isInternalResult(eventOrResult)) {
232+
applyMiddlewareHeaders(eventOrResult, headers);
233+
return eventOrResult;
215234
}
216235
}
217236

218237
// We apply the headers from the middleware response last
219-
applyMiddlewareHeaders(internalEvent.headers, {
220-
...middlewareResponseHeaders,
221-
...nextHeaders,
222-
});
238+
applyMiddlewareHeaders(eventOrResult, headers);
223239

224240
const resolvedRoutes: ResolvedRoute[] = [
225241
...foundStaticRoute,
@@ -229,14 +245,14 @@ export default async function routingHandler(
229245
debug("resolvedRoutes", resolvedRoutes);
230246

231247
return {
232-
internalEvent,
248+
internalEvent: eventOrResult,
233249
isExternalRewrite,
234250
origin: false,
235251
isISR,
236252
resolvedRoutes,
237253
initialURL: event.url,
238254
locale: NextConfig.i18n
239-
? detectLocale(internalEvent, NextConfig.i18n)
255+
? detectLocale(eventOrResult, NextConfig.i18n)
240256
: undefined,
241257
};
242258
} catch (e) {
@@ -266,3 +282,13 @@ export default async function routingHandler(
266282
};
267283
}
268284
}
285+
286+
/**
287+
* @param eventOrResult
288+
* @returns Whether the event is an instance of `InternalResult`
289+
*/
290+
function isInternalResult(
291+
eventOrResult: InternalEvent | InternalResult,
292+
): eventOrResult is InternalResult {
293+
return eventOrResult != null && "statusCode" in eventOrResult;
294+
}

0 commit comments

Comments
 (0)