Pages Router Hydration Causes An Extra Client Render On Full Page Load When Any Rewrite Is Configured, Including Non-Rewritten Routes
When a Next.js Pages Router application defines any rewrite rule, production hydration triggers an additional client-side render for all routes, including those not affected by the rewrite. This is caused by a global flag that enables rewrite-aware reconciliation unconditionally, rather than only for requests whose route or query actually diverge due to a rewrite.
The Pages Router client bundle includes a global `hasRewrites` flag derived from the project's Next.js config. During hydration, a `useEffect` (or equivalent router lifecycle) checks this flag and, if true, unconditionally performs a post-hydration reconciliation of query and route state. Because the flag is global, every route—even `/` when only `/rewritten -> /target` is configured—triggers a state update on the Router context, causing the entire application tree to re-render. The reconciliation is only needed when the effective `asPath` differs from the resolved `route` or when rewrite-derived query parameters are present; for routes where both match, the extra render is redundant.
1. Clone https://github.com/gurkerl83/next-pages-router-global-rewrite-rerender-repro
2. Run `pnpm install && pnpm build && pnpm start`
3. Open `/` directly or reload `/` in production
4. Observe browser console: `_app` logs two client renders on `/`
5. Open `/rewritten` directly or reload `/rewritten`
6. Observe browser console: `_app` also logs two client renders on `/rewritten`
Fixing Code Block
// In packages/next/src/client/router.ts, within the RouterProvider useEffect that handles hydration reconciliation.
// Replace the global condition with a request-local check.
useEffect(() => {
if (window.__NEXT_DATA__.hasRewrites) {
const { route, asPath, query } = router
const requestedPath = window.location.pathname
const requestedQuery = Object.fromEntries(
new URLSearchParams(window.location.search)
)
// Only reconcile if the effective route differs from the real route or
// if query params not present in the server-provided query are found.
const needsRewriteReconciliation =
asPath !== route ||
Object.keys(requestedQuery).some((key) => !(key in query))
if (needsRewriteReconciliation) {
router.reconcile()
}
}
}, [router])
The fix replaces the unconditional `hasRewrites` check with a per-request comparison between the browser's actual path/query and the router's server-side route/query. Only when a mismatch is detected (indicating the request was rewritten) will the reconciliation state update fire. This preserves the necessary behavior for rewritten routes while eliminating the duplicate render for unaffected static routes.
Edge Case Audit
If the `reconcile()` method is not available in the installed Next.js version, this patch will fail. The condition uses `window.location` which may be undefined in non-browser environments; ensure it is guarded. Rollback: revert the changed `useEffect` to the original `if (hasRewrites)` condition and rebuild. This change only affects client-side reconciliation; it does not alter server-side rewrite behavior. Test thoroughly with query-parameter-heavy rewrites and locale-specific routes to avoid regression.