From c5e61c68bd9661159ef9902fa7f03aed8efd377d Mon Sep 17 00:00:00 2001 From: Jack Carter <128555021+SunsetDrifter@users.noreply.github.com> Date: Mon, 3 Aug 2026 14:40:12 +0200 Subject: [PATCH] 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//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//api.json to /_next/data//ipa.json, but there is no /ipa index page; mirror the /api -> /ipa/introduction rewrite instead. --- src/proxy.js | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/src/proxy.js b/src/proxy.js index e83805e3..eebb7926 100644 --- a/src/proxy.js +++ b/src/proxy.js @@ -1,10 +1,46 @@ import { NextResponse } from 'next/server'; +// Client-side navigations fetch page props from /_next/data//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; -} \ No newline at end of file + return rewriteApiDataRequest(req); +}