@@ -82,6 +82,13 @@ export const IonRouter = ({ children, registerHistoryListener }: PropsWithChildr
8282 const currentTab = useRef < string | undefined > ( undefined ) ;
8383 const viewStack = useRef ( new ReactRouterViewStack ( ) ) ;
8484 const incomingRouteParams = useRef < Partial < RouteInfo > | null > ( null ) ;
85+ /**
86+ * Tracks URLs (pathname + search) that the user navigated away from via
87+ * browser back. When a POP event's destination matches the top of this
88+ * stack, it's a browser forward navigation. Cleared on PUSH (new
89+ * navigation invalidates forward history, just like in the browser).
90+ */
91+ const forwardStack = useRef < string [ ] > ( [ ] ) ;
8592
8693 const [ routeInfo , setRouteInfo ] = useState < RouteInfo > ( {
8794 id : generateId ( 'routeInfo' ) ,
@@ -187,19 +194,31 @@ export const IonRouter = ({ children, registerHistoryListener }: PropsWithChildr
187194 }
188195 /**
189196 * A `POP` action can be triggered by the browser's back/forward
190- * button.
197+ * button. Both fire as POP events, so we use a forward stack to
198+ * distinguish them: when going back, we push the leaving pathname
199+ * onto the stack. When the next POP's destination matches the top
200+ * of the stack, it's a forward navigation.
191201 */
192202 if ( action === 'POP' ) {
193203 const currentRoute = locationHistory . current . current ( ) ;
194- /**
195- * Check if the current route was "pushed" by a previous route
196- * (indicates a linear history path).
197- */
198- if ( currentRoute && currentRoute . pushedByRoute ) {
204+ const isForwardNavigation =
205+ forwardStack . current . length > 0 &&
206+ forwardStack . current [ forwardStack . current . length - 1 ] === location . pathname + location . search ;
207+
208+ if ( isForwardNavigation ) {
209+ forwardStack . current . pop ( ) ;
210+ incomingRouteParams . current = {
211+ routeAction : 'push' ,
212+ routeDirection : 'forward' ,
213+ tab : tabToUse ,
214+ } ;
215+ } else if ( currentRoute && currentRoute . pushedByRoute ) {
216+ // Back navigation — record current URL for potential forward
217+ forwardStack . current . push ( currentRoute . pathname + ( currentRoute . search || '' ) ) ;
199218 const prevInfo = locationHistory . current . findLastLocation ( currentRoute ) ;
200219 incomingRouteParams . current = { ...prevInfo , routeAction : 'pop' , routeDirection : 'back' } ;
201- // It's a non-linear history path like a direct link.
202220 } else {
221+ // It's a non-linear history path like a direct link.
203222 incomingRouteParams . current = {
204223 routeAction : 'pop' ,
205224 routeDirection : 'none' ,
@@ -218,6 +237,12 @@ export const IonRouter = ({ children, registerHistoryListener }: PropsWithChildr
218237 }
219238 }
220239
240+ // New navigation (PUSH) invalidates browser forward history,
241+ // so clear our forward stack to stay in sync.
242+ if ( action === 'PUSH' ) {
243+ forwardStack . current = [ ] ;
244+ }
245+
221246 let routeInfo : RouteInfo ;
222247
223248 // If we're navigating away from tabs to a non-tab route, clear the current tab
@@ -456,12 +481,17 @@ export const IonRouter = ({ children, registerHistoryListener }: PropsWithChildr
456481 const condition1 = routeInfo . lastPathname === routeInfo . pushedByRoute ;
457482 const condition2 = prevInfo . pathname === routeInfo . pushedByRoute && routeInfo . tab === '' && prevInfo . tab === '' ;
458483 if ( condition1 || condition2 ) {
484+ // Record current URL so browser forward is detectable
485+ forwardStack . current . push ( routeInfo . pathname + ( routeInfo . search || '' ) ) ;
459486 navigate ( - 1 ) ;
460487 } else {
461488 /**
462489 * It's a non-linear back navigation.
463490 * e.g., direct link or tab switch or nested navigation with redirects
491+ * Clear forward stack since the REPLACE-based navigate resets history
492+ * position, making any prior forward entries unreachable.
464493 */
494+ forwardStack . current = [ ] ;
465495 handleNavigate ( prevInfo . pathname + ( prevInfo . search || '' ) , 'pop' , 'back' , incomingAnimation ) ;
466496 }
467497 /**
0 commit comments