What if your video platform could work even when the user's connection is unreliable? Here's how I implemented offline-first capabilities for TrendVidStream using Service Workers and the Cache API.
Why Offline-First for Video?
Not all our users have stable connections. TrendVidStream serves 8 regions including countries where mobile internet can be intermittent. Offline-first ensures:
- Previously visited pages load instantly
- Navigation works even with spotty connectivity
- The shell (header, categories, footer) always renders
Service Worker Registration
// sw-register.js
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
try {
const reg = await navigator.serviceWorker.register('/sw.js', {
scope: '/'
});
console.log('SW registered:', reg.scope);
} catch (err) {
console.error('SW registration failed:', err);
}
});
}
Service Worker
// sw.js
const CACHE_NAME = 'tvs-v1';
const SHELL_CACHE = 'tvs-shell-v1';
// App shell resources (always cached)
const SHELL_URLS = [
'/',
'/assets/style.css',
'/assets/app.js',
'/offline.html',
];
// Install: cache app shell
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(SHELL_CACHE).then((cache) => {
return cache.addAll(SHELL_URLS);
})
);
self.skipWaiting();
});
// Activate: clean old caches
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keys) => {
return Promise.all(
keys
.filter((key) => key !== CACHE_NAME && key !== SHELL_CACHE)
.map((key) => caches.delete(key))
);
})
);
self.clients.claim();
});
// Fetch: stale-while-revalidate for pages, cache-first for assets
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url);
// Skip non-GET requests
if (event.request.method !== 'GET') return;
// Skip API calls
if (url.pathname.startsWith('/api/')) return;
// Static assets: cache-first
if (url.pathname.match(/\.(css|js|png|jpg|webp|svg|woff2)$/)) {
event.respondWith(cacheFirst(event.request));
return;
}
// HTML pages: stale-while-revalidate
event.respondWith(staleWhileRevalidate(event.request));
});
async function cacheFirst(request) {
const cached = await caches.match(request);
if (cached) return cached;
try {
const response = await fetch(request);
if (response.ok) {
const cache = await caches.open(CACHE_NAME);
cache.put(request, response.clone());
}
return response;
} catch {
return new Response('', { status: 503 });
}
}
async function staleWhileRevalidate(request) {
const cached = await caches.match(request);
const fetchPromise = fetch(request)
.then((response) => {
if (response.ok) {
const cache = caches.open(CACHE_NAME);
cache.then((c) => c.put(request, response.clone()));
}
return response;
})
.catch(() => null);
// Return cached version immediately if available
if (cached) {
// Update cache in background
fetchPromise;
return cached;
}
// No cache: wait for network
const response = await fetchPromise;
if (response) return response;
// No cache + no network: show offline page
return caches.match('/offline.html');
}
Offline Page
<!-- offline.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Offline - TrendVidStream</title>
<link rel="stylesheet" href="/assets/style.css">
</head>
<body>
<header class="vw-header">
<a href="/" class="logo">TrendVidStream</a>
</header>
<main class="offline-message">
<h1>You're Offline</h1>
<p>It looks like you've lost your internet connection. Previously visited pages may still be available.</p>
<p>Try:</p>
<ul>
<li><a href="/">Home Page</a></li>
<li><a href="/category/music">Music</a></li>
<li><a href="/category/gaming">Gaming</a></li>
</ul>
</main>
</body>
</html>
Cache Management in PHP
<?php
// Generate cache version based on deploy
function getCacheVersion(): string
{
$versionFile = __DIR__ . '/version.txt';
if (file_exists($versionFile)) {
return trim(file_get_contents($versionFile));
}
return 'v1';
}
// Add version to asset URLs for cache busting
function asset(string $path): string
{
$version = getCacheVersion();
return "/assets/{$path}?v={$version}";
}
This offline-first approach ensures TrendVidStream provides a reliable experience even on unstable connections. The stale-while-revalidate strategy for pages means users see content instantly while fresh data loads in the background.
Top comments (0)