Using RightMessage with Single Page Applications (SPAs)

If you're building with React, Vue, Angular, or any other SPA framework, you've probably noticed that RightMessage doesn't automatically pick up on your route changes. That's because SPAs don't do full page reloads – they just swap out content dynamically.

Here's how to fix that.

The Problem

Traditional websites trigger a fresh page load every time you navigate. SPAs don't. They update the URL and swap content, but as far as the browser's concerned, you're still on the same "page."

RightMessage needs to know when these transitions happen so it can:

  • Re-evaluate personalizations for the new route
  • Check if any widgets should display
  • Track the "pageview" properly
  • Update visitor context

The Solution (It's One Line)

Whenever your route changes, just tell RightMessage to reset:

RM.push(['reset'])

That's it. This tells RightMessage "hey, treat this like a brand new page load."

Now let's look at how to implement this in your framework of choice.

Framework Implementation Guides

Next.js

Pop this into your _app.js (or _app.tsx if you're using TypeScript):

// pages/_app.js
import { useEffect } from 'react'
import { useRouter } from 'next/router'

function MyApp({ Component, pageProps }) {
  const router = useRouter()
  
  useEffect(() => {
    const handleRouteChange = () => {
      if (window.RM) {
        window.RM.push(['reset'])
      }
    }
    
    router.events.on('routeChangeComplete', handleRouteChange)
    
    // Cleanup
    return () => {
      router.events.off('routeChangeComplete', handleRouteChange)
    }
  }, [router])
  
  return <Component {...pageProps} />
}

Vue.js (with Vue Router)

Add this to your router setup:

// router/index.js
const router = createRouter({
  // your router config
})

router.afterEach((to, from) => {
  // Only reset if we actually changed routes
  if (to.path !== from.path) {
    window.RM?.push(['reset'])
  }
})

React Router v6

For the latest React Router, use the useLocation hook:

import { useEffect } from 'react'
import { useLocation } from 'react-router-dom'

function App() {
  const location = useLocation()

  useEffect(() => {
    // Skip the initial mount
    if (window.RM) {
      window.RM.push(['reset'])
    }
  }, [location.pathname]) // Only fire on pathname changes

  return (
    // Your app routes
  )
}

React Router v5

Got an older React Router? Here are your options:

// Hook approach (functional components)
import { useHistory } from 'react-router-dom'

function App() {
  const history = useHistory()
  
  useEffect(() => {
    const unlisten = history.listen(() => {
      window.RM?.push(['reset'])
    })
    
    return unlisten
  }, [history])
}

// Class component approach
import { withRouter } from 'react-router-dom'

class App extends Component {
  componentDidMount() {
    this.unlisten = this.props.history.listen(() => {
      window.RM?.push(['reset'])
    })
  }
  
  componentWillUnmount() {
    this.unlisten()
  }
}

export default withRouter(App)

Angular

In your main app component:

// app.component.ts
import { Component, OnInit } from '@angular/core'
import { Router, NavigationEnd } from '@angular/router'
import { filter } from 'rxjs/operators'

declare global {
  interface Window { RM: any }
}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
  constructor(private router: Router) {}
  
  ngOnInit() {
    this.router.events.pipe(
      filter(event => event instanceof NavigationEnd)
    ).subscribe((event: NavigationEnd) => {
      if (window.RM) {
        window.RM.push(['reset'])
      }
    })
  }
}

Common Gotchas & How to Fix Them

"RM is not defined"

If you've modified how our script is loaded, you might end up dropping the RM variable we proactively add to the window object prior to loading our script. Always check for window.RM before calling it:

if (window.RM) {
  window.RM.push(['reset'])
}

Personalizations Not Updating

Make sure you're calling reset AFTER the route change completes, not before. Most routers have "after" hooks for this reason.

Double Tracking

Some routers fire multiple events per navigation. Add logic to prevent duplicate resets:

let lastPath = window.location.pathname

router.on('routeChange', () => {
  if (window.location.pathname !== lastPath) {
    lastPath = window.location.pathname
    window.RM?.push(['reset'])
  }
})

SSR/SSG Considerations

If you're server-side rendering, window won't exist during the build. Always check:

if (typeof window !== 'undefined' && window.RM) {
  window.RM.push(['reset'])
}

Testing Your Implementation

Want to make sure it's working? Here's a quick test:

  1. Enable debug mode: ?debug=true
  2. Navigate between routes in your SPA
  3. Verify personalizations or Flow changes update on route change
  4. Check that widgets display based on new URL rules

Advanced Patterns

Debouncing Rapid Navigation

If your app allows super fast navigation, you might want to debounce:

let resetTimeout

function debouncedReset() {
  clearTimeout(resetTimeout)
  resetTimeout = setTimeout(() => {
    window.RM?.push(['reset'])
  }, 100)
}

Selective Resets

Maybe you only want to reset on certain routes:

const significantRoutes = ['/products', '/pricing', '/signup']

router.on('routeChange', (url) => {
  if (significantRoutes.some(route => url.startsWith(route))) {
    window.RM?.push(['reset'])
  }
})

Custom Data on Route Change

You can also push custom data during resets:

router.on('routeChange', () => {
  window.RM?.push(['set', { 
    lastRoute: document.referrer,
    routeChangedAt: new Date().toISOString()
  }])
  window.RM?.push(['reset'])
})

Still Having Issues?

First, check the basics:

  • Is the RightMessage script loaded on all pages?
  • Are you seeing any console errors?
  • Is the reset actually being called? (Add a console.log to check)
  • Are your personalization rules URL-based? (They need to be for route changes to matter)

If you're still stuck, hit us up at support@rightmessage.com with:

  • Your framework and version
  • The implementation code you're using
  • What's happening vs. what you expect
  • Any console errors

We've helped hundreds of devs get this working, so don't hesitate to reach out. SPAs are tricky, but once you get the reset working, everything else just clicks into place.

Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.

Still need help? Contact Us Contact Us