# SPA navigation

URL: https://docs.ablyft.com/developers/application-integration/spa-navigation/

> Run experiments on single-page applications, where the page changes without a reload.



On a classic website, every click loads a new page and the [snippet](https://docs.ablyft.com/developers/installation/snippet/) starts again.
In a **single-page application (SPA)** the browser loads the page once, and your code swaps the content afterwards.
ABlyft would then check pages and audiences only once, on the first load.

This page explains the settings and calls that make ABlyft follow your navigation.

## What you need to decide

There are two ways to tell ABlyft that "the page has changed". You can combine them.

| Approach              | How it works                                                                         | Use it when                                         |
| --------------------- | ------------------------------------------------------------------------------------ | --------------------------------------------------- |
| **Page trigger**      | ABlyft watches the URL or the DOM itself and re-checks a page when something changes | You do not want to touch your application code      |
| **`ablyft.reInit()`** | Your application calls ABlyft after each navigation                                  | Your router offers a hook, or you want full control |

Both need the project setting **Use SPA features**, described next.

## Project settings for SPAs

Open your project and go to **Settings → Project Settings**. The section **Project Activations Settings** contains:

| Setting                           | What it does                                                                                                                         |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| **Use SPA features**              | Enables the features for dynamic websites: page triggering via **URL Change** or **DOM Change**, and **Reset JS**.                   |
| **Run only on `ablyft.reInit()`** | The snippet is loaded, but starts only when your code calls [`ablyft.reInit()`](https://docs.ablyft.com/developers/reference/javascript-api/#ablyftreinit). |
| **Default Page Trigger**          | The trigger that newly created pages receive. Default: **Direct**.                                                                   |

With **Use SPA features** switched on, ABlyft additionally does the following:

* Before an experiment is applied again, ABlyft first **undoes** its previous run: it runs the [Reset JS](#reset-js)
  and removes the CSS that the experiment and its variation had added.
* On every new run of the project, the CSS that ABlyft added to the page is removed and added again, so styles do not pile up.
* `ablyft.reInit()` re-initializes **all experiments the visitor is already in**, so that resets run and goals are applied again.

> Without **Use SPA features**, Reset JS is never run automatically. Switch the setting on before you rely on
> resets.

## Option 1: Let ABlyft detect the navigation

Every page has a **Trigger** that defines *when* ABlyft checks the page's targeting. You set it on the page under
**Targeting & Goals → Pages**, in the section **Trigger**. The default of most projects is **Direct**: check once, when ABlyft starts.

For SPAs, choose **URL Change (history API)**, **URL Change (polling)** or **DOM Change**. What each trigger does is listed in
[Pages & URL targeting](https://docs.ablyft.com/guides/audiences-and-delivery/pages/#trigger-when-ablyft-checks-the-page).

At start, ABlyft checks every page once. After that, it checks the page again each time the trigger fires.

> Triggers are an advanced setting. If a page runs on a normal, reloading part of your site, leave it on **Direct**.

### What happens on each check

1. If the page **matches** (URL rules and optional JavaScript rules) and is not active yet, ABlyft activates it,
   triggers its pageview goals, and evaluates its experiments (environments, audiences, traffic).
2. If the page is already active and **no longer matches**, ABlyft deactivates it. What happens to its experiments
   depends on the page's **deactivation mode**.

### Deactivation modes

The page's **deactivation mode** (see [Pages & URL targeting](https://docs.ablyft.com/guides/audiences-and-delivery/pages/#deactivation-mode)) decides
whether Reset JS runs and whether the page stays active.

Choose **Reset** for most cases: the experiment disappears when the visitor navigates away from the page.

## Option 2: Call `ablyft.reInit()` yourself

`ablyft.reInit()` re-runs ABlyft on the current page: pages and audiences are checked again ("rerun condition check").
Call it from your router after the new view has been rendered.

```js
// Example: call after your app finished rendering a new route
function onRouteChanged() {
  if (window.ablyft && typeof window.ablyft.reInit === 'function') {
    window.ablyft.reInit();
  }
}
```

`reInit()` exists only after the snippet has loaded, hence the check. Pushing goals and other commands before that
works differently, see [queueing](https://docs.ablyft.com/developers/reference/javascript-api/#use-the-api-before-the-snippet-has-loaded).

To start from a clean state, for example after a login or language switch, use `ablyft.reInit({ resetStates: true })`. The options are described in the
[JavaScript API](https://docs.ablyft.com/developers/reference/javascript-api/#ablyftreinit).

### Run only on `ablyft.reInit()`

Turn on **Run only on `ablyft.reInit()`** if ABlyft should not start on its own. This is useful when the application
must be ready first (for example, the first view is rendered or the user data is loaded). The snippet still loads
and `window.ablyft` is available, but experiments only start with your first `reInit()` call.

> **Do not forget the call:** With this setting on and no `reInit()` call, no experiment runs at all.

### React to ABlyft runs

ABlyft dispatches the `ablyft_initialized` event on `document` on every run, including every `reInit()`, and supports a
`lifecycleEnd` listener that is called after each run. Both are documented in the
[JavaScript API](https://docs.ablyft.com/developers/reference/javascript-api/#events).

## Reset JS

Reset JS is code that **undoes** what your experiment code did, so that a variation can be removed again without a page reload.
You can set it in two places:

* the experiment: **Experiment Level Codes → Reset JS code**,
* a variation: **Code → Reset JS code**.

ABlyft runs it when an experiment is reset: before the experiment is applied again, and when a page is deactivated in
the mode **Reset** or **Force-reset** (see above).

For an example with variation code and the matching Reset JS code, see [A complete example](https://docs.ablyft.com/developers/application-integration/custom-javascript/#a-complete-example).

The CSS you added to experiments and variations is removed automatically; you only need to reset what your JavaScript changed.
More on code fields: [Custom JavaScript](https://docs.ablyft.com/developers/application-integration/custom-javascript/#where-you-can-add-code).

## Which setup should I choose?

| Your application                                    | Recommendation                                                                                                                           |
| --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| SPA that uses `pushState` routing                   | Pages with trigger **URL Change (… history API)**, deactivation mode **Reset**                                                           |
| URL does not change, but views switch (tabs, steps) | Trigger **DOM Change**, or a **Callback** / **API** trigger, see [Dynamic content](https://docs.ablyft.com/developers/application-integration/dynamic-content/) |
| You control the router and want precise timing      | `ablyft.reInit()` in a router hook, optionally with **Run only on `ablyft.reInit()`**                                                    |

## Troubleshooting

* **Experiment shows on the first page but not after navigation:** the page still has the trigger **Direct**, and no
  `reInit()` is called. Change the trigger or call `reInit()`.
* **Changes stay when leaving the page:** no Reset JS was added, or **Use SPA features** is off.
* **Experiment runs twice or flickers:** both an automatic trigger and `reInit()` are in use. A page that is already
  active is not activated again, but pick one approach where possible.
* Turn on [debug mode](https://docs.ablyft.com/developers/debugging/preview-and-live-log/#debug-mode) and watch the console. Every page
  activation is logged. More in [Debugging](https://docs.ablyft.com/developers/debugging/).

## Next steps

- [Dynamic content](https://docs.ablyft.com/developers/application-integration/dynamic-content/): Content that appears after load.
- [Custom JavaScript](https://docs.ablyft.com/developers/application-integration/custom-javascript/): Write experiment and reset code.
- [JavaScript API](https://docs.ablyft.com/developers/reference/javascript-api/): Reference for reInit(), events and more.

