Offline-first requires caching resources and data via Service Worker for working without internet.
System design context: An offline-first app treats the network as an optimization, not a requirement. The app should be fully functional from cache, and sync changes when connectivity returns. This requires:
Service Worker setup (Vite/PWA):
1npm install vite-plugin-pwa
1// vite.config.ts2import { VitePWA } from "vite-plugin-pwa";34export default defineConfig({5 plugins: [6 VitePWA({7 registerType: "autoUpdate",8 workbox: {9 globPatterns: ["**/*.{js,css,html,ico,png,svg}"],10 runtimeCaching: [11 {12 urlPattern: /^https?:\/\/api\.example\.com\/.*$/,13 handler: "NetworkFirst",14 options: {15 cacheName: "api-cache",16 expiration: { maxEntries: 50, maxAgeSeconds: 3600 }17 }18 },19 {20 urlPattern: /^https?:\/\/.*\.(png|jpg|jpeg|svg|gif|webp)$/,21 handler: "CacheFirst",22 options: {23 cacheName: "image-cache",24 expiration: { maxEntries: 100, maxAgeSeconds: 30 * 24 * 60 * 60 }25 }26 }27 ]28 },29 manifest: {30 name: "My Offline App",31 short_name: "MyApp",32 icons: [33 { src: "/icon-192.png", sizes: "192x192", type: "image/png" },34 { src: "/icon-512.png", sizes: "512x512", type: "image/png" }35 ]36 }37 })38 ]39});
React hook for online status:
1function useOnlineStatus() {2 const [isOnline, setIsOnline] = useState(navigator.onLine);34 useEffect(() => {5 const handleOnline = () => setIsOnline(true);6 const handleOffline = () => setIsOnline(false);7 window.addEventListener("online", handleOnline);8 window.addEventListener("offline", handleOffline);9 return () => {10 window.removeEventListener("online", handleOnline);11 window.removeEventListener("offline", handleOffline);12 };13 }, []);1415 return isOnline;16}1718// Offline indicator19function OfflineBanner() {20 const isOnline = useOnlineStatus();21 if (isOnline) return null;22 return (23 <div className="bg-yellow-100 text-yellow-800 p-2 text-center">24 You are offline. Data may be outdated.25 </div>26 );27}
Caching strategies:
CacheFirst — serves from cache, falls back to network. Best for static assets (fonts, images, JS bundles).NetworkFirst — tries network, falls back to cache. Best for API data that should be fresh.StaleWhileRevalidate — serves cache immediately, updates in background. Best for semi-dynamic data (user profiles, settings).NetworkOnly — always fetches from network. Best for analytics/tracking requests.CacheOnly — always serves from cache. Best for offline-only data.Production pitfalls:
registerType: "autoUpdate" to auto-activate new workers.idb library for larger offline data stores.Monitoring: Track offline usage rates, cache hit ratios, and background sync success rates.