Repository navigation
feat(router): the default document restores scroll on back/forward - #948
Merged
Merged
Conversation
commit: |
An app that passes no `document` to createRouter renders DefaultDocument,
which rendered Html.Meta and Html.Scripts but not Html.ScrollRestoration, so
zero-config apps left back/forward scrolling to the browser: no saved
position per history entry, and none after a cache-miss refetch. It now
renders <ScrollRestoration /> after {children}, like the create-rango
templates. An app keeps browser-owned scrolling, or sets getKey, by passing
its own document.
mini (default document) drops its layout's own Html.ScrollRestoration,
which would now be a second mount; its scroll e2e runs against the default
document's instance.
Tests: default-document.test.tsx (renderRoute) pins manual mode on mount and
auto on unmount (fails without the change: "auto"); new test-app e2e
default-document-scroll.test.ts, dev + production, pins back/forward
restore with no custom document (fails without the change in dev).
Docs: the canonical Document snippet gains <Html.ScrollRestoration />
wherever it states the default's structure (getting-started, createRouter
reference, tutorial, README, router-setup skill), with how to opt out or set
getKey; "render it in the root layout" guidance now says it renders once, in
the document. router-options JSDoc, feature-map, CHANGELOG.
ivogt
force-pushed
the
feat/default-document-scroll-restoration
branch
from
September 28, 2026 16:07
df4ebcf to
ac77314
Compare
This was referenced Sep 28, 2026
ivogt
added a commit
that referenced
this pull request
Sep 28, 2026
Unreleased -> 0.17.0 (2026-09-28) in CHANGELOG.md, and the #948 entry gains its PR link.
5 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
An app that passes no
documenttocreateRoutergets the router's default document. After #937 it renderedHtml.MetaandHtml.Scripts, but notHtml.ScrollRestoration. So the zero-config app, and every scaffold that relies on the default, left back/forward scrolling to the browser. That means no saved position per history entry, and no restore after a cache-miss refetch (#943). An app had to write a custom document just to get scroll restoration.The default document now renders every
Htmlmember, matching the create-rango templates (rangojs/templates#1).Before / after
(
MetaTags/Scripts/ScrollRestorationare the components behindHtml.Meta/Html.Scripts/Html.ScrollRestoration; the default document imports them directly.)End-to-end example
Scroll down on
/, open/blog(lands at the top), press back:/returns to where it was left. Before this change the browser decided, and after a cache-miss refetch it usually landed at the top.To keep the browser's own behavior, or to pass
getKey, provide a document:Semantics
documenthistory.scrollRestoration = "auto")"manual", positions insessionStorage)document, plus<Html.ScrollRestoration />in a layoutgetKey), plus a[Scroll] Already initializedwarning from the default document's instance. The docs now point these apps to a custom document.documentwithHtml.ScrollRestorationdocumentwithout itCode change
src/components/DefaultDocument.tsx: renders<ScrollRestoration />after{children}, imported directly like the other two members; JSDoc says how to opt out or setgetKey.e2e/mini/src/router.tsx: drops the layout's<Html.ScrollRestoration />and theHtmlimport. mini uses the default document, so its own instance would now be a second mount. mini's scroll e2e now runs against the default document's instance.client.tsxcomment and mini README updated.src/router/router-options.ts: thedocumentJSDoc describes the default document's content.<Html.ScrollRestoration />wherever it says "the default is exactly this" (getting-started,createRouterreference, tutorial project setup, README, router-setup skill), with how to opt out or setgetKey. The "renderHtml.ScrollRestorationin the root layout" guidance (client-urls guide,useScrollRestorationreference,docs/client-urls.md) now says it renders once in the document, which the default document already does.docs/internal/feature-map.md: theHtmlnote records thatDefaultDocumentrenders all three members.CHANGELOG.md: Unreleased entry.tools/check-bundle-guards.mjs: router-chunk ratchet 44 KB -> 45 KB (see Notes).Test plan
renderRoute)src/components/__tests__/default-document.test.tsx"turns on scroll restoration out of the box": renderingDefaultDocumentsetshistory.scrollRestorationto"manual", and unmounting restores"auto".Expected: "manual" Received: "auto".e2e/default-document-scroll.test.ts, test-app (nodocument), dev + production: manual mode after hydration, scroll to 1200, router-navigate to/blog(0), back (1200), forward (0).Expected: "manual" Received: "auto".test:unit:all, lint, format,check:docs-api,check:skills,check:skills-catalog,check:e2e-bucketing,check:e2e-parity --strict.document, so the default document does not apply there; test-app is this change's app-level coverage.Notes
e2e/back-forward-scroll.test.ts("back/forward scroll without ScrollRestoration") runs against test-app, which now has scroll restoration from the default document. Whichever PR merges second moves that router-suite case to an app with a custom document that does not renderHtml.ScrollRestoration. Its cloudflare-basic case is unaffected. Both PRs also touchhooks.mdxandCHANGELOG.md.Htmlmember. That case is still pinned at unit level bysrc/testing/__tests__/html-namespace.rsc-test.tsx.router.tsimportsDefaultDocumentas thedocumentfallback, so it is in every app's client build, and itsScrollRestorationimport keepsinitScrollRestorationin the eager router chunk. Thebundle-guardsratchet is raised 44 KB -> 45 KB (tools/check-bundle-guards.mjs, reasoning recorded next to the earlier re-baselines). The alternative, lazy-loading the init out ofbrowser/scroll-restoration.ts(whose module state the navigation bridge shares), was judged not worth a chunk request for ~0.35 KB.