The App Shell pattern separates the minimal HTML, CSS, and JavaScript needed to render the user interface from the dynamic content. This shell is cached on first load for instant rendering on subsequent visits.
// App Shell Architecture
// 1. Cache the shell on install
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('shell-v1').then(cache => {
return cache.addAll([
'/shell/',
'/shell/styles.css',
'/shell/app.js',
'/shell/header.html',
'/shell/footer.html',
'/shell/nav.html',
'/offline.html'
]);
})
);
});
// 2. Serve shell from cache, content from network
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url);
// Shell resources: cache first
if (url.pathname.startsWith('/shell/')) {
event.respondWith(caches.match(event.request));
return;
}
// Content pages: network first with cache fallback
if (url.pathname.startsWith('/content/')) {
event.respondWith(
fetch(event.request)
.catch(() => caches.match('/offline.html'))
);
return;
}
// API calls: network only
if (url.pathname.startsWith('/api/')) {
event.respondWith(fetch(event.request));
return;
}
});
PRPL is a pattern for structuring and serving PWAs for fast initial load:
RAIL is a user-centric performance model:
| Letter | Meaning | Target |
|---|---|---|
| R | Response | 50ms for input response |
| A | Animation | 10ms per frame (60fps) |
| I | Idle | Use idle time for deferred work |
| L | Load | 1s for content-first paint |
import('./module.js') for on-demand loading// Dynamic imports for code splitting
const button = document.getElementById('load-chart');
button.addEventListener('click', async () => {
const { renderChart } = await import('./charts.js');
renderChart('revenue-data');
});
// Intersection Observer for lazy loading
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});
document.querySelectorAll('img[data-src]').forEach(img => {
observer.observe(img);
});