The flow
Every time the URL changes, in order:- Cleanup phase. For each experiment currently active whose
Location no longer matches the new URL, run its
onCleanuphandlers (LIFO), disconnect any observers, remove<style data-abtestly-exp="...">tags. - Re-evaluate targeting. For each experiment, check Location and Audience against the new URL. Mid-session bucketing runs the deterministic hash, a visitor who was in Variant B on route one is still in Variant B on route two.
- CSS re-inject. For experiments that now apply, inject their
<style>blocks. - Apply. Run
onApplyhandlers with anApplyContextparameterized with the new route. - Re-eval page-visit goals. A page-visit goal for
/checkoutfires when the visitor navigates to/checkout, whether that navigation was a full load or a client-side push. - Re-arm triggered activations. Click and view triggers are re-installed for eligible experiments on the new route.
What triggers a route change
history.pushState, wrapped.history.replaceState, wrapped.popstate, listened to.hashchange, listened to.
window.__abtestlySpaWrapped guards
against double-wrapping if you accidentally load the snippet twice).
Same-URL pushes are deduped, a router that calls pushState(null, '', location.href) does not trigger a full cleanup and reapply.
The ApplyContext
EveryonApply callback receives an ApplyContext:
routeRevision, detecting supersession
If youronApply does async work (a fetch, a waitFor), by the time
it completes the visitor might have navigated away. Applying stale
work to the new page would be a bug.
spa_stale_cancelled event when this check
fires, visible in the dev debugger so you
can see supersessions happen in the wild.
Cleanup
onCleanup runs when the experiment is torn down, either because
the visitor navigated to a route the experiment does not match, or
because the experiment was un-published.
CSS is auto-torn-down
Any<style> block the runtime injected with a
data-abtestly-exp="<id>" attribute is auto-removed on cleanup. You
do not have to write a cleanup for CSS-only variants, the runtime
handles it.
JavaScript changes need their own cleanup because the runtime has no
way to know what you did.
The route-change event
The runtime dispatches anabtestly:routechange event on window
after re-evaluation completes:
Turning it on
Site → Settings → SPA support → Enable. See SPA panel. Publish-gated. Once on, everything above is automatic. Your variants opt into SPA awareness by usingonApply and onCleanup instead of running
naked code at the top level.