PWA Architecture Patterns β Full App Shell, Server-Only, and Hybrid
In this tutorial, you will learn about PWA Architecture Patterns. We cover key concepts, practical examples, and best practices to help you master this topic.
PWA architecture patterns range from full app shell to server-only with hybrid approaches that balance offline capability and complexity for different application needs.
What You'll Learn
By the end of this tutorial, you will understand three main PWA architecture patterns, when to use each, and how to choose the right architecture for your project.
Why It Matters
There is no one-size-fits-all PWA architecture. A news app needs different offline behavior than a weather app or a form-heavy enterprise app. Choosing the wrong architecture leads to over-engineering or poor offline experience. Understanding the spectrum helps you pick the right balance.
Architecture Spectrum
PWA Architecture Patterns
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Architecture Spectrum β
ββββββββββββββ¬βββββββββββββββ¬βββββββββββββββ¬βββββββββββββββββββ€
β App Shell β Full β Hybrid β Server-Only β
β (Full β Static β App Shell β (Thin SW) β
β Offline) β Content β + API β β
ββββββββββββββΌβββββββββββββββΌβββββββββββββββΌβββββββββββββββββββ€
β Max β High β Moderate β Low offline β
β offline β offline β offline β capability β
ββββββββββββββΌβββββββββββββββΌβββββββββββββββΌβββββββββββββββββββ€
β High β Moderate β Moderate β Low complexity β
β complexityβ complexity β complexity β β
ββββββββββββββ΄βββββββββββββββ΄βββββββββββββββ΄βββββββββββββββββββ
Think of architecture patterns like vehicles. App shell is a fully-equipped RV β everything you need for extended offline travel. Full static is a reliable car with a full tank. Hybrid is a car with a spare gas can. Server-only is a bicycle β great for short trips, limited for long journeys.
Pattern 1: App Shell (Full Offline)
Ideal for: Apps where most content should work offline.
// sw.js β App Shell implementation
const SHELL_CACHE = 'shell-v2';
const DYNAMIC_CACHE = 'dynamic-v2';
// Pre-cache shell on install
self.addEventListener('install', event => {
event.waitUntil(
caches.open(SHELL_CACHE).then(cache => {
return cache.addAll([
'/',
'/index.html',
'/styles/shell.css',
'/scripts/main.js',
'/scripts/router.js',
'/images/logo.svg',
'/fonts/inter.woff2',
'/offline.html'
]);
}).then(() => self.skipWaiting())
);
});
// Cache-first for shell, network-first for content
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
const isShellAsset = url.pathname.match(/\.(css|js|woff2?|svg)$/) ||
SHELL_ASSETS.includes(url.pathname);
if (isShellAsset) {
// Cache-first for shell
event.respondWith(
caches.match(event.request).then(cached => {
return cached || fetch(event.request).then(response => {
caches.open(SHELL_CACHE).then(cache => cache.put(event.request, response));
return response.clone();
});
})
);
} else if (event.request.mode === 'navigate') {
// Network-first for pages
event.respondWith(
fetch(event.request).then(response => {
caches.open(DYNAMIC_CACHE).then(cache => cache.put(event.request, response));
return response;
}).catch(() => caches.match(event.request).then(cached => {
return cached || caches.match('/offline.html');
}))
);
} else {
// Network-first for API
event.respondWith(
fetch(event.request).catch(() => caches.match(event.request))
);
}
});
Pattern 2: Full Static Content
Ideal for: Blogs, documentation, marketing sites with content that changes infrequently.
// sw.js β Full static content pre-caching
self.addEventListener('install', event => {
event.waitUntil(
caches.open('static-v3').then(cache => {
return cache.addAll([
'/',
'/index.html',
'/about/',
'/docs/',
'/blog/',
'/styles/main.css',
'/scripts/app.js',
'/images/',
'/offline.html'
]);
}).then(() => self.skipWaiting())
);
});
self.addEventListener('fetch', event => {
// Cache-only: everything was pre-cached
event.respondWith(
caches.match(event.request).then(cached => {
if (cached) return cached;
// If not cached, fetch and cache for next time
return fetch(event.request).then(response => {
if (response.ok) {
caches.open('static-v3').then(cache => cache.put(event.request, response));
}
return response;
}).catch(() => caches.match('/offline.html'));
})
);
});
Pattern 3: Hybrid (App Shell + Dynamic API)
Ideal for: Social media, news, dashboards with dynamic content.
// sw.js β Hybrid approach
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
if (isShellAsset(url)) {
// Shell assets: cache-first
event.respondWith(cacheFirst(event.request));
} else if (url.pathname.startsWith('/api/')) {
// API: stale-while-revalidate
event.respondWith(staleWhileRevalidate(event.request));
} else if (event.request.mode === 'navigate') {
// Navigation: network-first
event.respondWith(networkFirst(event.request));
}
});
function cacheFirst(request) {
return caches.match(request).then(cached => {
return cached || fetch(request);
});
}
function staleWhileRevalidate(request) {
return caches.match(request).then(cached => {
const fetchPromise = fetch(request).then(response => {
if (response.ok) {
caches.open('api-cache').then(cache => cache.put(request, response));
}
return response;
});
return cached || fetchPromise;
});
}
function networkFirst(request) {
return fetch(request).catch(() => caches.match(request));
}
Pattern 4: Server-Only (Thin Service Worker)
Ideal for: Simple sites where offline is a nice-to-have, not critical.
// sw.js β Minimal service worker
self.addEventListener('install', event => {
// Cache only the offline fallback page
event.waitUntil(
caches.open('minimal-v1').then(cache => {
return cache.addAll([
'/offline.html'
]);
}).then(() => self.skipWaiting())
);
});
self.addEventListener('fetch', event => {
// If offline, show offline page for navigations
event.respondWith(
fetch(event.request).catch(() => {
if (event.request.mode === 'navigate') {
return caches.match('/offline.html');
}
// For assets, return empty
return new Response('', { status: 408 });
})
);
});
Choosing the Right Architecture
// Architecture decision helper
const projectCriteria = {
type: 'news-app', // 'content', 'app', 'game', 'ecommerce'
offlineCritical: true,
contentChangesFrequently: true,
hasUserAuth: true,
teamSize: 3,
targetAudience: 'emerging-markets'
};
function recommendArchitecture(criteria) {
if (criteria.offlineCritical && criteria.contentChangesFrequently) {
return 'Hybrid (App Shell + Dynamic API)';
} else if (criteria.offlineCritical && !criteria.contentChangesFrequently) {
return 'Full Static Content';
} else if (criteria.teamSize <= 2 && !criteria.offlineCritical) {
return 'Server-Only (Thin SW)';
} else {
return 'App Shell (Full Offline)';
}
}
Common Mistakes
- Over-engineering for the first version. Start simple (thin SW) and add offline capability as needed. Full app shell is overkill for a 5-page site.
- Choosing app shell for content that rarely changes. Full static pre-Caching is simpler and provides better offline experience for static content.
- Not considering user data sensitivity. Apps with user authentication need careful cache strategies to avoid serving stale or wrong-user data.
- Ignoring team capabilities. App shell architecture requires more frontend engineering. Choose based on what your team can maintain.
- Assuming one pattern fits all pages. Different routes in the same app can use different patterns. Blog posts can be static, while the dashboard is hybrid.
Practice Questions
- What are the four main PWA architecture patterns?
- When would you choose full static content over app shell?
- What is the advantage of the hybrid approach?
- When is a thin service worker (server-only) appropriate?
- How do you decide which architecture to use for a new project?
Challenge: Evaluate three different PWA types (recipe app, stock trading dashboard, company blog) and recommend an architecture for each. Justify each choice based on offline requirements, content freshness needs, and development complexity.
FAQ
Mini Project
Create three different service workers for the same app (a news reader): one using full app shell (offline everything), one using hybrid (shell + API caching), and one using thin SW (offline fallback only). Compare the offline capability and complexity of each. Document which you would use for a production deployment.
What's Next
Architecture is chosen. Now learn about PWA performance optimization β making your PWA load fast, run smoothly, and conserve resources.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro