Serve props JSON for /api data requests via middleware rewrite (#902)

* fix: serve props JSON for /api data requests via middleware rewrite

The /api/:path* -> /ipa/:path* rewrite in next.config.mjs is applied by
Vercel's routing to client-side props fetches
(/_next/data/<buildId>/api/....json), but the data-request context is
lost and the prerendered page HTML is returned instead of JSON
(vercel/next.js#39669). The router then never receives pageProps, so the
API sidebar stays collapsed and the tab title shows undefined until a
full reload. This half of the bug only occurs on Vercel infrastructure;
the dev server and next start resolve rewrites for data requests
correctly, and #900 fixed only the /ipa redirect half.

Middleware rewrites preserve data-request semantics, so rewrite /api/*
to /ipa/* in middleware for data requests only (x-nextjs-data header).
Regular page requests fall through to the existing config rewrites, and
the /ipa -> /api canonical redirect is unchanged.

* fix: move /api data-request rewrite into existing proxy.js

Next 16 uses proxy.js and rejects builds where both middleware.js and
proxy.js exist; this repo already had src/proxy.js for the
/docs-static/_next asset rewrite. Fold the /api -> /ipa data-request
rewrite into it and drop middleware.js. Verified the proxy intercepts:
data responses now carry x-middleware-rewrite: /ipa/... and JSON bodies.

* fix: map bare api.json data requests to ipa/introduction.json

The raw data-path fallback rewrote /_next/data/<build>/api.json to
/_next/data/<build>/ipa.json, but there is no /ipa index page; mirror
the /api -> /ipa/introduction rewrite instead.
This commit is contained in:
Jack Carter
2026-08-03 14:40:12 +02:00
committed by GitHub
parent e5da16cee6
commit c5e61c68bd

View File

@@ -1,10 +1,46 @@
import { NextResponse } from 'next/server';
// Client-side navigations fetch page props from /_next/data/<buildId>/api/....json.
// Vercel's routing applies the /api/:path* -> /ipa/:path* rewrite from
// next.config.mjs to those requests but loses the data-request context and
// serves the page HTML instead of the props JSON
// (https://github.com/vercel/next.js/issues/39669), so the router never
// receives pageProps. Proxy rewrites keep data-request semantics, so map
// /api/* to /ipa/* here for data requests only; regular page requests fall
// through to the config rewrites, and the /ipa -> /api canonical redirect
// is unchanged.
function rewriteApiDataRequest(req) {
const isDataRequest =
req.headers.get('x-nextjs-data') !== null ||
req.nextUrl.pathname.startsWith('/_next/data/');
if (!isDataRequest) return null;
const url = req.nextUrl.clone();
if (url.pathname.startsWith('/_next/data/')) {
// Raw data-request path, in case the runtime matches it un-normalized.
// Bare api.json maps to ipa/introduction.json to mirror the /api ->
// /ipa/introduction rewrite; there is no /ipa index page.
url.pathname = url.pathname
.replace(
/^(\/_next\/data\/[^/]+)\/api\.json$/,
'$1/ipa/introduction.json',
)
.replace(/^(\/_next\/data\/[^/]+)\/api\//, '$1/ipa/');
} else if (url.pathname === '/api') {
url.pathname = '/ipa/introduction';
} else if (url.pathname.startsWith('/api/')) {
url.pathname = url.pathname.replace(/^\/api\//, '/ipa/');
}
if (url.pathname === req.nextUrl.pathname) return null;
return NextResponse.rewrite(url);
}
export function proxy(req) {
if (req.nextUrl.href.includes('/docs-static/_next/'))
return NextResponse.rewrite(
req.nextUrl.href.replace('/docs-static/_next/', '/_next/'),
);
return null;
}
return rewriteApiDataRequest(req);
}