Handling Multiple Layouts
Sites often have multiple page layouts - for example, a default layout and a sidebar layout for documentation pages. SWUP only replaces content inside its container (#swup), so elements outside the container (like a sidebar) persist during transitions.
This creates a problem: navigating from a sidebar page to a non-sidebar page leaves the sidebar visible because SWUP only swapped the main content.
The Solution: Detect Layout Changes
The cleanest approach is to detect when the layout changes and force a full page reload. SWUP provides access to the incoming page's document before the transition completes:
function hasSidebarLayout(doc) {
return doc.body.classList.contains('with-sidebar');
}
swup.hooks.on('page:view', (visit) => {
const currentHasSidebar = hasSidebarLayout(document);
const newHasSidebar = hasSidebarLayout(visit.to.document);
if (currentHasSidebar !== newHasSidebar) {
window.location.href = visit.to.url;
return;
}
});
This gives you the best of both worlds:
- Same layout transitions: Smooth SWUP animations (sidebar→sidebar, default→default)
- Cross-layout transitions: The current page fades out, then a full reload brings in the new layout
The fade-out before reload creates a pleasant effect - it feels intentional rather than jarring, providing a graceful handoff between layout types.
Layout Setup
Each layout template needs the SWUP container on its main content area:
{# default.njk #}
<body class="{{ bodyClasses }}">
{% include "header.njk" %}
<main class="transition-fade" id="swup">
{{ content }}
</main>
{% include "footer.njk" %}
</body>
{# with-sidebar.njk #}
<body class="with-sidebar {{ bodyClasses }}">
{% include "header.njk" %}
<aside class="sidebar">...</aside>
<main class="transition-fade" id="swup">
{{ content }}
</main>
</body>
The with-sidebar class on the body element is what the layout detection checks for.