Skip to content

Pages Router popstate: isHashOnly is a coarse proxy for Next's onlyAHashChange #2169

Description

@NathanDrake2406

Context

Follow-up debt from #2116. That PR fixed a masked-route false positive in the Pages Router popstate handler by excluding masked entries (state.url !== state.as) from the hash-only fast path. It did not make hash handling generally equivalent to Next.js.

Problem

In packages/vinext/src/shims/router.ts (handlePagesRouterPopState), the hash-only detection is:

const isHashOnly = !isMaskedRoute && browserUrl === routerRuntimeState.lastPathnameAndSearch;

where browserUrl = window.location.pathname + window.location.search. This keys off path + search equality, not a real hash delta. Next.js's onlyAHashChange (.nextjs-ref/packages/next/src/shared/lib/router/router.ts) compares the actual hash fragments of the previous vs next URL and returns false for an identical no-hash as.

The isMaskedRoute guard added in #2116 closes the one case that mattered (a masked entry whose visible URL is unchanged). But the underlying approximation remains: any back/forward where path+search match but the hash relationship differs from Next's notion of a hash-only change can still be classified differently than Next.

Proposed work

Port onlyAHashChange faithfully — compare the previous and next URL hash fragments rather than path+search equality — so the popstate hash-only fast path matches Next.js exactly. Drop the isMaskedRoute special-case once the real hash-delta comparison subsumes it.

Acceptance

  • Hash-only detection compares actual hash fragments (prev vs next), matching Next's onlyAHashChange.
  • Existing masked-popstate regression tests in tests/pages-router-i18n-sticky-locale.test.ts stay green.
  • Add cases for: identical no-hash URL (not hash-only), add/remove/change hash on same path (hash-only), hash change combined with search change (not hash-only).

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions