AI & Agent Dev Bug Sandbox logo
AI & Agent Dev Bug Sandbox
Back to Radar

Server Action POSTs Are Retargeted By Non-Next History.PushState/ReplaceState Calls

Next.js patches window.history.pushState/replaceState and treats any external call that lacks the internal __NA/_N state as a navigation event. The provided URL is passed unfiltered to ACTION_RESTORE, which overwrites state.canonicalUrl. Server Action dispatches then POST to this corrupted canonical URL instead of the original page route, causing silent submission loss, execution with corrupted dynamic params, or unexpected redirects.

highConfidence 92%Next.jsAffected V16.2.12

Origin Analysis

In next/dist/client/components/app-router.js, applyUrlFromHistoryPushReplace forwards the caller-supplied URL directly to dispatchAppRouterAction({ type: ACTION_RESTORE, url: new URL(url ?? href, href) }) without validating whether the URL is a legitimate app route or differs only in query string. The server-action-reducer subsequently uses state.canonicalUrl (now set from this external URL) as the fetch endpoint for Server Actions. This design flaw allows any script that calls history.replaceState with a pathname to silently change where Server Action POSTs are sent.
1. Clone https://github.com/FahadAlazemi/next-server-action-history-retarget 2. Run npm install && npm run build && npm start 3. Open http://localhost:3000/ar/signup (route app/[lang]/signup/page.tsx) 4. In devtools console run: history.replaceState({}, '', '/[lang]/signup') 5. Click Sign up and observe the POST goes to /[lang]/signup instead of /ar/signup, resulting in 404 or an unexpected response error.

Fixing Code Block

Edge Case Audit

Applications that intentionally rely on external pathname history updates to sync usePathname or trigger shallow navigation will no longer work as expected. Before deploying, monitor any analytics, A/B testing, or third-party scripts that might call replaceState with a different pathname. Rollback recommendation: if this breaks legitimate flows, revert this patch and instead modify server-action-reducer to derive the POST endpoint from the route tree or action metadata rather than state.canonicalUrl. Validate that url is same-origin and avoid unescaped dynamic segment placeholders.

Ecosystem Topology