Build a Simple PWA in 15 Minutes (Step-by-Step)

Explore beginner-friendly guides, step-by-step tutorials, and expert tips to build fast, installable, offline-ready Progressive Web Apps.

Service workers, manifest file, offline support — a beginner-friendly, fastest tutorial to build a Progressive Web App (PWA) you can install and test in ~15 minutes. No frameworks required: plain HTML, CSS and JavaScript.

PWA tutorial preview

Updated: November 2025 • Author: MaxonCodes

Info!
This guide focuses on a minimal, practical PWA that adds a manifest, registers a service worker, caches assets, supports offline fallback, and prompts install — all in one small project. Follow the exact steps and you'll have a working PWA in about 15 minutes.

What you'll build — TL;DR

We’ll create a tiny app with:

  1. index.html (UI + registration code)
  2. manifest.json (app metadata)
  3. service-worker.js (caching + offline)
  4. icons (few sizes) and HTTPS friendly hosting

Result: a site that loads instantly from cache, works offline, and can be installed on phones & desktops as a PWA.

Why PWAs matter (short)

Progressive Web Apps combine best of web + native: fast load, offline capability, installable, and engageable via push notifications. They improve retention and conversion without app stores.

Tip:
You don’t need a build tool to do this tutorial — plain files and a static host (Vercel, Netlify, GitHub Pages) are enough. For local testing, use a simple static server (instructions below).


Project structure (what files you’ll create)

 pwa-demo/ ├─ index.html ├─ styles.css ├─ app.js ├─ manifest.json ├─ service-worker.js └─ icons/ ├─ icon-192.png └─ icon-512.png 

Step 0 — prerequisites

  • Basic HTML/CSS/JS knowledge
  • Node.js (optional, for a local static server) or Python
  • HTTPS host (Netlify/Vercel/GitHub Pages) for production PWA — for local testing use http://localhost

Step 1 — Create a minimal index.html

Create index.html with UI + manifest link + service worker registration. Paste this exact file:

<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>PWA Demo — MaxonCodes</title> <link rel="manifest" href="/manifest.json"> <meta name="theme-color" content="#0ea5a4"> <link rel="icon" href="/icons/icon-192.png"> <link rel="stylesheet" href="/styles.css"> </head> <body> <main class="app"> <h1>PWA Demo</h1> <p>This is a simple Progressive Web App example — works offline and installable.</p> <button id="btn-refresh">Refresh cache & update</button> <p id="status">Status: <em>online</em></p> </main> <script src="/app.js"></script> </body> </html>

Info!
Make sure your manifest link path is correct. If you serve from a subfolder, adjust paths accordingly.

Step 2 — Add a basic manifest.json

Create manifest.json at the site root — this tells the browser about your app and enables install prompt.

{ "name": "PWA Demo - MaxonCodes", "short_name": "PWA Demo", "description": "A tiny Progressive Web App demo with offline support.", "start_url": "/", "scope": "/", "display": "standalone", "background_color": "#0ea5a4", "theme_color": "#0ea5a4", "icons": [ { "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" }, { "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" } ] }

Icons: include at least 192×192 and 512×512 PNGs. You can generate them from a single large image with tools like ImageMagick or online favicon generators.

Step 3 — Create a minimal stylesheet (styles.css)

body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial;display:flex;min-height:100vh;align-items:center;justify-content:center;background:#f8fafc;color:#0f172a;margin:0} .app{max-width:720px;padding:24px;border-radius:12px;background:#ffffff;box-shadow:0 8px 30px rgba(2,6,23,0.08);text-align:center} button{background:#0ea5a4;color:#fff;padding:10px 16px;border:none;border-radius:8px;cursor:pointer} #status em{color:#0ea5a4;font-weight:600}

Step 4 — Add service-worker.js (cache assets + offline fallback)

Create service-worker.js at the root. This service worker implements a simple cache-first strategy and an offline fallback page.

const CACHE_NAME = "pwa-demo-v1"; const ASSETS = [ "/", "/index.html", "/styles.css", "/app.js", "/icons/icon-192.png", "/icons/icon-512.png" ]; // Install: cache essential assets self.addEventListener("install", (event) => { event.waitUntil( caches.open(CACHE_NAME).then((cache) => cache.addAll(ASSETS)) ); self.skipWaiting(); }); // Activate: clean old caches self.addEventListener("activate", (event) => { event.waitUntil( caches.keys().then((keys) => Promise.all( keys.map((key) => { if (key !== CACHE_NAME) return caches.delete(key); }) )) ); self.clients.claim(); }); // Fetch: cache-first strategy with fallback self.addEventListener("fetch", (event) => { if (event.request.method !== "GET") return; event.respondWith( caches.match(event.request).then((cached) => { if (cached) return cached; return fetch(event.request).then((response) => { // Optionally cache new requests return caches.open(CACHE_NAME).then((cache) => { cache.put(event.request, response.clone()); return response; }); }).catch(() => caches.match("/index.html")); // offline fallback }) ); });

Tip:
This is a minimal example. For production, consider runtime caching, stale-while-revalidate, and caching only for same-origin assets to avoid caching third-party responses inadvertently.

Step 5 — Register the service worker & handle install prompt (app.js)

Create app.js to register the service worker, show status, and handle manual cache update.

