Answer: I start by ensuring the core PWA criteria are met:
In create-react-app, much is scaffolded. I customize manifest.json and register the service worker:
// index.js
import * as serviceWorkerRegistration from './serviceWorkerRegistration';
serviceWorkerRegistration.register();
I also use Lighthouse to audit and verify compliance.
Answer: I use a service worker to cache essential assets (HTML, CSS, JS) and optionally API responses:
self.addEventListener('install', event => {
event.waitUntil(
caches.open('static-v1').then(cache =>
cache.addAll(['/index.html', '/main.css', '/main.js'])
)
);
});
For API caching:
self.addEventListener('fetch', event => {
if (event.request.url.includes('/api/')) {
event.respondWith(
fetch(event.request).catch(() => caches.match(event.request))
);
}
});
This ensures the app shell and some dynamic data are available offline.
Answer:
self.addEventListener('push', event => {
const data = event.data.json();
self.registration.showNotification(data.title, {
body: data.body,
icon: '/icons/icon-192.png'
});
});
On the React side, I integrate the subscription logic and send it to the backend for storage.
Answer:
idb) or localStorage.For writes, store the queued intent in IndexedDB before showing it as saved locally. Keep a stable operation ID across retries, with server-side idempotency to avoid applying the same write twice. A timeout does not prove that the server rejected the write: keep its status pending until an acknowledgement or status check confirms the outcome.
For a worked interview scenario, see Offline Email Client — free question and reference answer, which covers persistent drafts, incremental sync, and queued sending.
Read-cache example (the write queue needs separate storage and synchronization):
// Fallback to cache on failure
const getData = async () => {
try {
const res = await fetch('/api/data');
const json = await res.json();
localStorage.setItem('cachedData', JSON.stringify(json));
return json;
} catch {
return JSON.parse(localStorage.getItem('cachedData'));
}
};
Answer:
Answer:
manifest.json is valid and linked in index.html:<link rel="manifest" href="/manifest.json" />
name, short_name, start_url, display, icons, and theme_color.{
"name": "My App",
"short_name": "App",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#000000",
"icons": [
{ "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" }
]
}
beforeinstallprompt event.Answer:
navigator.onLine or window.addEventListener('online').Example:
window.addEventListener('online', () => {
// Process queued actions
});
The goal is eventual consistency without blocking the UI.
Answer:
console.log() inside service worker and check DevTools → Console → Service Worker.fetch and install events are firing.
self.addEventListener('activate', event => {
// Clean up old cache versions
});
Answer:
web-vitals to track metrics like FCP, LCP, and CLS.Answer: