Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/soft-toys-crash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@opennextjs/aws": patch
---

return the user 500 in case of middleware error
3 changes: 3 additions & 0 deletions examples/pages-router/src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";

export function middleware(request: NextRequest) {
if (request.headers.get("x-throw")) {
throw new Error("Middleware error");
}
return NextResponse.next({
headers: {
"x-from-middleware": "true",
Expand Down
8 changes: 2 additions & 6 deletions packages/open-next/src/core/requestHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { runWithOpenNextRequestContext } from "utils/promise";

import { NextConfig } from "config/index";
import type { OpenNextHandlerOptions } from "types/overrides";
import { debug, error, warn } from "../adapters/logger";
import { debug, error } from "../adapters/logger";
import { patchAsyncStorage } from "./patchAsyncStorage";
import {
constructNextUrl,
Expand Down Expand Up @@ -71,11 +71,7 @@ export async function openNextHandler(
};

//#override withRouting
try {
routingResult = await routingHandler(internalEvent);
} catch (e) {
warn("Routing failed.", e);
}
routingResult = await routingHandler(internalEvent);
//#endOverride

const headers =
Expand Down
321 changes: 174 additions & 147 deletions packages/open-next/src/core/routingHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type {
RoutingResult,
} from "types/open-next";

import { debug } from "../adapters/logger";
import { debug, error } from "../adapters/logger";
import { cacheInterceptor } from "./routing/cacheInterceptor";
import { detectLocale } from "./routing/i18n";
import {
Expand Down Expand Up @@ -65,169 +65,196 @@ function applyMiddlewareHeaders(
export default async function routingHandler(
event: InternalEvent,
): Promise<InternalResult | RoutingResult> {
// Add Next geo headers
for (const [openNextGeoName, nextGeoName] of Object.entries(
geoHeaderToNextHeader,
)) {
const value = event.headers[openNextGeoName];
if (value) {
event.headers[nextGeoName] = value;
try {
// Add Next geo headers
for (const [openNextGeoName, nextGeoName] of Object.entries(
geoHeaderToNextHeader,
)) {
const value = event.headers[openNextGeoName];
if (value) {
event.headers[nextGeoName] = value;
}
}
}

// First we remove internal headers
// We don't want to allow users to set these headers
for (const key of Object.keys(event.headers)) {
if (
key.startsWith(INTERNAL_HEADER_PREFIX) ||
key.startsWith(MIDDLEWARE_HEADER_PREFIX)
) {
delete event.headers[key];
// First we remove internal headers
// We don't want to allow users to set these headers
for (const key of Object.keys(event.headers)) {
if (
key.startsWith(INTERNAL_HEADER_PREFIX) ||
key.startsWith(MIDDLEWARE_HEADER_PREFIX)
) {
delete event.headers[key];
}
}
}

const nextHeaders = getNextConfigHeaders(event, ConfigHeaders);
const nextHeaders = getNextConfigHeaders(event, ConfigHeaders);

let internalEvent = fixDataPage(event, BuildId);
if ("statusCode" in internalEvent) {
return internalEvent;
}
let internalEvent = fixDataPage(event, BuildId);
if ("statusCode" in internalEvent) {
return internalEvent;
}

const redirect = handleRedirects(internalEvent, RoutesManifest.redirects);
if (redirect) {
debug("redirect", redirect);
return redirect;
}
const redirect = handleRedirects(internalEvent, RoutesManifest.redirects);
if (redirect) {
debug("redirect", redirect);
return redirect;
}

const eventOrResult = await handleMiddleware(internalEvent);
const isResult = "statusCode" in eventOrResult;
if (isResult) {
return eventOrResult;
}
const middlewareResponseHeaders = eventOrResult.responseHeaders;
let isExternalRewrite = eventOrResult.isExternalRewrite ?? false;
// internalEvent is `InternalEvent | MiddlewareEvent`
internalEvent = eventOrResult;

if (!isExternalRewrite) {
// First rewrite to be applied
const beforeRewrites = handleRewrites(
internalEvent,
RoutesManifest.rewrites.beforeFiles,
);
internalEvent = beforeRewrites.internalEvent;
isExternalRewrite = beforeRewrites.isExternalRewrite;
}
const foundStaticRoute = staticRouteMatcher(internalEvent.rawPath);
const isStaticRoute = !isExternalRewrite && foundStaticRoute.length > 0;
const eventOrResult = await handleMiddleware(internalEvent);
const isResult = "statusCode" in eventOrResult;
if (isResult) {
return eventOrResult;
}
const middlewareResponseHeaders = eventOrResult.responseHeaders;
let isExternalRewrite = eventOrResult.isExternalRewrite ?? false;
// internalEvent is `InternalEvent | MiddlewareEvent`
internalEvent = eventOrResult;

if (!(isStaticRoute || isExternalRewrite)) {
// Second rewrite to be applied
const afterRewrites = handleRewrites(
if (!isExternalRewrite) {
// First rewrite to be applied
const beforeRewrites = handleRewrites(
internalEvent,
RoutesManifest.rewrites.beforeFiles,
);
internalEvent = beforeRewrites.internalEvent;
isExternalRewrite = beforeRewrites.isExternalRewrite;
}
const foundStaticRoute = staticRouteMatcher(internalEvent.rawPath);
const isStaticRoute = !isExternalRewrite && foundStaticRoute.length > 0;

if (!(isStaticRoute || isExternalRewrite)) {
// Second rewrite to be applied
const afterRewrites = handleRewrites(
internalEvent,
RoutesManifest.rewrites.afterFiles,
);
internalEvent = afterRewrites.internalEvent;
isExternalRewrite = afterRewrites.isExternalRewrite;
}

// We want to run this just before the dynamic route check
const { event: fallbackEvent, isISR } = handleFallbackFalse(
internalEvent,
RoutesManifest.rewrites.afterFiles,
PrerenderManifest,
);
internalEvent = afterRewrites.internalEvent;
isExternalRewrite = afterRewrites.isExternalRewrite;
}
internalEvent = fallbackEvent;

// We want to run this just before the dynamic route check
const { event: fallbackEvent, isISR } = handleFallbackFalse(
internalEvent,
PrerenderManifest,
);
internalEvent = fallbackEvent;
const foundDynamicRoute = dynamicRouteMatcher(internalEvent.rawPath);
const isDynamicRoute = !isExternalRewrite && foundDynamicRoute.length > 0;

const foundDynamicRoute = dynamicRouteMatcher(internalEvent.rawPath);
const isDynamicRoute = !isExternalRewrite && foundDynamicRoute.length > 0;
if (!(isDynamicRoute || isStaticRoute || isExternalRewrite)) {
// Fallback rewrite to be applied
const fallbackRewrites = handleRewrites(
internalEvent,
RoutesManifest.rewrites.fallback,
);
internalEvent = fallbackRewrites.internalEvent;
isExternalRewrite = fallbackRewrites.isExternalRewrite;
}

if (!(isDynamicRoute || isStaticRoute || isExternalRewrite)) {
// Fallback rewrite to be applied
const fallbackRewrites = handleRewrites(
internalEvent,
RoutesManifest.rewrites.fallback,
);
internalEvent = fallbackRewrites.internalEvent;
isExternalRewrite = fallbackRewrites.isExternalRewrite;
}
// Api routes are not present in the routes manifest except if they're not behind /api
// /api even if it's a page route doesn't get generated in the manifest
// Ideally we would need to properly check api routes here
const isApiRoute =
internalEvent.rawPath === apiPrefix ||
internalEvent.rawPath.startsWith(`${apiPrefix}/`);

// Api routes are not present in the routes manifest except if they're not behind /api
// /api even if it's a page route doesn't get generated in the manifest
// Ideally we would need to properly check api routes here
const isApiRoute =
internalEvent.rawPath === apiPrefix ||
internalEvent.rawPath.startsWith(`${apiPrefix}/`);

const isNextImageRoute = internalEvent.rawPath.startsWith("/_next/image");

const isRouteFoundBeforeAllRewrites =
isStaticRoute || isDynamicRoute || isExternalRewrite;

// If we still haven't found a route, we show the 404 page
// We need to ensure that rewrites are applied before showing the 404 page
if (
!(
isRouteFoundBeforeAllRewrites ||
isApiRoute ||
isNextImageRoute ||
// We need to check again once all rewrites have been applied
staticRouteMatcher(internalEvent.rawPath).length > 0 ||
dynamicRouteMatcher(internalEvent.rawPath).length > 0
)
) {
internalEvent = {
...internalEvent,
rawPath: "/404",
url: constructNextUrl(internalEvent.url, "/404"),
headers: {
...internalEvent.headers,
"x-middleware-response-cache-control":
"private, no-cache, no-store, max-age=0, must-revalidate",
},
};
}
const isNextImageRoute = internalEvent.rawPath.startsWith("/_next/image");

if (
globalThis.openNextConfig.dangerous?.enableCacheInterception &&
!("statusCode" in internalEvent)
) {
debug("Cache interception enabled");
internalEvent = await cacheInterceptor(internalEvent);
if ("statusCode" in internalEvent) {
applyMiddlewareHeaders(
internalEvent.headers,
{
...middlewareResponseHeaders,
...nextHeaders,
const isRouteFoundBeforeAllRewrites =
isStaticRoute || isDynamicRoute || isExternalRewrite;

// If we still haven't found a route, we show the 404 page
// We need to ensure that rewrites are applied before showing the 404 page
if (
!(
isRouteFoundBeforeAllRewrites ||
isApiRoute ||
isNextImageRoute ||
// We need to check again once all rewrites have been applied
staticRouteMatcher(internalEvent.rawPath).length > 0 ||
dynamicRouteMatcher(internalEvent.rawPath).length > 0
)
) {
internalEvent = {
...internalEvent,
rawPath: "/404",
url: constructNextUrl(internalEvent.url, "/404"),
headers: {
...internalEvent.headers,
"x-middleware-response-cache-control":
"private, no-cache, no-store, max-age=0, must-revalidate",
},
false,
);
return internalEvent;
};
}
}

// We apply the headers from the middleware response last
applyMiddlewareHeaders(internalEvent.headers, {
...middlewareResponseHeaders,
...nextHeaders,
});
if (
globalThis.openNextConfig.dangerous?.enableCacheInterception &&
!("statusCode" in internalEvent)
) {
debug("Cache interception enabled");
internalEvent = await cacheInterceptor(internalEvent);
if ("statusCode" in internalEvent) {
applyMiddlewareHeaders(
internalEvent.headers,
{
...middlewareResponseHeaders,
...nextHeaders,
},
false,
);
return internalEvent;
}
}

// We apply the headers from the middleware response last
applyMiddlewareHeaders(internalEvent.headers, {
...middlewareResponseHeaders,
...nextHeaders,
});

const resolvedRoutes: ResolvedRoute[] = [
...foundStaticRoute,
...foundDynamicRoute,
];

debug("resolvedRoutes", resolvedRoutes);

const resolvedRoutes: ResolvedRoute[] = [
...foundStaticRoute,
...foundDynamicRoute,
];

debug("resolvedRoutes", resolvedRoutes);

return {
internalEvent,
isExternalRewrite,
origin: false,
isISR,
resolvedRoutes,
initialURL: event.url,
locale: NextConfig.i18n
? detectLocale(internalEvent, NextConfig.i18n)
: undefined,
};
return {
internalEvent,
isExternalRewrite,
origin: false,
isISR,
resolvedRoutes,
initialURL: event.url,
locale: NextConfig.i18n
? detectLocale(internalEvent, NextConfig.i18n)
: undefined,
};
} catch (e) {
error("Error in routingHandler", e);
// In case of an error, we want to return the 500 page from Next.js
return {
internalEvent: {
type: "core",
method: "GET",
rawPath: "/500",
url: constructNextUrl(event.url, "/500"),
headers: {
...event.headers,
},
query: event.query,
cookies: event.cookies,
remoteAddress: event.remoteAddress,
},
isExternalRewrite: false,
origin: false,
isISR: false,
resolvedRoutes: [],
initialURL: event.url,
locale: NextConfig.i18n
? detectLocale(event, NextConfig.i18n)
: undefined,
};
}
}
3 changes: 2 additions & 1 deletion packages/tests-e2e/tests/appRouter/isr.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ test.describe("dynamicParams set to true", () => {

// In `next start` this test would fail on subsequent requests because `x-nextjs-cache` would be `HIT`
// However, once deployed to AWS, Cloudfront will cache `MISS`
test("should SSR on a path that was not prebuilt", async ({ page }) => {
// We are gonna skip this one for now, turborepo caching can cause this page to be STALE once deployed
test.skip("should SSR on a path that was not prebuilt", async ({ page }) => {
const res = await page.goto("/isr/dynamic-params-true/11");
expect(res?.headers()["x-nextjs-cache"]).toEqual("MISS");
const title = await page.getByTestId("title").textContent();
Expand Down
Loading
Loading