Next.js PWA Features
Progressive Web Apps (PWAs) combine the best of web and mobile applications, providing users with an app-like experience directly in their browser. In this guide, we'll explore how to enhance your Next.js application with PWA features, making it installable, reliable even offline, and more engaging through capabilities like push notifications.
What are PWAs?
Progressive Web Apps are web applications that use modern web capabilities to deliver an experience similar to native applications. They are:
- Reliable: Load instantly, even in uncertain network conditions
- Fast: Respond quickly to user interactions
- Engaging: Feel like a natural app on the device, with immersive user experience
Adding PWA Features to Next.js
Next.js doesn't come with built-in PWA features, but we can easily add them using the next-pwa
package. Let's explore how to implement this.
Step 1: Install Dependencies
First, we need to install the required package:
npm install next-pwa
# or
yarn add next-pwa
Step 2: Configure next.config.js
Next, we'll modify the Next.js configuration file to integrate PWA features:
// next.config.js
const withPWA = require('next-pwa')({
dest: 'public',
disable: process.env.NODE_ENV === 'development'
})
module.exports = withPWA({
// Your existing Next.js config
reactStrictMode: true,
})
This configuration:
- Sets the destination directory for PWA files
- Disables PWA features during development for faster builds
Step 3: Create a Web App Manifest
The web app manifest is a JSON file that provides information about your web application. Create a file named manifest.json
in your public
directory:
{
"name": "My Next.js PWA",
"short_name": "Next PWA",
"description": "A Progressive Web App built with Next.js",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#000000",
"icons": [
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
Step 4: Link the Manifest in Your App
Add the manifest and theme color to your app's head. In Next.js, this is typically done in pages/_document.js
:
// pages/_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
render() {
return (
<Html>
<Head>
<link rel="manifest" href="/manifest.json" />
<link rel="apple-touch-icon" href="/icons/icon-192x192.png" />
<meta name="theme-color" content="#000000" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
Core PWA Features in Next.js
1. Offline Capability
One of the key features of a PWA is working offline. The service worker caches your app's assets and can serve them when there's no network connection.
Creating a Custom Offline Page
Let's create a custom offline page that will be shown when users have no internet connection:
// pages/offline.js
import Head from 'next/head'
import styles from '../styles/Home.module.css'
export default function Offline() {
return (
<div className={styles.container}>
<Head>
<title>Offline - My Next.js PWA</title>
</Head>
<main className={styles.main}>
<h1 className={styles.title}>
You are offline
</h1>
<p className={styles.description}>
Please check your internet connection and try again.
</p>
</main>
</div>
)
}
Then, configure next-pwa
to use this offline page:
// next.config.js
const withPWA = require('next-pwa')({
dest: 'public',
disable: process.env.NODE_ENV === 'development',
register: true,
skipWaiting: true,
fallbacks: {
document: '/offline'
}
})
module.exports = withPWA({
// Your existing Next.js config
})
2. Installation Prompts
PWAs can be installed on devices, creating an icon on the home screen or desktop. You can customize when and how the installation prompt appears:
// components/InstallPWA.jsx
import { useState, useEffect } from 'react'
const InstallPWA = () => {
const [supportsPWA, setSupportsPWA] = useState(false)
const [promptInstall, setPromptInstall] = useState(null)
useEffect(() => {
const handler = (e) => {
e.preventDefault()
setSupportsPWA(true)
setPromptInstall(e)
}
window.addEventListener('beforeinstallprompt', handler)
return () => window.removeEventListener('beforeinstallprompt', handler)
}, [])
const onClick = (evt) => {
evt.preventDefault()
if (!promptInstall) {
return
}
promptInstall.prompt()
}
if (!supportsPWA) {
return null
}
return (
<button
className="install-button"
id="setup_button"
aria-label="Install app"
title="Install app"
onClick={onClick}
>
Install App
</button>
)
}
export default InstallPWA
You can then include this component in your layout or specific pages:
// pages/index.js
import InstallPWA from '../components/InstallPWA'
export default function Home() {
return (
<div>
<h1>My Next.js PWA</h1>
<InstallPWA />
{/* Rest of your page */}
</div>
)
}
3. Push Notifications
Push notifications allow you to re-engage users even when they're not actively using your app. Here's how to implement basic push notification support:
// utils/notifications.js
export async function requestNotificationPermission() {
if (!('Notification' in window)) {
console.log('This browser does not support notifications')
return false
}
try {
const permission = await Notification.requestPermission()
if (permission === 'granted') {
console.log('Notification permission granted')
return true
} else {
console.log('Notification permission denied')
return false
}
} catch (error) {
console.error('Error requesting notification permission:', error)
return false
}
}
export function sendNotification(title, options = {}) {
if (!('Notification' in window)) {
return
}
if (Notification.permission === 'granted') {
return new Notification(title, options)
}
}
Then use it in your components:
// components/NotificationButton.jsx
import { useState } from 'react'
import { requestNotificationPermission, sendNotification } from '../utils/notifications'
export default function NotificationButton() {
const [permissionGranted, setPermissionGranted] = useState(false)
const handleRequestPermission = async () => {
const granted = await requestNotificationPermission()
setPermissionGranted(granted)
if (granted) {
sendNotification('Notifications enabled!', {
body: 'You will now receive updates from our app.',
icon: '/icons/icon-192x192.png'
})
}
}
const sendTestNotification = () => {
sendNotification('Hello from Next.js PWA', {
body: 'This is a test notification',
icon: '/icons/icon-192x192.png'
})
}
return (
<div>
{!permissionGranted ? (
<button onClick={handleRequestPermission}>
Enable Notifications
</button>
) : (
<button onClick={sendTestNotification}>
Send Test Notification
</button>
)}
</div>
)
}
Building a Real-World PWA Feature: Offline Data Caching
Let's create a practical example of caching API data for offline use:
// hooks/useOfflineData.js
import { useState, useEffect } from 'react'
export function useOfflineData(url, defaultData = []) {
const [data, setData] = useState(defaultData)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
async function fetchData() {
setLoading(true)
try {
// Try to fetch fresh data
const response = await fetch(url)
if (!response.ok) throw new Error('Network response was not ok')
const freshData = await response.json()
setData(freshData)
// Store in localStorage for offline use
localStorage.setItem(`offline_data_${url}`, JSON.stringify({
timestamp: Date.now(),
data: freshData
}))
} catch (err) {
setError(err)
// Try to get data from localStorage
const cachedData = localStorage.getItem(`offline_data_${url}`)
if (cachedData) {
const { data: offlineData } = JSON.parse(cachedData)
setData(offlineData)
console.log('Using cached data due to network error')
}
} finally {
setLoading(false)
}
}
fetchData()
}, [url])
return { data, loading, error }
}
Now you can use this hook in your components to handle offline data access:
// pages/products.js
import { useOfflineData } from '../hooks/useOfflineData'
export default function Products() {
const { data: products, loading, error } = useOfflineData('/api/products', [])
if (loading) return <div>Loading products...</div>
if (error && products.length === 0) {
return <div>Error loading products and no cached data available.</div>
}
return (
<div>
{error && <div>Network error: Using cached data</div>}
<h1>Products</h1>
<ul>
{products.map(product => (
<li key={product.id}>
{product.name} - ${product.price}
</li>
))}
</ul>
</div>
)
}
Testing Your PWA
To test PWA functionality properly:
-
Build for production: PWA features are typically disabled in development mode
bashnpm run build
npm start -
Use Lighthouse: Open Chrome DevTools, navigate to the Lighthouse tab, and run an audit to check PWA compliance
-
Test offline functionality: In Chrome DevTools, go to Network tab and enable "Offline" mode to see how your app behaves without a connection
-
Verify installability: Look for the install icon in the address bar or menu to confirm your app can be installed
Common PWA Challenges in Next.js
Challenge: Service Worker Scope
By default, service workers can only control pages within their scope (typically the location of the service worker file). If your Next.js app is deployed to a subdirectory, you might need to adjust the scope:
// next.config.js
const withPWA = require('next-pwa')({
dest: 'public',
scope: '/app/', // If your app is deployed at /app/
})
Challenge: Dynamic Routes and Offline Support
For apps with many dynamic routes, consider using a network-first strategy for these pages:
// next.config.js
const withPWA = require('next-pwa')({
dest: 'public',
runtimeCaching: [
{
urlPattern: /\/posts\/[^\/]+$/, // Match dynamic routes like /posts/123
handler: 'NetworkFirst',
options: {
cacheName: 'dynamic-posts',
expiration: {
maxEntries: 32,
maxAgeSeconds: 24 * 60 * 60, // 24 hours
},
},
},
// Other caching strategies...
]
})
Summary
Adding PWA features to your Next.js application significantly enhances user experience by providing offline access, push notifications, and installation capabilities. We've covered:
- Basic PWA setup with
next-pwa
- Creating and linking the web app manifest
- Implementing offline capabilities
- Adding installation prompts
- Enabling push notifications
- Building a real-world offline data caching system
By implementing these features, you can deliver a more native-like experience directly through the web, improving user retention and engagement.
Additional Resources
- next-pwa Documentation
- Google's PWA Checklist
- MDN Progressive Web Apps Guide
- Workbox Documentation (The service worker library used by next-pwa)
Exercises
- Create a Next.js PWA that caches user preferences in localStorage and IndexedDB for offline use
- Implement a background sync feature that queues user actions when offline and executes them when connection is restored
- Add a "Add to Home Screen" tutorial to guide users through installing your PWA
- Build a PWA with different caching strategies for different types of content (e.g., images vs API data)
- Create a custom update notification that informs users when a new version of your PWA is available
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)