@@ -116,7 +116,7 @@ import type {
116116import { submitAction } from './use-endpoint' ;
117117import { useQwikRouterEnv } from './use-functions' ;
118118import { isSameOrigin , isSamePath , toPath , toUrl } from './utils' ;
119- import { startViewTransition } from './view-transition' ;
119+ import { startViewTransition , type ViewTransition } from './view-transition' ;
120120
121121declare const window : ClientSPAWindow ;
122122
@@ -157,7 +157,10 @@ const preventNav: {
157157
158158// Track navigations during prevent so we don't overwrite.
159159// We need to use an object so we can write into it from qrls.
160- const internalState = { navCount : 0 } ;
160+ const internalState : {
161+ navCount : number ;
162+ currentTransition ?: ViewTransition ;
163+ } = { navCount : 0 } ;
161164
162165const getScroller = ( ) => {
163166 let scroller = document . getElementById ( QWIK_ROUTER_SCROLLER ) ;
@@ -359,6 +362,7 @@ export const useQwikRouter = (props?: QwikRouterProps) => {
359362 scroll = true ,
360363 } = typeof opt === 'object' ? opt : { forceReload : opt } ;
361364 internalState . navCount ++ ;
365+ internalState . currentTransition ?. skipTransition ( ) ;
362366
363367 // If this is the first SPA navigation, we rewrite routeInternal's URL
364368 // as the browser location URL to prevent an erroneous origin mismatch.
@@ -491,6 +495,10 @@ export const useQwikRouter = (props?: QwikRouterProps) => {
491495 useContextProvider ( RouteActionContext , actionState ) ;
492496 useContextProvider < any > ( RoutePreventNavigateContext , registerPreventNav ) ;
493497
498+ /**
499+ * This is split in 3 tasks because we need to update the head once we figured out the route, and
500+ * before we trigger the render, and we need to subscribe only head to loader signal updates
501+ */
494502 useTask$ (
495503 async ( { track } ) => {
496504 const navigation = track ( routeInternal ) ;
@@ -588,8 +596,11 @@ export const useQwikRouter = (props?: QwikRouterProps) => {
588596 updateRouteLoaderPaths ( routeLoaderCtx , loadedRoute . $loaderPaths$ , trackUrl ) ;
589597 const routeLoaders = ensureRouteLoaderSignals ( contentModules , loaderState , routeLoaderCtx ) ;
590598 if ( routeLoaders . length > 0 ) {
591- // Trigger loader signals to fetch data for the new route
592- // No await because we want to render ASAP; loaders will update the page when they resolve
599+ // Trigger loader signals to fetch data for the new route. No await —
600+ // we want to render ASAP. Loaders update the page when they resolve,
601+ // and SSR awaits them on the server side. A loader that redirects
602+ // fires goto() directly; the new nav starts while this one finishes
603+ // committing, producing a brief flash of the current page.
593604 for ( let i = 0 ; i < routeLoaders . length ; i ++ ) {
594605 const loader = routeLoaders [ i ] ;
595606 // trigger load
@@ -644,7 +655,14 @@ export const useQwikRouter = (props?: QwikRouterProps) => {
644655 routeLocationTarget . params = $params$ ;
645656 }
646657
647- routeInternal . untrackedValue = { type : navType , dest : trackUrl } ;
658+ routeInternal . untrackedValue = {
659+ type : navType ,
660+ dest : trackUrl ,
661+ forceReload : navigation . forceReload ,
662+ replaceState : navigation . replaceState ,
663+ scroll : navigation . scroll ,
664+ historyUpdated : navigation . historyUpdated ,
665+ } ;
648666
649667 // Update content.
650668 // IMPORTANT: contentInternal must use .untrackedValue, NOT .value. Subscribers
@@ -657,6 +675,10 @@ export const useQwikRouter = (props?: QwikRouterProps) => {
657675 contentInternal . untrackedValue = noSerialize ( contentModules ) ;
658676 actionDataSignal . value = actionData ;
659677
678+ // Preserve historyUpdated/scroll/etc. for the commit task. Without this, the
679+ // commit task reads `navigation.historyUpdated` as undefined and calls
680+ // clientNavigate a second time, pushing an extra history entry.
681+
660682 // hand off to next tasks
661683 navContext . value = noSerialize ( {
662684 routeName : $routeName$ ,
@@ -665,6 +687,7 @@ export const useQwikRouter = (props?: QwikRouterProps) => {
665687 shouldForcePrevUrl,
666688 shouldForceUrl,
667689 shouldForceParams,
690+ navCount : navCountBefore ,
668691 } ) ;
669692 } ,
670693 // We should only wait for head calculation to complete on the server
@@ -714,8 +737,13 @@ export const useQwikRouter = (props?: QwikRouterProps) => {
714737 return ;
715738 }
716739
740+ if ( nav . navCount !== internalState . navCount ) {
741+ navResolver . r ?.( ) ;
742+ return ;
743+ }
744+
717745 const container = _getContextContainer ( ) ;
718- const navigation = track ( routeInternal ) ;
746+ const navigation = routeInternal . untrackedValue ;
719747
720748 const { navType, replaceState, routeName } = nav ;
721749 const trackUrl = routeLocation . url ;
@@ -757,6 +785,9 @@ export const useQwikRouter = (props?: QwikRouterProps) => {
757785 }
758786
759787 const navigate = ( ) => {
788+ if ( nav . navCount !== internalState . navCount ) {
789+ return Promise . resolve ( ) ;
790+ }
760791 if ( navigation . historyUpdated ) {
761792 const currentPath = location . pathname + location . search + location . hash ;
762793 const nextPath = toPath ( trackUrl ) ;
@@ -774,14 +805,17 @@ export const useQwikRouter = (props?: QwikRouterProps) => {
774805
775806 const _waitNextPage = ( ) => {
776807 if ( props ?. viewTransition === false || ! ( 'viewTransition' in document ) ) {
777- return navigate ( ) ;
808+ return navigate ( ) . then ( ( ) => undefined as ViewTransition | undefined ) ;
778809 }
779- return startViewTransition ( {
810+ const { ready , transition } = startViewTransition ( {
780811 update : navigate ,
781812 types : [ 'qwik-navigation' ] ,
782813 } ) ;
814+ internalState . currentTransition = transition ;
815+ return ready . then ( ( ) => transition ) ;
783816 } ;
784817 _waitNextPage ( ) . finally ( ( ) => {
818+ internalState . currentTransition = undefined ;
785819 ( container as ClientContainer ) . element . setAttribute ?.( Q_ROUTE , routeName ) ;
786820 const scrollState = currentScrollState ( scroller ) ;
787821 saveScrollHistory ( scrollState ) ;
0 commit comments