const statusEl = document.getElementById("status"); const btnUpdate = document.getElementById("btn-refresh"); if ("serviceWorker" in navigator) { navigator.serviceWorker.register("/service-worker.js") .then((reg) => { console.log("SW registered", reg); statusEl.innerHTML = 'Status: service worker registered'; }) .catch((err) => { console.error("SW registration failed", err); statusEl.innerHTML = 'Status: sw registration failed'; }); } else { statusEl.innerHTML = 'Status: service worker not supported'; } // Optional: simple "update" button that prompts new service worker to take control btnUpdate.addEventListener("click", async () => { if (navigator.serviceWorker.controller) { statusEl.innerHTML = 'Status: Updating cache...'; const reg = await navigator.serviceWorker.getRegistration(); if (reg && reg.waiting) { reg.waiting.postMessage({ action: "skipWaiting" }); } else if (reg) { reg.update(); statusEl.innerHTML = 'Status: Checked for update'; } } }); // Listen for online/offline window.addEventListener("online", () => statusEl.querySelector("em").textContent = "online"); window.addEventListener("offline", () => statusEl.querySelector("em").textContent = "offline");

Step 6 — Add messaging to service worker for skipWaiting

To allow immediate activation when a new SW is installed, add a message listener in service-worker.js (append):

// inside service-worker.js self.addEventListener("message", (event) => { if (event.data && event.data.action === "skipWaiting") { self.skipWaiting(); } });

Step 7 — Add icons

Create an icons/ folder with at least:

  • icon-192.png (192×192)
  • icon-512.png (512×512)

Use a square image, exported to PNG. There are online generators (e.g., RealFaviconGenerator) that create all required sizes and HTML snippets.

Warning!
PWAs require HTTPS in production — except on localhost. Deploy to a secure host when you go live (Netlify, Vercel, Cloudflare Pages, GitHub Pages with HTTPS enabled).

Step 8 — Test locally (quick)

Run a local static server and visit http://localhost:

# Using npm http-server (quick) npx http-server -c-1 -p 8080 # Or Python 3 python -m http.server 8080 

Open DevTools → Application → Service Workers to confirm registration. Use Lighthouse (Audits) to run a PWA audit.

Step 9 — Deploy to production (fast options)

Recommended free/fast hosts:

  • Vercel: connect repo, push to main — automatic HTTPS & CDN
  • Netlify: drag-and-drop or connect Git repo — automatic HTTPS
  • GitHub Pages: use with gh-pages or static export (ensure HTTPS)

Once deployed over HTTPS, visiting the site on mobile should prompt an install banner (or the browser will show “Install app” in the address bar / menu).

Step 10 — Verify PWA & Core Web Vitals

Open Chrome DevTools → Lighthouse → run an audit (PWA, Performance). Key checks:

  • Service worker registered
  • Has a valid manifest
  • Is installable
  • Works offline (controlled by service worker)

Optional Enhancements (go beyond 15 minutes)

  • Use Workbox for advanced caching strategies (precache + runtime caching + routing)
  • Implement stale-while-revalidate or network-first for API responses
  • Add Push Notifications (requires server-side subscription handling)
  • Use IndexedDB for structured offline data storage
  • Implement background sync to retry failed requests
Success! You now have a minimal, installable PWA with offline support. This is production-ready for small sites — enhance caching rules & assets for larger apps.

Troubleshooting (quick)

ProblemFix
Service worker not registeringCheck console for errors, ensure service-worker.js is served from root and site is on localhost or HTTPS.
Install prompt not showingEnsure manifest exists, icons valid, page served over HTTPS and service worker is active.
Offline page shows blankMake sure that index.html is cached and that your fetch fallback returns cached page.
How does caching strategy affect updates?

If you use cache-first, users might see stale content until new service worker activates. Use versioned cache names, call skipWaiting() on install, and prompt the user to refresh when a new SW is available.

SEO & PWA best practices

  • Ensure each page has unique <title> and <meta name="description">
  • Provide structured data (Article/Product) as JSON-LD
  • Pre-render important pages and use server-side rendering for SEO-critical content if necessary
  • Keep the manifest updated and provide accurate start_url and scope
Should every website be a PWA?

Not necessarily. PWAs are highly useful for sites with repeat visitors, mobile-focused audiences, or apps requiring offline/installation. For single-visit brochure sites, it may not be worth the extra work.

FAQs

1. What is the minimum requirement for a PWA?

At minimum: a valid manifest.json, a registered service worker, served over HTTPS (or localhost for dev), and at least one icon.

2. Do PWAs work on iOS?

Yes — iOS supports many PWA features via “Add to Home Screen,” but service worker and some APIs have limitations. iOS Safari requires specific meta tags (apple-touch-icon) and doesn't always show the install prompt.

3. How do I update cached assets?

Use cache versioning: change CACHE_NAME. On activate, remove old caches. Or implement a strategy to revalidate and update caches (Workbox helps a lot).

4. Can I add push notifications?

Yes — but push requires a backend to manage subscriptions and send push messages via Push API (VAPID keys). This is an advanced feature beyond this 15-minute tutorial.

5. How can I make my PWA installable on desktop?

Desktop Chrome shows an install button in the address bar when manifest + service worker + HTTPS are present. You can also use the beforeinstallprompt event to control when to show your own install CTA.

Credit:
Step-by-step PWA tutorial by MaxonCodes.com. Use this as a base for learning or production — customize caching and data strategies for real apps.

Post a Comment