What is a single-page app (SPA)?
Definition and purpose
A single-page application (SPA) is a type of web application designed to provide a smoother and faster user experience by dynamically updating content without reloading the entire page. SPAs are typically built using modern JavaScript frameworks like React, Next.js, or Vue.js. Many SPAs also utilize server-side rendering (SSR), which adds complexity to client-side implementations like A/B testing and personalization.
The primary advantage of SPAs is improving the visitor’s navigation experience by loading resources once and updating the content dynamically, rather than reloading the entire page after each action.
Implementation challenges and solutions
SPAs introduce unique challenges for tools like Kameleoon that rely on page loads to apply variations or run scripts. To ensure optimal performance, it is crucial to adapt the implementation strategy based on the type of SPA you’re working with.
In the following sections, we will outline best practices and procedures for handling each scenario, ensuring smooth integration and accurate tracking of visitor behavior across your website.
Native setup
If your website is a full-site SPA, enabling the “support for dynamic websites” option ensures Kameleoon looks for URL changes. When the URL changes, all Kameleoon scripts rerun, including targeting and variation code.
Kameleoon will also monitor updates in your SPA by using a MutationObserver to track changes in the DOM, even when the page URL remains static. This allows Kameleoon to apply (or reapply) variation changes dynamically as updates occur on your website. Kameleoon supports various modifications made through the graphic editor, including:
- Style changes
- Text modifications
- Position updates (swap, insert before/after)
- Custom CSS selectors
This approach ensures that all relevant changes are consistently applied in a dynamic environment.
To enable support for dynamic websites, navigate to Admin > Configuration > General > Advanced settings.

To better manage how elements are identified and avoid issues with dynamic attributes and selectors, you can use these two options: set a custom attribute or avoid dynamic ID selectors.
Set a custom attribute
You can define a custom attribute (for example, data-id
, data-qa
) to identify elements on the page. This is particularly useful when your site generates dynamic IDs for HTML elements.
The Kameleoon Graphic Editor identifies elements using CSS selectors, which fall into two categories:
- ID-Based Selection: If the element has an ID, Kameleoon will use it to locate and modify the element when the page loads.
- Combinator Selector: If no ID is found, Kameleoon creates a selector path using the nearest parent element with an ID.
In cases where your IDs are dynamic and change frequently, the Graphic Editor might struggle to identify elements. To resolve this issue, you can add a static custom attribute (for example, <button custom-id="1">
) that reliably identifies elements across updates.
Avoid dynamic ID selectors
By default, Kameleoon avoids using element IDs that contain dynamically generated numbers longer than five consecutive digits. Instead, it builds a path using a nearby static parent element with an ID.
You can customize this behavior by specifying a regular expression to exclude certain dynamic IDs, ensuring more accurate targeting.

