Skip to content

Commit a46d3fc

Browse files
authored
fix 404 when no route match at all (#426)
* fix 404 when no route match at all * fix some issue with rewrite not being checked for 404 verification added some test * Create tame-pets-juggle.md
1 parent bc26e9a commit a46d3fc

File tree

4 files changed

+79
-6
lines changed

4 files changed

+79
-6
lines changed

.changeset/tame-pets-juggle.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"open-next": patch
3+
---
4+
5+
fix 404 when no route match at all

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,12 @@ export function fixCacheHeaderForHtmlPages(
284284
rawPath: string,
285285
headers: OutgoingHttpHeaders,
286286
) {
287+
// We don't want to cache error pages
288+
if (rawPath === "/404" || rawPath === "/500") {
289+
headers[CommonHeaders.CACHE_CONTROL] =
290+
"private, no-cache, no-store, max-age=0, must-revalidate";
291+
return;
292+
}
287293
// WORKAROUND: `NextServer` does not set cache headers for HTML pages — https://github.com/serverless-stack/open-next#workaround-nextserver-does-not-set-cache-headers-for-html-pages
288294
if (HtmlPages.includes(rawPath)) {
289295
headers[CommonHeaders.CACHE_CONTROL] =

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

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ export interface MiddlewareOutputEvent {
2222
origin: Origin | false;
2323
}
2424

25+
const staticRegexp = RoutesManifest.routes.static.map(
26+
(route) => new RegExp(route.regex),
27+
);
28+
29+
const dynamicRegexp = RoutesManifest.routes.dynamic.map(
30+
(route) => new RegExp(route.regex),
31+
);
32+
2533
export default async function routingHandler(
2634
event: InternalEvent,
2735
): Promise<InternalResult | MiddlewareOutputEvent> {
@@ -47,6 +55,8 @@ export default async function routingHandler(
4755
internalEvent = middleware;
4856
}
4957

58+
// At this point internalEvent is an InternalEvent or a MiddlewareOutputEvent
59+
5060
let isExternalRewrite = middleware.externalRewrite ?? false;
5161
if (!isExternalRewrite) {
5262
// First rewrite to be applied
@@ -57,9 +67,11 @@ export default async function routingHandler(
5767
internalEvent = beforeRewrites.internalEvent;
5868
isExternalRewrite = beforeRewrites.isExternalRewrite;
5969
}
60-
const isStaticRoute = RoutesManifest.routes.static.some((route) =>
61-
new RegExp(route.regex).test(event.rawPath),
62-
);
70+
const isStaticRoute =
71+
!isExternalRewrite &&
72+
staticRegexp.some((route) =>
73+
route.test((internalEvent as InternalEvent).rawPath),
74+
);
6375

6476
if (!isStaticRoute && !isExternalRewrite) {
6577
// Second rewrite to be applied
@@ -74,9 +86,11 @@ export default async function routingHandler(
7486
// We want to run this just before the dynamic route check
7587
internalEvent = handleFallbackFalse(internalEvent, PrerenderManifest);
7688

77-
const isDynamicRoute = RoutesManifest.routes.dynamic.some((route) =>
78-
new RegExp(route.regex).test(event.rawPath),
79-
);
89+
const isDynamicRoute =
90+
!isExternalRewrite &&
91+
dynamicRegexp.some((route) =>
92+
route.test((internalEvent as InternalEvent).rawPath),
93+
);
8094
if (!isDynamicRoute && !isStaticRoute && !isExternalRewrite) {
8195
// Fallback rewrite to be applied
8296
const fallbackRewrites = handleRewrites(
@@ -87,6 +101,41 @@ export default async function routingHandler(
87101
isExternalRewrite = fallbackRewrites.isExternalRewrite;
88102
}
89103

104+
// Api routes are not present in the routes manifest except if they're not behind /api
105+
// /api even if it's a page route doesn't get generated in the manifest
106+
// Ideally we would need to properly check api routes here
107+
const isApiRoute =
108+
internalEvent.rawPath === "/api" ||
109+
internalEvent.rawPath.startsWith("/api/");
110+
111+
const isRouteFoundBeforeAllRewrites =
112+
isStaticRoute || isDynamicRoute || isExternalRewrite;
113+
114+
// If we still haven't found a route, we show the 404 page
115+
// We need to ensure that rewrites are applied before showing the 404 page
116+
if (
117+
!isRouteFoundBeforeAllRewrites &&
118+
!isApiRoute &&
119+
// We need to check again once all rewrites have been applied
120+
!staticRegexp.some((route) =>
121+
route.test((internalEvent as InternalEvent).rawPath),
122+
) &&
123+
!dynamicRegexp.some((route) =>
124+
route.test((internalEvent as InternalEvent).rawPath),
125+
)
126+
) {
127+
internalEvent = {
128+
...internalEvent,
129+
rawPath: "/404",
130+
url: "/404",
131+
headers: {
132+
...internalEvent.headers,
133+
"x-middleware-response-cache-control":
134+
"private, no-cache, no-store, max-age=0, must-revalidate",
135+
},
136+
};
137+
}
138+
90139
// We apply the headers from the middleware response last
91140
Object.entries({
92141
...middlewareResponseHeaders,
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { expect, test } from "@playwright/test";
2+
3+
test("should return 404 on a route not corresponding to any route", async ({
4+
page,
5+
}) => {
6+
const result = await page.goto("/not-existing/route");
7+
expect(result).toBeDefined();
8+
expect(result?.status()).toBe(404);
9+
const headers = result?.headers();
10+
expect(headers?.["cache-control"]).toBe(
11+
"private, no-cache, no-store, max-age=0, must-revalidate",
12+
);
13+
});

0 commit comments

Comments
 (0)