US English (US)
ES Spanish

Submit Article Requests

Do you have a suggestion for an article you would like to see created?
Feel free to submit this form and add your suggestions to our document board.

Please fill out the contact form below and we will reply as soon as possible.

  • Integration Hub
  • Contact Us
English (US)
US English (US)
ES Spanish
  • Docs home
  • Installation & Developers
  • Installing Appcues on Web

Set up Appcues in a single-page application

Configure page detection for React, Next.js, Vue, Angular, and other SPA frameworks.

Updated at March 25th, 2026

Submit Article Requests

Do you have a suggestion for an article you would like to see created?
Feel free to submit this form and add your suggestions to our document board.

Please fill out the contact form with the details about the help content you'd like to see.

  • Home

  • Getting Started

    • Installation & Developers

      • Web Experiences

        • Mobile Experiences

          • Workflows

            • Analytics & Data

              • Account Management

                • Best Practices

                  • Integrations

                    Table of Contents

                    You probably don't need to do anything When to disable automatic detection Manual page tracking by framework React (with React Router v6+) Next.js (App Router) Next.js (Pages Router) Vue 3 (with Vue Router 4) Angular (v14+) Svelte (with SvelteKit) Other frameworks Confirm it worked Still stuck? Related

                    You probably don't need to do anything

                    If your Appcues installation includes enableURLDetection: true (the default since May 2022), the SDK automatically detects page changes in single-page applications. You don't need to call Appcues.page() manually.

                    <script type="text/javascript">
                      window.AppcuesSettings = { enableURLDetection: true };
                    </script>
                    <script src="//fast.appcues.com/YOUR_APPCUES_ID.js"></script>
                    

                    If you installed via NPM, automatic URL detection is enabled by default with Appcues.setup().

                    To check: Open the Appcues Debugger and navigate between pages in your app. The Tracking Pages indicator should flash briefly and return to green on each navigation. If it does, automatic detection is working and you can stop here.

                    When to disable automatic detection

                    enableURLDetection watches the full URL, including query parameters and hash fragments. This causes problems when your app updates query parameters without actually navigating to a different page.

                    For example, a search page that updates the URL on each keystroke:

                    https://app.example.com/results?search=t
                    https://app.example.com/results?search=te
                    https://app.example.com/results?search=tes
                    https://app.example.com/results?search=test
                    

                    The path (/results) hasn't changed, but the URL has. Appcues sees each change as a new page, which interrupts any in-progress Flow and re-checks for content. If your users see Flows disappearing or restarting mid-step, this is likely the cause.

                    To fix this: Disable automatic detection and call Appcues.page() manually — but only when the pathname actually changes, not on query parameter or hash changes.

                    Disable it by setting enableURLDetection to false:

                    <script type="text/javascript">
                      window.AppcuesSettings = { enableURLDetection: false };
                    </script>
                    <script src="//fast.appcues.com/YOUR_APPCUES_ID.js"></script>
                    

                    Then follow the framework-specific instructions below.

                    Manual page tracking by framework

                    Only follow these instructions if you've disabled enableURLDetection. The key principle: call Appcues.page() only when the pathname changes — not when query parameters or hash fragments change. This prevents Flows from restarting when users filter, search, or paginate.

                    Make sure Appcues.identify() has been called before the first Appcues.page() call. identify() includes a built-in page call, so you don't need to call page() immediately after identifying.

                    React (with React Router v6+)

                    Track only pathname changes using useLocation:

                    import { useLocation } from "react-router-dom";
                    import { useEffect, useRef } from "react";
                    
                    function AppcuesPageTracker() {
                      const location = useLocation();
                      const prevPathname = useRef(location.pathname);
                    
                      useEffect(() => {
                        if (location.pathname !== prevPathname.current) {
                          prevPathname.current = location.pathname;
                          window.Appcues?.page();
                        }
                      }, [location]);
                    
                      return null;
                    }
                    

                    Add <AppcuesPageTracker /> inside your <BrowserRouter> so it has access to router context:

                    import { BrowserRouter } from "react-router-dom";
                    
                    function App() {
                      return (
                        <BrowserRouter>
                          <AppcuesPageTracker />
                          {/* your routes */}
                        </BrowserRouter>
                      );
                    }
                    

                    Next.js (App Router)

                    In Next.js 13+ with the App Router, usePathname from next/navigation already returns only the pathname (no query parameters), so it only fires when the path actually changes:

                    "use client";
                    
                    import { usePathname } from "next/navigation";
                    import { useEffect } from "react";
                    
                    export function AppcuesPageTracker() {
                      const pathname = usePathname();
                    
                      useEffect(() => {
                        window.Appcues?.page();
                      }, [pathname]);
                    
                      return null;
                    }
                    

                    Add this component to your root layout (app/layout.tsx or app/layout.js):

                    import { AppcuesPageTracker } from "./appcues-page-tracker";
                    
                    export default function RootLayout({ children }) {
                      return (
                        <html>
                          <body>
                            <AppcuesPageTracker />
                            {children}
                          </body>
                        </html>
                      );
                    }
                    

                    Next.js (Pages Router)

                    If you're using the legacy Pages Router, listen for route changes in _app.js. The routeChangeComplete callback receives the new URL — compare pathnames to filter out query-only changes:

                    import { useRouter } from "next/router";
                    import { useEffect, useRef } from "react";
                    
                    export default function App({ Component, pageProps }) {
                      const router = useRouter();
                      const prevPathname = useRef(router.pathname);
                    
                      useEffect(() => {
                        const handleRouteChange = (url) => {
                          const newPathname = url.split("?")[0];
                          if (newPathname !== prevPathname.current) {
                            prevPathname.current = newPathname;
                            window.Appcues?.page();
                          }
                        };
                    
                        router.events.on("routeChangeComplete", handleRouteChange);
                        return () => router.events.off("routeChangeComplete", handleRouteChange);
                      }, [router]);
                    
                      return <Component {...pageProps} />;
                    }
                    

                    Vue 3 (with Vue Router 4)

                    Use an afterEach navigation guard and compare the path property to ignore query-only changes:

                    import { createRouter, createWebHistory } from "vue-router";
                    
                    const router = createRouter({
                      history: createWebHistory(),
                      routes: [
                        // your routes
                      ],
                    });
                    
                    router.afterEach((to, from) => {
                      if (to.path !== from.path) {
                        window.Appcues?.page();
                      }
                    });
                    
                    export default router;
                    

                    Angular (v14+)

                    Subscribe to NavigationEnd events and compare the URL pathname to filter out query parameter changes:

                    import { Component } from "@angular/core";
                    import { Router, NavigationEnd } from "@angular/router";
                    import { filter, pairwise, startWith } from "rxjs/operators";
                    
                    declare global {
                      interface Window { Appcues: any; }
                    }
                    
                    @Component({
                      selector: "app-root",
                      templateUrl: "./app.component.html",
                    })
                    export class AppComponent {
                      constructor(private router: Router) {
                        this.router.events
                          .pipe(
                            filter((event): event is NavigationEnd => event instanceof NavigationEnd),
                            startWith({ urlAfterRedirects: "" } as NavigationEnd),
                            pairwise()
                          )
                          .subscribe(([prev, curr]) => {
                            const prevPath = prev.urlAfterRedirects.split("?")[0];
                            const currPath = curr.urlAfterRedirects.split("?")[0];
                            if (prevPath !== currPath) {
                              window.Appcues?.page();
                            }
                          });
                      }
                    }
                    

                    Svelte (with SvelteKit)

                    In your root layout (+layout.svelte), use afterNavigate and compare pathnames:

                    <script>
                      import { afterNavigate } from "$app/navigation";
                    
                      let prevPathname = "";
                    
                      afterNavigate(({ to }) => {
                        const newPathname = to?.url?.pathname || "";
                        if (newPathname !== prevPathname) {
                          prevPathname = newPathname;
                          window.Appcues?.page();
                        }
                      });
                    </script>
                    
                    <slot />
                    

                    Other frameworks

                    For any SPA framework not listed above, the pattern is the same: listen for route change events, compare the new pathname against the previous one, and only call window.Appcues.page() if the path actually changed. Skip calls where only query parameters or hash fragments differ.

                    Confirm it worked

                    After setting up manual page tracking:

                    1. Open the Appcues Debugger.
                    2. Navigate between several pages in your app.
                    3. Confirm the Tracking Pages indicator turns green after each navigation without a full page refresh.
                    4. To test further, publish a Flow targeted to a specific page URL and restricted to your User ID. Navigate to that page — the Flow should appear without a refresh. If you need to refresh the page for the Flow to show, your page() call is in the wrong place or firing before the URL updates.

                    Still stuck?

                    Collect the following and email support@appcues.com:

                    • Your Appcues Account ID
                    • Your SPA framework and router library (with versions)
                    • The URL pattern where page tracking isn't working
                    • A screenshot of the Debugger after navigating between pages
                    • Any console errors related to Appcues

                    Related

                    • Install the Appcues SDK
                    • Identify users and groups
                    • Supported technologies and frameworks
                    • Troubleshoot element targeting — for dynamic selectors in SPAs
                    • Installation testing / debugging
                    development spa single page app automatic page detection enableurldetection react angular vue

                    Was this article helpful?

                    Yes
                    No
                    Give feedback about this article

                    Related Articles

                    • Appcues Installation Overview
                    • Build a Flow Across Pages
                    Appcues logo

                    Product

                    Why Appcues How it works Integrations Security Pricing What's new

                    Use cases

                    Appcues Integration Hub User Onboarding Software Feature Adoption Software NPS & Surveys Announcements Insights Mobile Adoption

                    Company

                    About
                    Careers

                    Support

                    Developer Docs Contact

                    Resources

                    The Appcues Blog Product Adoption Academy GoodUX Case studies Webinar Series Made with Appcues

                    Follow us

                    Facebook icon Twitter icon grey Linkedin icon Instagram icon
                    © 2022 Appcues. All rights reserved.
                    Security Terms of Service Privacy Policy

                    Knowledge Base Software powered by Helpjuice

                    Expand