How to Build a PWA with Next.js in 2026: The Complete Technical Guide
Konrad Bachowski
Tech lead, HeyNeuron
How to Build a PWA with Next.js in 2026: The Complete Technical Guide
A custom mobile app runs $50,000–$250,000 to build. A Progressive Web App (PWA) built on your existing Next.js codebase costs a fraction of that — and today's PWAs deliver push notifications, offline mode, and a home screen icon that most users can't distinguish from a native app. Over 54,000 websites use PWA technology as of January 2026, and businesses that make the switch report 20–250% boosts in engagement.
This guide walks you through how to build a PWA with Next.js 15's App Router — the current approach without deprecated third-party packages. You'll get step-by-step implementation, browser compatibility details (including iOS caveats most tutorials skip), a cost breakdown, GDPR guidance for service worker caching, and a pre-launch checklist.
Why PWA in 2026 — The Business Case
Before writing a line of code, the numbers should drive the decision.
The global PWA market was valued at $5.23 billion in 2025 and is growing at roughly 19–31% CAGR through 2033, according to data from Grand View Research and Straits Research. The adoption cases are no longer experimental:
- Twitter/X: 65% increase in pages per session, 75% more tweets sent, 20% drop in bounce rate — and the PWA weighed 97% less than the native app
- Nikkei: 2.3× more organic traffic, 58% more subscriptions after going PWA
- Starbucks: 2× daily active users; the PWA works on low-bandwidth connections common in markets outside the US
Browser support is no longer a barrier. Service worker support hit 96% overall and 100% across evergreen browsers in 2025, with Firefox adding PWA install support in version 143.0 (September 2025). The last holdout was Firefox, and that gap is closed.
PWA vs Native App vs Hybrid: Cost Comparison
The economics are the clearest argument for PWA when your product is already on the web.
| Factor | PWA (Next.js) | React Native | Native (iOS + Android) |
|---|---|---|---|
| Initial build cost | $8,000–$40,000 | $40,000–$150,000 | $80,000–$300,000 |
| Codebases to maintain | 1 | 1 | 2 |
| App store fees | None | $99/yr (Apple) + $25 (Google) | Same |
| Update deployment | Instant (web) | App store review (1–3 days) | Same |
| Offline capability | Configurable | Full | Full |
| Push notifications | Yes (Web Push API) | Yes | Yes |
| iOS home screen install | Supported (limited) | Full native | Full native |
Development costs run 50–70% lower for PWA vs native, which is why companies with existing Next.js sites typically start there before deciding whether a native app is justified.
If your product needs device features like camera, Bluetooth, biometric auth, or background GPS — native or React Native wins. For everything else, PWA is faster and cheaper.
Decision shortcut: If your existing Next.js site already ranks in Google and converts traffic, the marginal cost of making it installable and offline-capable is measured in days, not months.
For a detailed framework comparison, see how to choose the right tech stack for your web app.
The Three Requirements for a Valid PWA
Every PWA must satisfy three technical requirements before a browser offers the install prompt:
- Served over HTTPS — all production hosts (Vercel, Netlify, AWS Amplify) handle this automatically
- Web App Manifest — a JSON file describing the app name, icons, colors, and display mode
- Service Worker — a JavaScript file that intercepts network requests and enables caching and offline mode
Next.js 15's App Router makes the manifest trivially easy. The service worker requires a bit more setup.
Step-by-Step: Building a PWA with Next.js 15 App Router
Step 1 — Create the Web App Manifest
In the App Router, place a manifest.ts file (or manifest.json) in your /app directory. Next.js serves it automatically at /manifest.webmanifest.
// app/manifest.ts
import type { MetadataRoute } from 'next'
export default function manifest(): MetadataRoute.Manifest {
return {
name: 'My App',
short_name: 'App',
description: 'My Progressive Web App built with Next.js',
start_url: '/',
display: 'standalone',
background_color: '#ffffff',
theme_color: '#0f172a',
icons: [
{
src: '/icon-192x192.png',
sizes: '192x192',
type: 'image/png',
purpose: 'maskable',
},
{
src: '/icon-512x512.png',
sizes: '512x512',
type: 'image/png',
},
],
}
}
Icon requirements: You need at least a 192×192 and a 512×512 PNG. Use a maskable purpose for the 192 icon so it renders correctly inside Android adaptive icon shapes. Tools like Maskable.app let you preview how it looks before shipping.
No additional configuration in next.config.ts is needed — Next.js automatically links the manifest in the HTML <head>.
Step 2 — Add a Service Worker
Next.js doesn't auto-generate service workers (the next-pwa package that many older tutorials reference is deprecated and incompatible with the App Router). You have two options:
Option A — Workbox (recommended for production): Use Google's Workbox library via workbox-webpack-plugin for precaching and route-based caching strategies. It requires a custom Webpack config but gives you full control over cache behavior.
Option B — Vanilla Service Worker (simpler): Write a public/sw.js file directly and register it in a client component. This is faster to get running for smaller projects.
// app/sw-register.tsx — a client component
'use client'
import { useEffect } from 'react'
export default function ServiceWorkerRegister() {
useEffect(() => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('/sw.js')
.then((reg) => console.log('SW registered:', reg.scope))
.catch((err) => console.error('SW registration failed:', err))
}
}, [])
return null
}
Include this component in your root layout.tsx before the closing </body>.
Step 3 — Write the Service Worker
// public/sw.js
const CACHE_NAME = 'app-v1'
const STATIC_ASSETS = ['/', '/offline', '/icon-192x192.png']
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.addAll(STATIC_ASSETS))
)
self.skipWaiting()
})
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k)))
)
)
self.clients.claim()
})
self.addEventListener('fetch', (event) => {
if (event.request.method !== 'GET') return
event.respondWith(
caches.match(event.request).then((cached) => {
if (cached) return cached
return fetch(event.request).catch(() => caches.match('/offline'))
})
)
})
This is a Cache First with Network Fallback strategy: serve from cache when available, fetch from network otherwise, and show an /offline page when both fail.
Step 4 — Create an Offline Fallback Page
// app/offline/page.tsx
export default function OfflinePage() {
return (
<main>
<h1>You're offline</h1>
<p>Check your connection and try again.</p>
</main>
)
}
Pre-cache this page in your service worker's STATIC_ASSETS array so it's available without a network connection.
Step 5 — Add an Install Prompt
Browsers fire a beforeinstallprompt event when the PWA criteria are met. You can intercept it and show your own UI:
'use client'
import { useState, useEffect } from 'react'
export default function InstallPrompt() {
const [deferredPrompt, setDeferredPrompt] = useState<any>(null)
useEffect(() => {
const handler = (e: Event) => {
e.preventDefault()
setDeferredPrompt(e)
}
window.addEventListener('beforeinstallprompt', handler)
return () => window.removeEventListener('beforeinstallprompt', handler)
}, [])
const handleInstall = async () => {
if (!deferredPrompt) return
deferredPrompt.prompt()
const { outcome } = await deferredPrompt.userChoice
if (outcome === 'accepted') setDeferredPrompt(null)
}
if (!deferredPrompt) return null
return (
<button onClick={handleInstall}>
Install App
</button>
)
}
Note: beforeinstallprompt is not supported on iOS Safari. iPhone users must manually use the Share → Add to Home Screen menu. More on this in the browser compatibility section.
Caching Strategies: Which One to Use
Your service worker's fetch handler is where you choose how aggressively to cache. The right strategy depends on the type of content.
| Strategy | How it works | Best for |
|---|---|---|
| Cache First | Serve cache, fetch in background | Static assets (JS, CSS, images) |
| Network First | Fetch network, fall back to cache | Dynamic pages, API responses |
| Stale While Revalidate | Serve cache immediately, update cache async | News, product listings |
| Cache Only | Only serve from cache | Fully offline apps |
For a typical Next.js app: use Cache First for /static/ assets (Webpack bundles), Network First for page routes and API calls, and Stale While Revalidate for content that changes daily (blog posts, product catalogs).
Browser Compatibility — iOS Limitations You Must Know
Most PWA tutorials are written for Chrome on Android, where every feature works. iOS has a different story.
| PWA Feature | Chrome Android | Safari iOS (18+) | Firefox Android |
|---|---|---|---|
| Install to home screen | Yes (auto-prompt) | Yes (manual only) | Yes |
| Service worker | Yes | Yes | Yes |
| Offline mode | Yes | Yes | Yes |
| Web Push Notifications | Yes | Yes (iOS 16.4+, opt-in) | Yes |
| Background sync | Yes | No | No |
| beforeinstallprompt | Yes | No | Yes |
| Persistent storage | Yes | No (7-day eviction) | Yes |
The critical iOS caveats:
- Safari evicts service worker caches after 7 days of inactivity. If your user doesn't visit for a week, they lose their cached content.
- Web Push on iOS requires the user to add the PWA to their home screen first — browser-tab push doesn't work on iOS.
- The install prompt must be manual (Share → Add to Home Screen). You cannot trigger it programmatically.
If your user base is >50% iOS (common for consumer apps), these limitations matter. For business tools accessed primarily on desktop or Android, they're minor. Check your analytics before deciding.
Core Web Vitals Targets for PWA
A PWA that installs but scores poorly on Core Web Vitals will hurt your SEO and engagement metrics simultaneously. Google uses the same scoring for PWAs as for regular web pages.
Production targets:
- LCP (Largest Contentful Paint): < 2.5s — aggressive caching of above-the-fold images and fonts
- INP (Interaction to Next Paint): < 200ms — defer non-critical JavaScript, avoid long tasks on the main thread
- CLS (Cumulative Layout Shift): < 0.1 — reserve space for images with width/height attributes or aspect-ratio CSS
Next.js helps with LCP via <Image> auto-optimization and font preloading. Service worker caching of the critical CSS and font files pushes LCP below 1.5s for returning visitors — a measurable advantage over regular web pages.
GDPR Compliance for Service Workers
Service workers can cache user-specific data (API responses with personal information, auth tokens in localStorage). This creates compliance obligations that most PWA tutorials ignore.
What to document and implement:
- Data processing register (Article 30): List "PWA client-side cache" as a processing activity. Specify what data is cached (page content, user preferences), retention period (cache eviction policy), and legal basis.
- Minimal caching principle: Only cache what you need for offline functionality. Don't cache API responses that contain personal data (user profiles, order history) in the service worker unless you have explicit consent.
- Right to erasure: When a user requests data deletion, your
PATCH /api/users/{id}handler should also trigger apostMessageto the service worker to clear relevant cache entries. - Cookie/storage consent: If you cache data in
localStorageorIndexedDBthat isn't strictly necessary for the app to function, you need consent under ePrivacy.
For EU-facing products, a clean approach is to only cache static assets and the app shell in the service worker, and pull personalized data fresh from the network (with offline-graceful error states rather than stale personal data).
Implementation Cost by Route
If you're deciding whether to DIY or hire, here's what to budget.
| Route | Cost | Timeline | Best for |
|---|---|---|---|
| DIY | $0 + dev time | 2–5 days | Teams with Next.js experience |
| Freelancer | $1,500–$5,000 | 1–2 weeks | Adding PWA to an existing site |
| Agency | $8,000–$25,000 | 3–6 weeks | Full PWA build + auditing + compliance |
| Full custom (Workbox + design) | $15,000–$40,000 | 6–12 weeks | Complex offline-first apps with auth |
For a typical Next.js marketing or SaaS site, adding PWA functionality — manifest, basic service worker, offline page — is a 2–3 day freelancer job. The cost jumps when you add background sync, sophisticated offline data management, or push notification infrastructure.
See how much it costs to build a SaaS platform for a broader cost picture if your PWA is the foundation of a new product.
When NOT to Build a PWA
PWA is the right choice in most cases — but there are specific scenarios where native wins.
1. Your product requires native hardware access. Continuous background GPS tracking, Bluetooth device pairing (beyond basic Web Bluetooth), NFC payments, ARKit/ARCore — these require native SDKs. Web APIs exist for limited versions, but they're not production-ready for complex use cases.
2. Your primary audience is iPhone users who won't see the install prompt. If analytics show 70%+ iOS traffic and your product's value is tied to the installable experience (homescreen icon, full-screen mode), the friction of manual installation on iOS may undermine adoption. A React Native app gives a cleaner install story for iOS-first products.
3. Your offline data model is complex. A PWA can sync data with IndexedDB and background sync (on Chrome/Android), but managing conflict resolution, multi-user shared state offline, and large binary file caching is significantly harder than in native. If the offline use case is your product's core feature, not a nice-to-have, the complexity of getting it right in a PWA is comparable to native development anyway.
4. You need to submit to corporate MDM-managed app stores. Some enterprise clients require software to be distributed through managed App Store or Play Store deployments. PWAs don't fit that requirement (though Microsoft Store does accept PWAs for Windows devices).
For comparison with hybrid approaches, see React Native vs Flutter for mobile app development.
Pre-Launch PWA Checklist
- [ ] Manifest served correctly — verify at
/.well-known/manifest.webmanifestor/manifest.webmanifest - [ ] Icons in both sizes — 192×192 (maskable) and 512×512 PNG in
/public - [ ] Service worker registered — Chrome DevTools → Application → Service Workers shows "activated and running"
- [ ] Offline fallback works — throttle to Offline in DevTools and reload the page
- [ ] HTTPS in all environments — no mixed content warnings in the console
- [ ] Install prompt intercepted —
beforeinstallpromptfires on desktop Chrome and Android Chrome - [ ] iOS manual install tested — test Share → Add to Home Screen, verify icon and title display
- [ ] Core Web Vitals passing — run Lighthouse in a production build (not dev mode)
- [ ] Cache versioning — increment
CACHE_NAMEon each deployment so stale workers update - [ ] GDPR data audit done — confirm service worker only caches non-personal static assets
Internal Resources
If you're building a Next.js product and evaluating your stack, these articles cover the adjacent decisions:
- How to choose the right tech stack for your web app — framework selection framework including Next.js, React Native, Flutter
- React Native vs Flutter for mobile app development — when mobile native is the better call
- How much does it cost to build a custom CRM — if your PWA is a business tool
- How much does it cost to build a SaaS platform — full product build budgeting
- Next.js development agency Poland — hiring context for Next.js projects in Eastern Europe
Our web app and website development services cover Next.js builds including PWA implementation and performance auditing.
Frequently Asked Questions
Does Next.js support PWA natively in 2026?
Yes. Since fall 2024, Next.js has native support for the Web App Manifest via manifest.ts in the App Router. Service workers are not auto-generated — you write them manually or use Workbox — but no third-party package is required to meet the PWA install criteria.
Is next-pwa still supported?
The next-pwa package by DuCanhGH (the community-maintained fork) works with Next.js 14 and 15 App Router as of mid-2025, but it adds complexity and dependency risk. For new projects, the official Next.js manifest route plus a manually registered service worker is simpler and more maintainable.
Can I get push notifications in a Next.js PWA?
Yes. Use the Web Push API with VAPID keys on your server. On Android Chrome and desktop browsers, push works in the background. On iOS, push requires the user to have added the PWA to their home screen (supported since iOS 16.4 in March 2023). Generate VAPID keys with the web-push npm package.
How does PWA caching affect SEO?
Service worker caching improves SEO by boosting Core Web Vitals scores (LCP in particular) for returning visitors. It doesn't interfere with Googlebot crawling — Googlebot doesn't execute service workers. The manifest and HTTPS requirement align with Google's mobile-friendliness signals. Net effect: positive for SEO.
What happens to cached data when a service worker updates?
The new service worker installs and waits (in the waiting state) until all tabs running the old version are closed. Calling self.skipWaiting() in the install event forces immediate activation. Always increment your CACHE_NAME constant on each deployment so old caches are deleted during the activate event.
How large can service worker caches be?
Browser storage quotas vary: Chrome grants roughly 60% of available disk space, Firefox around 10%. The navigator.storage.estimate() API returns your quota and usage in bytes. For typical PWAs (app shell + static assets), 50–100 MB is more than sufficient. IndexedDB is the right choice for large datasets.
Does a PWA need to be submitted to app stores?
No, for Android and iOS you don't need app store submission — users install from the browser. The Microsoft Store on Windows accepts PWA submissions if you want discoverability there. Google Play also accepts "Trusted Web Activities" (TWA) which wrap a PWA in a Play Store listing, which can help for Play Store discoverability.
How do I debug a service worker?
Chrome DevTools has the best tooling: Application → Service Workers shows registration status, cache storage contents, and lets you force update. Application → Storage shows cache sizes and lets you clear them. In production, add console.log calls in your service worker and filter for them in DevTools → Sources → "Service workers" thread.
Conclusion
Building a PWA with Next.js in 2026 is straightforward: a manifest.ts file and a service worker registration component get you to the install prompt in a day. The more important decisions are around caching strategy, iOS limitations, and GDPR compliance for anything you cache client-side — areas most tutorials skip.
The business case is clear: Twitter, Nikkei, and Starbucks all saw measurable engagement lifts after moving to PWA, at 50–70% lower cost than equivalent native development. For teams already running Next.js, the upgrade is low-risk and fast to ship.
If you want a senior Next.js team to handle the implementation, performance auditing, and compliance review, reach out to us — we've shipped PWAs for SaaS products and B2B tools across Europe and the US.
Stay up to date with AI and automation
Subscribe to our newsletter to receive specific tips and tools once a week. Join over 2,000 subscribers.