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:
- Works offline
- Installs to the home screen
- Has a service worker for caching
- Runs on any static host (Vercel, Netlify, GitHub Pages)
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:
- Install — caches all app files
- Activate — deletes old caches
- Fetch — serves from cache, falls back to network
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
- Open Chrome DevTools → Application → Service Workers
- Check "Offline" in the Network tab
- Refresh the page — it should still load
- On Android: three-dot menu → "Add to Home Screen"
Why Vanilla JS?
- Zero dependencies — nothing to update, nothing to break
- Fast — no build step, no transpilation
- Small — the entire app is under 10KB gzipped
- Understandable — you can read every line
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 — $29FAQ
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.