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!

Apr 26, 2025 - 10:59
 0
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:

 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">