mirror of
https://github.com/cloudflare/vinext.git
synced 2026-05-09 08:25:34 +02:00
[PR #423] [MERGED] fix: honor beforePopState when useRouter is mounted #561
Labels
No labels
enhancement
enhancement
good first issue
help wanted
nextjs-tracking
nextjs-tracking
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
starred/vinext#561
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/cloudflare/vinext/pull/423
Author: @JaredStowell
Created: 3/10/2026
Status: ✅ Merged
Merged: 3/10/2026
Merged by: @james-elicx
Base:
main← Head:jstowell/fix-before-pop-state-bypass📝 Commits (1)
4fd2b11Update router popstate handling📊 Changes
6 files changed (+144 additions, -67 deletions)
View changed files
📝
packages/vinext/src/shims/router.ts(+2 -13)📝
tests/__snapshots__/entry-templates.test.ts.snap(+57 -54)📝
tests/e2e/pages-router/before-pop-state.spec.ts(+46 -0)➕
tests/fixtures/pages-basic/pages/before-pop-state-destination.tsx(+16 -0)📝
tests/fixtures/pages-basic/pages/before-pop-state-test.tsx(+3 -0)📝
tests/shims.test.ts(+20 -0)📄 Description
Fix a Pages Router bug where
router.beforePopState()could be bypassed whenever the currently mounted page calleduseRouter().Previously, the
next/routershim had two separatepopstatelisteners:_beforePopStateCbuseRouter()-scoped listener that always callednavigateClient()That meant a blocked back/forward navigation could still proceed through the hook-local listener even if the module-level listener returned early.
This change removes the duplicate
useRouter()popstatelistener and keeps browser back/forward handling owned by the module-level listener only.useRouter()still updates via the existingvinext:navigateevent after successful navigations.Problem
The bug was in
packages/vinext/src/shims/router.ts.Before this change:
beforePopState(cb)stored the callback in_beforePopStateCbpopstatehandler checked_beforePopStateCband returned if it gotfalseuseRouter()also registered its ownpopstatelistenernavigateClient(window.location.pathname + window.location.search)So when a page mounted
useRouter(), a blockedpopstatecould still navigate anyway.In practice, the existing e2e test did not catch this because it navigated from
/before-pop-state-testto/about, and/aboutdoes not mountuseRouter(). By the timepage.goBack()fired, the extra hook-level listener was no longer present.Fix
Make
popstatehandling single-owner.Changes:
useRouter()-localpopstateuseEffectpopstatelisteneruseRouter()state from the existingvinext:navigateeventThis matches Next.js more closely. In Next.js, the router owns a single
onPopStatehandler and checksbeforePopState()there; it does not install an additional hook-levelpopstatelistener.Tests
Added regression coverage for the real failing case.
Fixture changes
tests/fixtures/pages-basic/pages/before-pop-state-destination.tsxuseRouter()and exposes its currentasPathtests/fixtures/pages-basic/pages/before-pop-state-test.tsxE2E coverage
Expanded
tests/e2e/pages-router/before-pop-state.spec.tsto cover:useRouteruseRouterThis closes the coverage gap that allowed the bug to slip through.
Unit/snapshot coverage
tests/shims.test.tstests/__snapshots__/entry-templates.test.ts.snapfor the new Pages Router fixture routeVerification
pnpm test tests/shims.test.ts -t "beforePopState on both"pnpm test tests/entry-templates.test.ts -uPLAYWRIGHT_PROJECT=pages-router pnpm run test:e2e tests/e2e/pages-router/before-pop-state.spec.tsPLAYWRIGHT_PROJECT=cloudflare-pages-router pnpm run test:e2e tests/e2e/cloudflare-pages-router/interactive.spec.ts🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.