Posted on 22/01/2026 16:24:39
It is intended - as we designed to make the UX flow fine.
When you do it, the page content changes fast and interactively - but the header section does not. The URL does change - and if you reload the page, the header section is for the variant as well.
Our JS fires events - you should be able to overrule how we navigate - below untested version of how that can be done.
There is an interception point: selectioncomplete.swift.variantselector is fired before the URL update + PageUpdater.Update(...) fetch happens, and it’s cancelable (our code checks dispatchEvent(...) != false). So you can create a script that calls preventDefault() and do a full navigation instead.
Option A (best): cancel the async swap and do a real page load to the selected variant
Drop this on the product page (after Swift scripts are loaded):
<script>
(function () {
function buildVariantUrl(variantSelectorEl, selections) {
// Mirror Swift logic: base-url OR current URL with variantid param
var variantid = selections.join(".");
var baseUrl = variantSelectorEl.getAttribute("data-base-url");
if (baseUrl) {
// baseUrl is already a full URL (often includes querystring)
return baseUrl + "&variantid=" + encodeURIComponent(variantid);
}
var url = new URL(window.location.href);
url.searchParams.set("variantid", variantid);
return url.toString();
}
// Attach locally to each selector so we can access e.target reliably
document.querySelectorAll(".js-variant-selector").forEach(function (vs) {
vs.addEventListener("selectioncomplete.swift.variantselector", function (e) {
// Stop Swift from calling PageUpdater.Update(...)
e.preventDefault();
var url = buildVariantUrl(vs, e.detail && e.detail.selections ? e.detail.selections : []);
// Full reload (updates <head>, canonical, meta, structured data, etc.)
window.location.href = url;
});
});
})();
</script>
How this works:
-
selectioncomplete.swift.variantselector is dispatched before window.history.replaceState(...) and before PageUpdater.Update(variantSelectorElement) is called.
-
Because it’s cancelable: true, e.preventDefault() makes dispatchEvent(...) return false, and your code short-circuits the inline update.
Option B (fallback): let it swap inline, then immediately reload the page
This is the “brute force” version: it allows the fetch + DOM swap, then reloads. It’s less efficient (double work), but sometimes partners prefer “no logic, just reload”.
<script>
(function () {
// Fires after fetch completed and HTML is available (but before swap decision completes)
document.addEventListener("updated.swift.pageupdater", function () {
window.location.reload();
}, { once: true });
})();
</script>
Notes:
-
updated.swift.pageupdater is fired after the fetch returns HTML (response.text()), right before/around the replacement path.
-
If you want to reload after the DOM has been replaced, hook swapped.swift.pageupdater instead (it’s dispatched after innerHTML = html in Success).
Tiny gotcha worth calling out
If you use Option B, make it { once: true } (as above) or you can get reload loops if other parts of the page also trigger page updates.