Integrates swup.js (v4) into Magento 2 to enable smooth, animated page transitions without a full-page reload. Fully configurable from the Magento backend, compatible with Full Page Cache / Varnish, and aware of Magento's customer-section mechanism.
- Smooth page transitions (fade, slide in four directions, or none)
- Optional loading progress bar (no extra plugin required)
- Full Page Cache / Varnish compatible – swup reads the same cached HTML a regular browser request would receive
- Automatic CSRF
form_keyupdate after each transition - Automatic reload of Magento private/customer sections (mini-cart, wishlist, login state)
- Automatic re-execution of inline and external
<script>tags in replaced containers - Configurable container selector, link selector, and ignored paths
[data-no-swup]attribute to opt individual links out of swup navigation- Custom
swup:pageViewDOM event for analytics / tag manager integration - Per-store-view configuration
| Dependency | Version |
|---|---|
| PHP | >= 8.1 |
| Magento Framework | >= 103.0 (Magento 2.4.x) |
composer require zwernemann/magento2-swup
bin/magento module:enable Zwernemann_Swup
bin/magento setup:upgrade
bin/magento cache:flush- Copy the
app/code/Zwernemann/Swupdirectory into your Magento installation at the same path. - Run the setup commands:
bin/magento module:enable Zwernemann_Swup
bin/magento setup:upgrade
bin/magento cache:flushNavigate to Stores → Configuration → Zwernemann → Swup – Page Transitions.
| Setting | Default | Description |
|---|---|---|
| Enable swup.js | Yes | Master switch. Disabling this removes swup completely without touching any other setting. |
| Content Container Selector | main |
CSS selector for the element swup replaces on each navigation. Must be unique and present on every page. |
| Link Selector | a[href]:not(…) |
CSS selector for links intercepted by swup. External links, anchors (#), mailto:, and tel: links are excluded by default. |
| Setting | Default | Description |
|---|---|---|
| Page Transition Animation | Fade | Visual effect when switching between pages (see Animations). |
| Transition Duration (ms) | 400 |
Duration of the animation in milliseconds. Must match the generated CSS transition time. |
| Show Loading Progress Bar | Yes | Displays a thin progress bar at the top of the screen during page loads. |
| Progress Bar Color | #0099ff |
Any valid CSS color value (hex, rgb(), named color). |
| Progress Bar Height (px) | 3 |
Height of the progress bar in pixels. |
| Scroll to Top on Navigation | Yes | Automatically scrolls to the top of the page after each transition. |
| Setting | Default | Description |
|---|---|---|
| Enable swup Client-Side Cache | No | swup can cache fetched pages in memory for instant back-navigation. Keep disabled on stores with session-specific content (cart, customer data) to prevent stale data. Magento's own FPC is unaffected. |
| Ignore Paths | /checkout/customer/account/admin |
One URL path fragment per line. Any link whose href contains one of these strings will trigger a normal full-page request instead of a swup transition. |
| Value | Effect |
|---|---|
none |
Instant switch – no CSS transition |
fade |
Cross-fade (opacity) |
slide-left |
Old page slides left, new page enters from right |
slide-right |
Old page slides right, new page enters from left |
slide-up |
Old page slides up, new page enters from below |
slide-down |
Old page slides down, new page enters from above |
The required CSS is generated inline by the module and automatically uses the configured transition duration.
Add the data-no-swup attribute to any link (or a parent element) to force a full-page navigation:
<!-- Single link -->
<a href="/special-page" data-no-swup>Special page</a>
<!-- Entire navigation block -->
<nav data-no-swup>
<a href="/checkout">Checkout</a>
<a href="/login">Login</a>
</nav>After every successful page transition the module dispatches a custom DOM event that analytics tools and tag managers can listen to:
document.addEventListener('swup:pageView', function (event) {
console.log('New page:', event.detail.url, event.detail.title);
// Google Analytics 4 example:
gtag('event', 'page_view', {
page_location: event.detail.url,
page_title: event.detail.title
});
});event.detail contains:
| Key | Type | Value |
|---|---|---|
url |
string |
Full URL of the newly rendered page |
title |
string |
document.title after content replacement |
Magento generates per-page-type CSS bundles (different files for CMS pages, category pages, product pages, etc.). Because swup only replaces the configured container and leaves <head> untouched, CSS required by the new page might never be loaded — causing broken styles such as stretched videos or unexpected button appearances.
The module bundles the official SwupHeadPlugin (@swup/head-plugin v2) as a UMD build — no npm step required. On every navigation it diffs the fetched <head> against the current one, removes tags that are gone and adds tags that are new. Browsers do not re-download already-cached stylesheets because matching tags are kept in place.
Magento's RequireJS config scripts (<script type="text/x-magento-init">) and the module's own inline <style> tag are marked as persistTags so they are never removed from <head> during navigation.
swup fetches the target URL via an XHR request with Accept: text/html. Magento's FPC (and Varnish in front of it) serves the same cached full-page HTML that a regular browser would receive. swup then extracts only the configured container element from the response on the client side.
This means:
- The swup request is served from cache – no cache bypass.
- No Varnish VCL changes are required.
- The
X-Requested-With: swuprequest header is sent so custom server-side logic can detect swup requests if needed.
After each page transition the module calls customerData.reload([], false). This reloads all previously loaded private/customer sections from Magento's section API so personalised blocks (mini-cart counter, wishlist, logged-in greeting, etc.) reflect the current session state without a full page reload.
The CSRF form_key is also refreshed in the newly inserted content by reading the current form_key cookie value.
Browsers do not execute <script> tags that are injected into the DOM via innerHTML — which is exactly how swup replaces container content. Without special handling, any inline or external scripts embedded in the page content would silently do nothing after a swup navigation.
After each content:replace the module iterates over every <script> element in the replaced containers and recreates it as a fresh DOM node, causing the browser to treat it as a newly parsed script and execute it. All attributes (src, async, defer, data-*, …) are preserved.
Magento-specific script types are intentionally excluded from this re-execution:
| Type | Reason |
|---|---|
text/x-magento-init |
Processed by mage/apply/main when contentUpdated fires — re-executing them here would initialise widgets twice and cause errors. |
text/x-magento-template |
Knockout.js template blocks, not executable JavaScript. |
text/html / text/template |
Generic template containers, not executable JavaScript. |
This means third-party extension scripts, CMS block snippets, and custom inline scripts work automatically, while Magento's own widget bootstrapping path remains unchanged.
app/code/Zwernemann/Swup/
├── Block/
│ └── SwupInit.php # Block: renders config JSON for the template
├── Helper/
│ └── Config.php # Reads all backend settings; provides getJsConfig()
├── Model/Config/Source/
│ └── Animation.php # Dropdown options for the animation selector
├── etc/
│ ├── acl.xml # Admin ACL resource
│ ├── adminhtml/system.xml # Backend configuration fields
│ ├── config.xml # Default configuration values
│ └── module.xml # Module declaration
├── registration.php
└── view/frontend/
├── layout/default.xml # Injects the SwupInit block on every frontend page
├── requirejs-config.js # Maps "swup" alias to the bundled UMD build
├── templates/swup_init.phtml # Renders inline CSS + RequireJS bootstrap
└── web/js/
├── swup-init.js # swup initializer (RequireJS module)
└── swup.min.js # Bundled swup v4 UMD build (no npm required)
MIT