Custom setup
If your website is not a full-site SPA or you need to handle specific parts of the site, refer to the section below.
There could be four different use cases for custom SPA setups.
Partial-site SPAs
To enable this option for specific pages, disable it from the Advanced settings and add the following code to your Global script:
if (document.location.href.includes('mySPApage')) { Kameleoon.API.Core.enableSinglePageSupport(); }
Replace “mySPApage” with your funnel’s URL.
Page changes without URL changes
In cases where the DOM updates without a URL change, rely on alternative indicators to reload Kameleoon. Below is an example using sessionStorage
to detect page changes. You can add the code to your Global script:
window.kam_step = JSON.parse(window.sessionStorage.getItem('formStep'))?.step; Kameleoon.API.Core.runWhenConditionTrue(() => { return window.kam_step != JSON.parse(window.sessionStorage.getItem('formStep'))?.step; }, () => { Kameleoon.API.Core.load(); });
Detect page changes with pathname
For pages where you need Kameleoon to rerun but only when the pathname changes (ignoring query parameters), detect changes using the pathname
instead of a full URL. The code below can be added to your Global script:
if (document.location.href.includes('configurator')) {
window.kam_configurator_url = document.location.pathname;
Kameleoon.API.Core.runWhenConditionTrue(() => { return window.kam_configurator_url != document.location.pathname; }, () => { Kameleoon.API.Core.load(); });
} else {
Kameleoon.API.Core.enableSinglePageSupport();
}
Reapply variation code without reloading Kameleoon
In some cases, certain page elements are dynamic, and when the code is rehydrated, it may overwrite the Kameleoon code that has already been applied. To prevent this, you have two options:
- You can inform Kameleoon when the page or a specific element has finished rendering by using one of the following methods:
- Define a window variable, for example,
window.pageLoadForKam = true
. - Trigger an event on the page that Kameleoon can listen for, for example, using
window.addEventListener('turbo:load')
. - Add a specific class or attribute to the
<body>
tag or the target element. Then, in the variation code, use this information to make changes to the page or specific elements.
- Define a window variable, for example,
- In the variation code, you can set the fourth argument of the
runWhenElementPresent
function totrue
. This argument enables support for dynamic elements and single-page applications (SPA). When this option is enabled, if the specified CSS selector for the element is not found by Kameleoon during the initial page load, Kameleoon will monitor the DOM for the selector. Once the element appears in the DOM, Kameleoon will execute the callback function. Additionally, if a new element matching the selector is later added to the page, Kameleoon will re-execute the callback with the newly added element.
Be careful: Use the code below sparingly and only when absolutely necessary. Limit its usage to the minimum number of cases and avoid applying it to multiple elements simultaneously to maintain optimal page performance. Be mindful of the potential for an infinite loop if the page’s source code modifies the element each time it is updated.
const insertMyNewCTA = () => {
if (document.querySelector("#kameleoonElement-myNewCTA") == null) {
Kameleoon.API.Core.runWhenElementPresent( '#myElement', ([myElement]) => { myElement.insertAdjacentHTML('beforebegin', "<button id='kameleoonElement-myNewCTA'>More info</button>"); }, null, true ); }
}
insertMyNewCTA();
General Considerations
Event listeners, timeouts, and intervals
Use Kameleoon.API.Utils
to manage event listeners, timeouts, or intervals. These methods ensure that listeners and timers are automatically removed when a URL change occurs without a page reload. These removals prevent duplicates when Kameleoon reruns.
Unique element IDs
When adding elements via Kameleoon, ensure their id
starts with KameleoonElement-
. This naming convention ensures the element is removed before Kameleoon reruns, avoiding duplication or conflicts.
Example:
<div id="KameleoonElement-myNewCTA"></div>
Page view incrementation
Kameleoon only counts a new page when all its scripts are re-executed, which happens under two conditions:
- When
Kameleoon.API.Core.enableSinglePageSupport()
is called, combined with a URL change. - When
Kameleoon.API.Core.load()
is explicitly triggered.
By following all the guidelines above, you can ensure that variations are implemented effectively and consistently across SPAs, enhancing user experience while maintaining data integrity. For any additional assistance, feel free to contact the Kameleoon team.
Alternatives
REACT and JS SDKs
Kameleoon offers a React SDK and a JavaScript/TypeScript SDK designed for single-page applications (SPAs). These SDKs provide an alternative method for running experiments and managing feature flags directly within your SPA by integrating the SDK into your application’s codebase.
For a comprehensive list of features available for SPAs, refer to our SDK documentation.
GatsbyJS plugin
Kameleoon also provides a dedicated plugin for GatsbyJS applications. To integrate Kameleoon into your GatsbyJS app, follow the instructions outlined in this article.
Next.js framework
To mitigate this issue, add an attribute to the <body> element once the page is hydrated. This attribute signals to Kameleoon that the page is ready for DOM modifications, preventing potential conflicts with Next.js hydration. Additionally, you can utilize Kameleoon’s command queue, triggered with Events.trigger(‘my page is hydrated’), to ensure synchronized actions with Next.js. You can then use the targeting criteria “Custom event” in your experiments. For more details on implementing custom events and other targeting criteria in Kameleoon experiments, refer to the documentation here.