Support
Komma igång
Using the Weply widget on Single-Page Application (SPA) websites

Using the Weply widget on Single-Page Application (SPA) websites

Updated:
May 21, 2026

Single-Page Applications (SPAs) navigate between views without triggering a full browser page reload. Because the Weply widget initialises when the page loads, it does not automatically detect route changes in an SPA, meaning it cannot update its state, track the visitor's journey, or apply URL-based rules (such as show/hide lists and proactive messages) after the initial load.

This guide explains how to configure the widget so it behaves correctly on SPA sites.

How it works

The widget exposes a reloadWidget() method through its public API. By calling this method every time the user navigates to a new route, you simulate the behaviour of a traditional multi-page website. The widget checks the current URL, updates the visitor journey, and applies any URL-based rules you have configured.

Step 1: Expose the widget API globally

Add the following script to your main index.html file, alongside the widget script:

<script type="text/javascript"> 
// This function is called every time the widget finishes loading
  window.weplyWidgetReady = function (api) { 
  // Expose the widget API only on the first load
  if (!window.weplyWidget) {      
    window.weplyWidget = api.widget;      
    // Optional: hide the widget on first load for a smoother experience      
    // See "Hiding the widget on first load" below      
    window.weplyWidget.setVisibility(false);    
  	}  
  };
</script>

Important: Assign window.weplyWidget only on the first load. Every widget reload triggers weplyWidgetReady again,. reassigning the API on every reload can cause unintended side effects, such as the widget hiding again unexpectedly. On a full browser refresh, the window object is cleared and the API is re-exposed automatically.

Step 2: Reload the widget on every route change

Call reloadWidget() whenever the user navigates to a new page or view. Replace the website ID with your own:

const weplyWidget = window.weplyWidget;
if (weplyWidget) {  
	weplyWidget.reloadWidget('YOUR_WEBSITE_ID');
}

Call reloadWidget() on every route change - including pages where the widget should be hidden. The widget uses each reload to detect the current URL, update the visitor journey, and determine whether it should be visible or hidden based on your configuration.

Optional: load the widget minimized

You can pass a second parameter to load the widget in its minimized state:

weplyWidget.reloadWidget('YOUR_WEBSITE_ID', true);

If the parameter is omitted, the widget keeps its current state (minimized or expanded).

Hiding the widget on first load (recommended)

Without this step, the widget may appear briefly before the first reloadWidget() call runs, then reload again, creating a visible "blink". To avoid this, hide the widget on the initial load and let reloadWidget() control when it first appears:

<script type="text/javascript">
  window.weplyWidgetReady = function (api) {
    if (!window.weplyWidget) {
      window.weplyWidget = api.widget;
      window.weplyWidget.setVisibility(false); // Hide on first load
    }
  };
</script>

With this in place:

  1. The widget loads silently in the background
  2. The page finishes loading
  3. reloadWidget() runs and the widget appears correctly for the current URL

Note: If you are loading the widget manually for the first time (e.g. triggered by a button click), do not use setVisibility(false) on first load. In this case, weplyWidgetReady fires when the widget is first loaded manually, but reloadWidget()does not run automatically afterward — so the widget may remain hidden with no subsequent reload to show it.

Framework examples

Plain JavaScript (History API)

This approach works with any SPA that uses the browser History API for navigation. Add it to your main index.html:

<script>
  // Keep track of the last known route
  let lastUrl = location.pathname + location.search;

  // Widget ready hook
  window.weplyWidgetReady = function (api) {
    if (!window.weplyWidget) {
      window.weplyWidget = api.widget;
    }
  };

  // Central reload handler
  function reloadWeplyWidgetIfNeeded() {
    const currentUrl = location.pathname + location.search;

    if (currentUrl === lastUrl) return;

    lastUrl = currentUrl;

    if (window.weplyWidget) {
      console.log('Reloading Weply widget for route:', currentUrl);
      // Replace with your website ID
      window.weplyWidget.reloadWidget('YOUR_WEBSITE_ID');
    }
  }

  // Hook into History API (pushState + replaceState)
  (function (history) {
    const originalPushState = history.pushState;
    const originalReplaceState = history.replaceState;

    history.pushState = function () {
      const result = originalPushState.apply(history, arguments);
      setTimeout(reloadWeplyWidgetIfNeeded, 0);
      return result;
    };

    history.replaceState = function () {
      const result = originalReplaceState.apply(history, arguments);
      setTimeout(reloadWeplyWidgetIfNeeded, 0);
      return result;
    };
  })(window.history);

  // Back/forward navigation
  window.addEventListener('popstate', () => {
    setTimeout(reloadWeplyWidgetIfNeeded, 0);
  });
</script>

Angular

Place the logic in AppComponent so it runs for the entire application lifetime. Using NavigationEnd ensures the widget reloads only after each navigation completes:

ngOnInit(): void {
  this.router.events
    .pipe(
      filter((event: any) => event instanceof NavigationEnd)
    )
    .subscribe(() => {
      const weplyWidget = (window as any).weplyWidget;
      if (weplyWidget) {
        weplyWidget.reloadWidget('YOUR_WEBSITE_ID');
      }
    });
}


Alternatively, a route guard is also a suitable place for this logic.

Multiple widgets

If your site loads more than one Weply widget, expose the API for each widget separately using the website_id to distinguish them:

window.weplyWidgetReady = function (api) {
  if (!window.weplyWidget1 && api.website_id === 'ID1') {
    window.weplyWidget1 = api.widget;
  } else if (!window.weplyWidget2 && api.website_id === 'ID2') {
    window.weplyWidget2 = api.widget;
  }
};

Then reload both widgets on every route change:

if (window.weplyWidget1) window.weplyWidget1.reloadWidget('ID1');
if (window.weplyWidget2) window.weplyWidget2.reloadWidget('ID2');


Handling overlapping widget iframes

When multiple widgets are loaded on the same page, their iframes may overlap. A hidden widget's iframe can sit on top of a visible one and block interaction with it, even though it appears invisible.

To prevent this, lower the z-index of the inactive widget's iframe after each reload. Do not remove the iframe entirely, as this may interfere with widget state:

const id = 'ID_OF_WIDGET_TO_DEACTIVATE';
const frame = [...document.querySelectorAll('iframe')]
  .find(frame => frame.src.includes(id));

if (frame) {
  frame.style.zIndex = '0';
}

Run this logic on every page change, after the widgets have reloaded. The widget restores the iframe to its original state on the next reload, so the z-index adjustment needs to be reapplied each time.

Recommended flow for multiple widgets on each route change:

  1. Reload all widgets
  2. Wait for weplyWidgetReady to fire for each widget
  3. Determine which widget should be inactive on the current page
  4. Lower the z-index of its iframe

Chat triggers

If you use chat triggers to open or load the widget from a button or link on your site, there are a few things to keep in mind in an SPA context:

  • Opening an already-loaded widget (window.weplyWidget.maximize()) works as expected, as long as the widget is currently visible on the page.
  • Loading the widget for the first time via a trigger is supported, but the widget can only call reloadWidget() after it has been initialised at least once. Before that first load, navigating between routes will not trigger a reload.

For more on chat triggers, see: Using chat triggers effectively

Your website ID

Your website ID is the unique identifier in your widget script tag:

<script src="https://app.weply.chat/widget/YOUR_WEBSITE_ID" async></script>

The same ID must be passed to reloadWidget(). If the IDs do not match, the widget will not reload correctly.