Sep 7, 2026

Vanilla JavaScript PWA Tutorial: No React, No Next.js, Just JS

Every PWA tutorial starts with "install React, then Next.js, then configure Webpack..." By the time you're done, you have 200MB of node_modules for a to-do app.

This tutorial uses zero dependencies. Just HTML, CSS, and JavaScript. You'll have a working PWA in 10 minutes.

What We're Building

A simple web app that:

Step 1: The HTML

Create index.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>My PWA</title>
  <link rel="manifest" href="/manifest.json">
  <meta name="theme-color" content="#0F172A">
  <link rel="apple-touch-icon" href="/icon-192.png">
</head>
<body>
  <h1>Hello, PWA!</h1>
  <p>This app works offline.</p>
  <script src="/app.js"></script>
</body>
</html>

Step 2: The Manifest

Create manifest.json:

{
  "name": "My PWA",
  "short_name": "PWA",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#0F172A",
  "theme_color": "#06B6D4",
  "icons": [
    { "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" }
  ]
}

Step 3: The Service Worker

Create sw.js:

const CACHE = 'my-pwa-v1';
const ASSETS = ['/', '/index.html', '/app.js', '/style.css'];

self.addEventListener('install', e => {
  e.waitUntil(
    caches.open(CACHE).then(cache => cache.addAll(ASSETS))
  );
});

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

self.addEventListener('fetch', e => {
  e.respondWith(
    caches.match(e.request).then(r => r || fetch(e.request))
  );
});

The service worker does three things:

Step 4: Register the Service Worker

Create app.js:

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js')
    .then(reg => console.log('SW registered:', reg.scope))
    .catch(err => console.log('SW failed:', err));
}

Step 5: Deploy

If you're using Vercel:

npm i -g vercel
vercel deploy --prod

That's it. Your PWA is live.

Testing Offline

  1. Open Chrome DevTools → Application → Service Workers
  2. Check "Offline" in the Network tab
  3. Refresh the page — it should still load
  4. On Android: three-dot menu → "Add to Home Screen"

Why Vanilla JS?

React and Next.js are great for complex apps. But for a simple PWA? Vanilla JS is enough.

Want a head start?

TinyCoder is a vanilla JS PWA with 5 offline dev tools. Config-driven, deployable in minutes.

Get TinyCoder — $29

FAQ

Do I need HTTPS?
Yes. Service workers only work over HTTPS. Vercel, Netlify, and GitHub Pages all provide this by default.

Does this work on iOS?
Yes. Add the apple-touch-icon meta tag and users can install via Share → Add to Home Screen.

Can I use this with a backend?
The service worker caches static assets. API calls still need a network connection unless you cache them explicitly.