Skip to content

Commit 7f48e53

Browse files
committed
fix: populate static api routes for our staticRouteMatcher
1 parent ad701c2 commit 7f48e53

File tree

5 files changed

+54
-13
lines changed

5 files changed

+54
-13
lines changed

packages/open-next/src/adapters/config/index.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import path from "node:path";
22

3+
import type { RouteDefinition } from "types/next-types";
34
import { debug } from "../logger";
45
import {
56
loadAppPathRoutesManifest,
@@ -11,6 +12,7 @@ import {
1112
loadFunctionsConfigManifest,
1213
loadHtmlPages,
1314
loadMiddlewareManifest,
15+
loadPagesManifest,
1416
loadPrerenderManifest,
1517
loadRoutesManifest,
1618
} from "./util.js";
@@ -39,3 +41,38 @@ export const AppPathRoutesManifest =
3941

4042
export const FunctionsConfigManifest =
4143
/* @__PURE__ */ loadFunctionsConfigManifest(NEXT_DIR);
44+
45+
/**
46+
* Returns static API routes for both app and pages router cause Next will filter them out in staticRoutes in `routes-manifest.json`.
47+
* We also need to filter out page files that are under `app/api/*` as those would not be present in the routes manifest either.
48+
* This line from Next.js skips it:
49+
* https://github.com/vercel/next.js/blob/ded56f952154a40dcfe53bdb38c73174e9eca9e5/packages/next/src/build/index.ts#L1299
50+
*
51+
* Without it handleFallbackFalse will 404 on static API routes if there is a catch-all route on root level.
52+
*/
53+
export function getStaticAPIRoutes(): RouteDefinition[] {
54+
const createRouteDefinition = (route: string) => ({
55+
page: route,
56+
regex: `^${route}(?:/)?$`,
57+
});
58+
const dynamicRoutePages = new Set(
59+
RoutesManifest.routes.dynamic.map(({ page }) => page),
60+
);
61+
const PagesManifest = loadPagesManifest(NEXT_DIR);
62+
const pagesStaticAPIRoutes = Object.keys(PagesManifest)
63+
.filter(
64+
(route) => route.startsWith("/api/") && !dynamicRoutePages.has(route),
65+
)
66+
.map(createRouteDefinition);
67+
68+
// We filter out both static API and page routes from the app paths manifest
69+
const appPathsStaticAPIRoutes = Object.values(AppPathRoutesManifest)
70+
.filter(
71+
(route) =>
72+
route.startsWith("/api/") ||
73+
(route === "/api" && !dynamicRoutePages.has(route)),
74+
)
75+
.map(createRouteDefinition);
76+
77+
return [...pagesStaticAPIRoutes, ...appPathsStaticAPIRoutes];
78+
}

packages/open-next/src/adapters/config/util.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export function loadBuildId(nextDir: string) {
2424
export function loadPagesManifest(nextDir: string) {
2525
const filePath = path.join(nextDir, "server/pages-manifest.json");
2626
const json = fs.readFileSync(filePath, "utf-8");
27-
return JSON.parse(json);
27+
return JSON.parse(json) as Record<string, string>;
2828
}
2929

3030
export function loadHtmlPages(nextDir: string) {

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,12 @@ export function handleFallbackFalse(
437437
? rawPath
438438
: `/${NextConfig.i18n?.defaultLocale}${rawPath}`;
439439
// We need to remove the trailing slash if it exists
440-
if (NextConfig.trailingSlash && localizedPath.endsWith("/")) {
440+
// Not if localizedPath is "/" tho, because that would not make it find `isPregenerated` below since it would be try to match an empty string.
441+
if (
442+
localizedPath !== "/" &&
443+
NextConfig.trailingSlash &&
444+
localizedPath.endsWith("/")
445+
) {
441446
localizedPath = localizedPath.slice(0, -1);
442447
}
443448
const matchedStaticRoute = staticRouteMatcher(localizedPath);
@@ -447,6 +452,7 @@ export function handleFallbackFalse(
447452
const matchedDynamicRoute = dynamicRouteMatcher(localizedPath).filter(
448453
({ route }) => !prerenderedFallbackRoutesName.includes(route),
449454
);
455+
450456
const isPregenerated = Object.keys(routes).includes(localizedPath);
451457
if (
452458
routeFallback &&

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { AppPathRoutesManifest, RoutesManifest } from "config/index";
1+
import {
2+
AppPathRoutesManifest,
3+
RoutesManifest,
4+
getStaticAPIRoutes,
5+
} from "config/index";
26
import type { RouteDefinition } from "types/next-types";
37
import type { ResolvedRoute, RouteType } from "types/open-next";
48

@@ -53,5 +57,8 @@ function routeMatcher(routeDefinitions: RouteDefinition[]) {
5357
};
5458
}
5559

56-
export const staticRouteMatcher = routeMatcher(RoutesManifest.routes.static);
60+
export const staticRouteMatcher = routeMatcher([
61+
...RoutesManifest.routes.static,
62+
...getStaticAPIRoutes(),
63+
]);
5764
export const dynamicRouteMatcher = routeMatcher(RoutesManifest.routes.dynamic);

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

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import {
2424
} from "./routing/matcher";
2525
import { handleMiddleware } from "./routing/middleware";
2626
import {
27-
apiPrefix,
2827
dynamicRouteMatcher,
2928
staticRouteMatcher,
3029
} from "./routing/routeMatcher";
@@ -158,13 +157,6 @@ export default async function routingHandler(
158157
isExternalRewrite = fallbackRewrites.isExternalRewrite;
159158
}
160159

161-
// Api routes are not present in the routes manifest except if they're not behind /api
162-
// /api even if it's a page route doesn't get generated in the manifest
163-
// Ideally we would need to properly check api routes here
164-
const isApiRoute =
165-
internalEvent.rawPath === apiPrefix ||
166-
internalEvent.rawPath.startsWith(`${apiPrefix}/`);
167-
168160
const isNextImageRoute = internalEvent.rawPath.startsWith("/_next/image");
169161

170162
const isRouteFoundBeforeAllRewrites =
@@ -175,7 +167,6 @@ export default async function routingHandler(
175167
if (
176168
!(
177169
isRouteFoundBeforeAllRewrites ||
178-
isApiRoute ||
179170
isNextImageRoute ||
180171
// We need to check again once all rewrites have been applied
181172
staticRouteMatcher(internalEvent.rawPath).length > 0 ||

0 commit comments

Comments
 (0)