mirror of
https://github.com/cloudflare/vinext.git
synced 2026-05-09 08:25:34 +02:00
[PR #929] [MERGED] fix(app-router): restore hash anchors on history traversal #956
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#956
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/929
Author: @NathanDrake2406
Created: 4/28/2026
Status: ✅ Merged
Merged: 4/28/2026
Merged by: @james-elicx
Base:
main← Head:nathan/fix-hash-popstate-scroll📝 Commits (2)
fbc6fdffix(app-router): restore hash anchors on history traversal78ce7dbtest(app-router): cover hash scroll edge cases📊 Changes
3 files changed (+161 additions, -0 deletions)
View changed files
📝
packages/vinext/src/server/app-browser-entry.ts(+30 -0)➕
tests/e2e/app-router/nextjs-compat/hash-popstate-scroll.spec.ts(+93 -0)➕
tests/fixtures/app-basic/app/nextjs-compat/hash-popstate-scroll/page.tsx(+38 -0)📄 Description
What changed
App Router popstate scroll restoration now falls back to hash-anchor scrolling when the traversed history entry has no vinext numeric scroll state.
A focused App Router e2e fixture and regression tests cover vinext-managed hash-only Link entries through back and forward traversal, including decoded non-latin IDs, named-anchor fallback, and #top.
Why
vinext sets history.scrollRestoration to manual. Hash-only App Router navigations can push entries without __vinext_scrollY, so forward traversal to /path#anchor updated the URL but skipped the browser native anchor scroll and could leave the page at the top.
Approach
The popstate restoration path keeps saved numeric scroll state as the first-priority behavior. Only entries without vinext scroll state fall back to window.location.hash.
Hash fallback mirrors practical Next.js semantics: decode the fragment when possible, treat #top and empty fragments as top, prefer id lookup, then fall back to name lookup. Malformed encodings are left as-is instead of throwing. The fallback uses explicit auto scroll behavior to match the existing vinext hash-scroll helpers.
Validation
Risks / follow-ups
Numeric back and forward restoration intentionally remains higher priority than hash scrolling, so entries with saved __vinext_scrollY keep existing behavior.
This does not attempt to refactor the existing duplicate hash-scroll helpers between App Router browser entry and next/navigation; the change stays scoped to the App Router popstate gap.
Next.js references
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.