Make a Laravel Blade App Feel Like a Single Page Application (SPA) — Without Vue or React!
In a recent project, I wanted to keep my Laravel Blade setup super lightweight — no Vue, no React, no heavy front-end frameworks. But I still wanted smooth page transitions like a real SPA, without full-page reloads every time users clicked a link. So, I decided to build a simple AJAX page loader that: Loads only the page content inside the tag. Updates the URL without reloading the browser. Shows a loading spinner while fetching. Supports browser back and forward buttons! The result? ✔Lightning-fast page loads. ✔Smooth user experience. ✔Very little JavaScript code. Let’s dive into how I built it! Step 1: Write a Small JavaScript In my app.js, I added a simple AJAX fetch system. resources/js/app.js document.addEventListener('DOMContentLoaded', () => { const mainElement = document.querySelector('main'); function showLoader() { mainElement.innerHTML = ` `; } function loadPage(url, push = true) { showLoader(); fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' } }) .then(response => response.text()) .then(html => { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const newContent = doc.querySelector('main')?.innerHTML; if (newContent) { mainElement.innerHTML = newContent; if (push) { history.pushState(null, '', url); } } }) .catch(err => { console.error('Page load failed', err); mainElement.innerHTML = `Page failed to load.`; }); } document.body.addEventListener('click', e => { const link = e.target.closest('a[data-link]'); if (link) { e.preventDefault(); const url = link.getAttribute('href'); loadPage(url); } }); window.addEventListener('popstate', () => { loadPage(location.pathname, false); }); }); Step 2: Update Blade Links Every link you want to load via AJAX should have the data-link attribute: Dashboard Profile Settings When users click, the page content will update instantly inside , without a full browser reload! Step 3: (Optional) Improve UX with a Spinner Loader Before fetching a page, I display a simple CSS spinner using Tailwind CSS: This gives users feedback that something is happening — no more "dead time" during slow network requests. Final Result ✔Fast, smooth page loads. ✔Real URL updates (so users can bookmark/share pages). ✔Browser Back and Forward buttons work correctly. ✔Tiny JavaScript — no extra libraries needed! ✔Laravel Blade templates still do all the heavy lifting All with just plain Laravel, Blade, and a little JavaScript! If you're building a Laravel app and don't want the complexity of a full frontend framework, I highly recommend trying this out! Feel free to copy or improve this idea!

In a recent project, I wanted to keep my Laravel Blade setup super lightweight — no Vue, no React, no heavy front-end frameworks.
But I still wanted smooth page transitions like a real SPA, without full-page reloads every time users clicked a link.
So, I decided to build a simple AJAX page loader that:
- Loads only the page content inside the
tag.
- Updates the URL without reloading the browser.
- Shows a loading spinner while fetching.
- Supports browser back and forward buttons!
The result?
✔Lightning-fast page loads.
✔Smooth user experience.
✔Very little JavaScript code.
Let’s dive into how I built it!
Step 1: Write a Small JavaScript
In my app.js
, I added a simple AJAX fetch system.
resources/js/app.js
document.addEventListener('DOMContentLoaded', () => {
const mainElement = document.querySelector('main');
function showLoader() {
mainElement.innerHTML = `
`;
}
function loadPage(url, push = true) {
showLoader();
fetch(url, {
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.text())
.then(html => {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const newContent = doc.querySelector('main')?.innerHTML;
if (newContent) {
mainElement.innerHTML = newContent;
if (push) {
history.pushState(null, '', url);
}
}
})
.catch(err => {
console.error('Page load failed', err);
mainElement.innerHTML = `Page failed to load.`;
});
}
document.body.addEventListener('click', e => {
const link = e.target.closest('a[data-link]');
if (link) {
e.preventDefault();
const url = link.getAttribute('href');
loadPage(url);
}
});
window.addEventListener('popstate', () => {
loadPage(location.pathname, false);
});
});
Step 2: Update Blade Links
Every link you want to load via AJAX should have the data-link
attribute:
href="/dashboard" data-link>Dashboard
href="/profile" data-link>Profile
href="/settings" data-link>Settings
When users click, the page content will update instantly inside , without a full browser reload!
Step 3: (Optional) Improve UX with a Spinner Loader
Before fetching a page, I display a simple CSS spinner using Tailwind CSS:
class="flex justify-center items-center h-40">
class="animate-spin rounded-full h-10 w-10 border-t-2 border-b-2 border-blue-500">
This gives users feedback that something is happening — no more "dead time" during slow network requests.
Final Result
✔Fast, smooth page loads.
✔Real URL updates (so users can bookmark/share pages).
✔Browser Back and Forward buttons work correctly.
✔Tiny JavaScript — no extra libraries needed!
✔Laravel Blade templates still do all the heavy lifting
All with just plain Laravel, Blade, and a little JavaScript!
If you're building a Laravel app and don't want the complexity of a full frontend framework, I highly recommend trying this out!