Skip to main content
Laravel

Laravel PWA Complete Guide

Learn how to build a Progressive Web App with Laravel step by step.

Published 3 min read
Illustration of a Laravel Progressive Web App

A Progressive Web App (PWA) is a website that can be installed on a phone or desktop, opens in its own window and keeps working when the connection is poor. A PWA needs three things: a web app manifest, a service worker, and HTTPS. Laravel needs no special package for any of them. This guide adds each one by hand so you can see exactly what is happening.

What you need first

  • A working Laravel application served over HTTPS. Browsers allow service workers on localhost for development, but production must use HTTPS.
  • App icons in at least two sizes: 192x192 and 512x512 pixels, saved in public/icons.

Step 1: Create the manifest

The manifest tells the browser how your app should look when installed. Save this as public/manifest.json:

{
  "name": "My Laravel App",
  "short_name": "MyApp",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#0b1b3a",
  "icons": [
    { "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" }
  ]
}

Link it from your main Blade layout, inside the <head>:

<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#0b1b3a">

Step 2: Write a service worker

A service worker is a script that sits between your app and the network. Save this as public/sw.js. The service worker must be served from the site root so it can control every page.

const CACHE = "app-shell-v1";
const OFFLINE_URL = "/offline";

self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open(CACHE).then((cache) => cache.addAll([OFFLINE_URL]))
  );
  self.skipWaiting();
});

self.addEventListener("activate", (event) => {
  event.waitUntil(
    caches.keys().then((keys) =>
      Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))
    )
  );
  self.clients.claim();
});

self.addEventListener("fetch", (event) => {
  if (event.request.mode !== "navigate") return;
  event.respondWith(
    fetch(event.request).catch(() => caches.match(OFFLINE_URL))
  );
});

This worker caches an offline page when it installs, removes old caches when it activates, and shows the offline page only when a page navigation fails. Everything else goes straight to the network, so users never see stale data by accident.

Step 3: Add the offline route

In routes/web.php:

Route::view('/offline', 'offline');

Create resources/views/offline.blade.php with a short message such as "You are offline. Check your connection and try again."

Step 4: Register the service worker

Add this to your layout, just before the closing </body> tag:

<script>
  if ("serviceWorker" in navigator) {
    window.addEventListener("load", () => {
      navigator.serviceWorker.register("/sw.js");
    });
  }
</script>

Step 5: Test it

  1. Open the site in Chrome and open DevTools.
  2. In the Application panel, check that the manifest loads with no errors and that the service worker is activated.
  3. Tick Offline under the service worker settings and reload. You should see your offline page.
  4. Run a Lighthouse audit to confirm the app is installable.

Common problems

  • The install option never appears. Check the manifest for missing icons or a wrong start_url, and confirm the site uses HTTPS.
  • Changes do not show up. The old service worker is still active. Change the CACHE name, or use Update on reload in DevTools while developing.
  • The worker does not control the page. It was served from a sub-folder. Serve sw.js from the site root.

Keep caching simple

It is tempting to cache everything. Resist that. Start with the offline page, then add caching for specific static assets once you know they are safe to reuse. Never cache pages that show private or frequently changing data.

Need a PWA for your own Laravel project? Contact us and tell us what you are building.

Share this article
All articles →