A driver instance lives within a single page: a full page load destroys it, and even in single-page apps the next route’s elements don’t exist while the current route is showing. The reliable pattern for tours that span navigation is to treat the tour as resumable: persist the step to resume at, let the app navigate normally, and start a fresh tour from the saved step on the next page.
Define the tour in a function so every page can create it. On the step whose click navigates away, use advanceOnClick together with an onNextClick override: instead of moving to a step that lives on another page, save where to resume and tear the tour down. The link’s own click keeps working, so navigation happens naturally.
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
const TOUR_KEY = "app-tour-step";
export function createTour() {
let isNavigating = false;
const driverObj = driver({
steps: [
{
element: "#reports-nav",
popover: { title: "Reports", description: "Your reports live here." },
},
{
element: "#settings-link",
// Clicking the highlighted link continues the tour and navigates.
advanceOnClick: true,
popover: {
title: "Open Settings",
description: "Click this link to continue on the settings page.",
showButtons: ["close"],
onNextClick: () => {
// The next step lives on another page: save where to resume
// and let the click's navigation happen naturally.
isNavigating = true;
localStorage.setItem(TOUR_KEY, "2");
driverObj.destroy();
},
},
},
// These steps target elements on the settings page. waitForElement
// absorbs the rendering delay after a client-side route change.
{
element: "#profile-form",
waitForElement: 5000,
popover: { title: "Your Profile", description: "Update your details here." },
},
{
element: "#save-settings",
popover: { title: "Save", description: "Don't forget to save your changes." },
},
],
onDestroyed: () => {
// Finished or exited: clear the saved progress so the tour does not
// come back on the next visit. Skipped for the hand-off destroy above.
if (!isNavigating) {
localStorage.removeItem(TOUR_KEY);
}
},
});
return driverObj;
}On every page load (or in your router’s mount hook), check for saved progress and resume from it:
const savedStep = localStorage.getItem(TOUR_KEY);
if (savedStep !== null) {
createTour().drive(Number(savedStep));
}drive() accepts the step index to start from, so the tour picks up exactly where it left off.
destroy() in the hand-off matters for single-page apps, where the old page’s tour would otherwise stay on screen.driverObj.getActiveIndex() + 1, or store a step id and look up its index when resuming, so reordering steps doesn’t break saved progress.waitForElement on the first resumed step keeps the tour from falling back to the centered popover while the page hydrates. See Interactive Tour for more on click-driven steps and waiting.