@@ -84,6 +84,10 @@ async function main() {
8484 async function fetchRscPayload ( ) {
8585 const renderRequest = createRscRenderRequest ( window . location . href )
8686 const payload = await createFromFetch < RscPayload > ( fetch ( renderRequest ) )
87+ // Go to the top if this is not a fragment navigation
88+ if ( ! window . location . hash ) {
89+ window . scrollTo ( { top : 0 , behavior : 'smooth' } ) ;
90+ }
8791 setPayload ( payload )
8892 }
8993
@@ -138,37 +142,38 @@ async function main() {
138142 }
139143}
140144
145+ function isAnchorNavigation ( link : HTMLAnchorElement ) {
146+ return link . href &&
147+ link . origin === window . location . origin &&
148+ link . pathname === window . location . pathname &&
149+ link . hash !== '' ;
150+ }
151+
141152// Helper that set up events interception for client side navigation
142- function listenNavigation ( onNavigation : ( ) => void ) {
153+ function listenNavigation ( fetchRsc : ( ) => void ) {
143154
144- // When the user clicks the back button, navigate with RSC.
145- window . addEventListener ( 'popstate' , onNavigation )
146155
156+ // pushState is called to navigate to a new URL without a full page reload.
157+ // called in the onClick function
147158 const oldPushState = window . history . pushState
148159 window . history . pushState = function ( ...args ) {
149- const res = oldPushState . apply ( this , args )
150- onNavigation ( )
151- return res
160+ oldPushState . apply ( this , args )
161+ fetchRsc ( )
152162 }
153163
164+ // replaceState changes the current URL entry without adding a new history entry (used for redirects or URL cleanups).
154165 const oldReplaceState = window . history . replaceState
155166 window . history . replaceState = function ( ...args ) {
156- const res = oldReplaceState . apply ( this , args )
157- onNavigation ( )
158- return res
167+ oldReplaceState . apply ( this , args )
168+ fetchRsc ( )
159169 }
160170
161171 function onClick ( e : MouseEvent ) {
162172 let link = ( e . target as Element ) . closest ( 'a' )
163173 if ( ! link ) return ;
164174
165175 // Navigation in the same page
166- const isAnchorNav =
167- link . href &&
168- link . origin === window . location . origin &&
169- link . pathname === window . location . pathname &&
170- link . hash !== '' ;
171-
176+ const isAnchorNav = isAnchorNavigation ( link ) ;
172177 if (
173178 ! isAnchorNav &&
174179 link instanceof HTMLAnchorElement &&
@@ -177,7 +182,7 @@ function listenNavigation(onNavigation: () => void) {
177182 link . origin === location . origin &&
178183 ! link . hasAttribute ( 'download' ) &&
179184 e . button === 0 && // left clicks only
180- ! e . metaKey && // open in new tab (mac )
185+ ! e . metaKey && // open in new tab (Mac )
181186 ! e . ctrlKey && // open in new tab (windows)
182187 ! e . altKey && // download
183188 ! e . shiftKey &&
@@ -188,11 +193,23 @@ function listenNavigation(onNavigation: () => void) {
188193 }
189194 }
190195
196+ // When the user clicks the back button, navigate with RSC.
197+ // Fire on new URL (anchor link also)
198+ window . addEventListener ( 'popstate' , ( event ) => {
199+ // ignore change for hash-only changes
200+ if ( ( event . target as Window ) ?. location . hash ) return ;
201+ fetchRsc ( ) ;
202+ } )
203+
204+ // On click
191205 document . addEventListener ( 'click' , onClick )
192206
193207 return ( ) => {
208+ // click = mouse click
194209 document . removeEventListener ( 'click' , onClick )
195- window . removeEventListener ( 'popstate' , onNavigation )
210+ // popstate = back button
211+ window . removeEventListener ( 'popstate' , fetchRsc )
212+ // restore initial state
196213 window . history . pushState = oldPushState
197214 window . history . replaceState = oldReplaceState
198215 }
0 commit comments