Page Transitions in MPAs — Navigation Patterns and Full-Page Reloads
In this tutorial, you will learn about Page Transitions in MPAs. We cover key concepts, practical examples, and best practices to help you master this topic.
MPA page transitions involve full browser navigation with loading indicators, focus management, scroll restoration, and progressive enhancement techniques for smoother user experience.
What You'll Learn
By the end of this tutorial, you will understand how page navigation works in MPAs, how to manage loading states, preserve scroll position, handle focus for Accessibility, and progressively enhance navigation for a smoother feel.
Why It Matters
Full-page reloads are the defining characteristic of MPAs. While they provide clear visual feedback that navigation occurred, they also cause a flash of white and lose scroll position. Understanding how to manage and enhance these transitions is key to building MPAs that feel polished.
Real-World Use
The GOV.UK website uses MPAs with progressive enhancement. Basic navigation works with full page loads. JavaScript enhances the experience by adding loading indicators, preserving scroll position on back navigation, and managing focus for screen readers.
MPA Navigation Lifecycle
┌──────────────────────────────────────────────────────────┐
│ MPA Page Transition Sequence │
├──────────────────────────────────────────────────────────┤
│ │
│ 1. User clicks link │
│ │ │
│ 2. Browser cancels current page rendering │
│ │ │
│ 3. Page goes blank (or shows cached version) │
│ │ │
│ 4. Browser displays loading spinner (tab indicator) │
│ │ │
│ 5. Server processes request, returns HTML │
│ │ │
│ 6. Browser receives response, starts parsing │
│ │ │
│ 7. Browser renders new page (FCP, LCP) │
│ │ │
│ 8. Scroll position resets to top │
│ │ │
│ 9. Focus moves to the new page title │
│ │ │
│ 10. Page is fully interactive (TTI) │
│ │
└──────────────────────────────────────────────────────────┘
Think of MPA navigation like flipping pages in a physical book. Each page turn clears the previous page and shows new content. You lose your place (scroll position), and your eyes need to find where to start reading again (focus management). Progressive enhancement adds bookmarks and guides to make this Process smoother.
Loading Indicators
// Add a loading indicator for navigation
(function() {
let navigationStart = null;
const loadingBar = document.createElement('div');
loadingBar.id = 'loading-bar';
loadingBar.style.cssText = `
position: fixed; top: 0; left: 0; height: 3px;
background: #0066cc; z-index: 9999;
transition: width 0.3s ease;
width: 0;
`;
document.body.appendChild(loadingBar);
// Show loading bar on link clicks
document.addEventListener('click', (e) => {
const link = e.target.closest('a');
if (!link || link.target === '_blank') return;
if (link.hostname !== window.location.hostname) return;
navigationStart = Date.now();
loadingBar.style.width = '30%';
});
// Hide loading bar on page load
window.addEventListener('pageshow', () => {
loadingBar.style.width = '100%';
const elapsed = Date.now() - (navigationStart || Date.now());
setTimeout(() => {
loadingBar.style.width = '0';
}, Math.max(200, 500 - elapsed));
});
// Handle back/forward cache (bfcache)
window.addEventListener('pagehide', () => {
loadingBar.style.width = '0';
});
})();
Scroll Position Restoration
// Preserve and restore scroll position
(function() {
const scrollPositions = {};
// Save scroll position before navigation
document.addEventListener('click', (e) => {
const link = e.target.closest('a');
if (!link) return;
if (link.hostname !== window.location.hostname) return;
const key = window.location.pathname + window.location.search;
scrollPositions[key] = {
x: window.scrollX,
y: window.scrollY
};
// Store in sessionStorage for persistence across navigations
try {
sessionStorage.setItem('scrollPositions',
JSON.stringify(scrollPositions));
} catch (e) {
// sessionStorage might be full
}
});
// Restore scroll position on page load
window.addEventListener('load', () => {
// First try to restore from History API
if (window.history.state && window.history.state.scrollY) {
window.scrollTo(window.history.state.scrollX, window.history.state.scrollY);
return;
}
// Fall back to sessionStorage
try {
const saved = sessionStorage.getItem('scrollPositions');
if (saved) {
const positions = JSON.parse(saved);
const key = window.location.pathname + window.location.search;
const pos = positions[key];
if (pos) {
window.scrollTo(pos.x, pos.y);
}
}
} catch (e) {
// Ignore parse errors
}
});
// Save scroll state in history
let scrollTimeout;
window.addEventListener('scroll', () => {
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(() => {
const state = window.history.state || {};
state.scrollX = window.scrollX;
state.scrollY = window.scrollY;
window.history.replaceState(state, '');
}, 100);
});
})();
Focus Management for Accessibility
// Manage focus for keyboard and screen reader users
(function() {
function managePageFocus() {
// Find the main heading or first landmark
const heading = document.querySelector('h1') ||
document.querySelector('h2') ||
document.querySelector('[role="main"]') ||
document.querySelector('main');
if (heading) {
// Make focusable if not already
if (!heading.hasAttribute('tabindex')) {
heading.setAttribute('tabindex', '-1');
}
heading.focus({ preventScroll: true });
// Announce page change to screen readers
const announcer = document.getElementById('page-announcer') ||
createAnnouncer();
const pageTitle = document.title || heading.textContent;
announcer.textContent = `Navigated to ${pageTitle}`;
}
}
function createAnnouncer() {
const announcer = document.createElement('div');
announcer.id = 'page-announcer';
announcer.setAttribute('aria-live', 'polite');
announcer.setAttribute('aria-atomic', 'true');
announcer.style.cssText = `
position: absolute;
width: 1px; height: 1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
`;
document.body.appendChild(announcer);
return announcer;
}
// Run on page load
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', managePageFocus);
} else {
managePageFocus();
}
// Also run after dynamic content updates
document.addEventListener('turbo:load', managePageFocus);
document.addEventListener('htmx:afterSwap', managePageFocus);
})();
Common Mistakes
- Not managing focus on navigation. Screen reader users are sent back to the top of the page without knowing navigation occurred. Always move focus to the main heading after page load.
- Losing scroll position on back navigation. Users expect to return to their previous scroll position when pressing the back button. Use History API or sessionStorage to preserve scroll positions.
- No loading indication. Full-page reloads take time. Users need visual feedback that their click registered. Add a loading bar or spinner for navigation.
- Blocking navigation with JavaScript. Do not prevent default link behavior or add click handlers that delay navigation. Navigation should start immediately.
- Ignoring bfcache (Back-Forward Cache). The browser can cache pages for instant back/forward navigation. Set correct Cache-Control headers and avoid beforeunload handlers that disable bfcache.
Practice Questions
- What is the sequence of events during an MPA page transition?
- How do you preserve scroll position when the user navigates back?
- Why is focus management important for accessibility during page transitions?
- How does the bfcache improve navigation performance?
- What are some ways to provide loading feedback during page transitions?
Challenge: Add progressive enhancement to an MPA: implement a loading bar that appears on link clicks, preserve scroll position across navigations using sessionStorage, manage focus for screen readers by moving focus to the h1 on page load, and verify that the bfcache works correctly for back/forward navigation.
FAQ
Mini Project
Enhance an MPA with smooth navigation features: a CSS loading bar that activates on link clicks and completes on page load, scroll position preservation using sessionStorage for the last 10 pages, focus management that moves keyboard focus to the page heading on load, and a bfcache test to confirm pages restore instantly on back navigation.
What's Next
You understand page transitions. Now learn about Form Submissions in MPAs with validation, error handling, and the PRG pattern.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro