Web Development

Next.js PWA Offline: A Simple 2024 Service Worker Setup

Tired of your Next.js app breaking offline? Learn the simple, modern way to set up a PWA with a service worker in 2024. Get your app installable and offline-ready.

D

Daniel Carter

A senior front-end developer passionate about performance, accessibility, and modern web APIs.

7 min read12 views

Next.js PWA Offline: A Simple 2024 Service Worker Setup

Imagine this: a user is browsing your beautiful Next.js application on their phone during their commute. The train goes into a tunnel, the Wi-Fi drops, and... your app goes blank. Frustrating, right? In 2024, users expect seamless, resilient experiences, and a dropped connection shouldn't mean a dead end.

This is where Progressive Web Apps (PWAs) shine. By adding a few key pieces to your Next.js project, you can make it installable, feel like a native app, and most importantly, work offline. It sounds complex, but the modern toolchain has made it surprisingly straightforward. Forget wrestling with manually written service worker files and arcane caching logic. We're going to use a modern, well-maintained library to do the heavy lifting.

In this guide, we'll walk through a simple, step-by-step process to convert your Next.js application into an offline-capable PWA. We'll cover the setup, explain the core concepts without the jargon, and give you the confidence to ship an app that's truly unstoppable.

What is a PWA and Why Bother in 2024?

A Progressive Web App (PWA) is a web application that uses modern web capabilities to deliver an app-like experience to users. Think of it as the perfect hybrid between a website and a native mobile app. The key benefits are what make them so compelling:

  • Installable: Users can add your app to their home screen, just like a native app from an app store.
  • Offline Capable: This is our focus today. PWAs can work even with a poor or non-existent internet connection.
  • Engaging: They can receive push notifications to re-engage users.
  • Reliable: They load instantly, regardless of network state, providing a dependable user experience.

In a world where user retention is everything, providing a reliable and fast experience is no longer a luxury—it's a baseline expectation. A PWA checks all these boxes without the overhead of separate native app development.

The Unsung Hero: The Service Worker

The magic behind a PWA's offline capability is a script called a Service Worker. You can think of it as a programmable proxy that sits between your web app and the network. It runs in the background, separate from your app's main thread, and can intercept and handle network requests.

When your app requests a file (like a CSS file, an image, or even API data), the service worker can step in. If it has a fresh copy of that file in its cache, it can serve it directly to the app without ever hitting the network. This is what makes it feel instantaneous and allows it to work offline. While you could write a service worker from scratch, it's a complex task prone to errors. Thankfully, we have tools that generate one for us based on a simple configuration.

Prerequisites for Your PWA Journey

Before we dive in, make sure you have the following:

  • A working Next.js project (this guide works for both the App Router and Pages Router).
  • Node.js (version 18 or later) and your preferred package manager (npm, yarn, or pnpm) installed.
  • A basic understanding of how to modify your next.config.mjs file.

Step-by-Step Setup with next-pwa

Advertisement

We'll use the next-pwa package, the most popular and well-supported solution for adding PWA capabilities to Next.js.

Step 1: Install the Dependency

Open your terminal in your project's root directory and run:

npm install next-pwa

Or if you use yarn or pnpm:

yarn add next-pwa
pnpm add next-pwa

Step 2: Configure next.config.mjs

Next, we need to tell Next.js to use our new PWA plugin. Open your next.config.mjs (or .js) file and wrap your configuration with withPWA.

// next.config.mjs
import withPWA from 'next-pwa';

const pwaConfig = withPWA({
  dest: 'public',
  register: true,
  skipWaiting: true,
  disable: process.env.NODE_ENV === 'development',
});

/** @type {import('next').NextConfig} */
const nextConfig = {
  // Your regular Next.js config goes here
  reactStrictMode: true,
};

export default pwaConfig(nextConfig);

Let's quickly break down those options:

  • dest: 'public': This tells next-pwa where to output the service worker file (sw.js) and related files. The public directory is the correct place.
  • register: true: This automatically registers the service worker for you.
  • skipWaiting: true: This forces a new service worker to activate immediately upon installation, ensuring users get updates faster.
  • disable: process.env.NODE_ENV === 'development': This is crucial. It disables the service worker during development to prevent caching issues that can make development frustrating. The service worker will only be generated for production builds.

Step 3: Create the manifest.json File

The web app manifest is a JSON file that tells the browser about your PWA and how it should behave when 'installed' on the user's device. Create a file named manifest.json inside your /public directory.

Here's a good starting template. Be sure to create your own icons!

// public/manifest.json
{
  "name": "My Awesome Next.js App",
  "short_name": "AwesomeApp",
  "description": "An awesome PWA 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"
    }
  ]
}

The browser needs to know where to find your manifest file. You link it in the <head> of your application. The method differs slightly between the App Router and Pages Router.

For the App Router (app/layout.tsx)

In the new App Router, you add this to your root layout.tsx file's metadata object.

// app/layout.tsx
import type { Metadata } from 'next';

export const metadata: Metadata = {
  title: 'My Awesome App',
  description: 'An awesome PWA built with Next.js',
  manifest: '/manifest.json',
  themeColor: '#000000',
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

For the Pages Router (pages/_document.js)

If you're using the Pages Router, you'll need to create a custom _document.js file (if you don't have one already) and add the links there.

// pages/_document.js
import { Html, Head, Main, NextScript } from 'next/document';

export default function Document() {
  return (
    <Html lang="en">
      <Head>
        <link rel="manifest" href="/manifest.json" />
        <meta name="theme-color" content="#000000" />
      </Head>
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}

Step 5: Build and Test Your PWA

Now for the moment of truth! Remember, the service worker is only generated for production builds.

  1. Build your app: npm run build
  2. Start your app in production mode: npm run start
  3. Test it: Open your browser (Chrome is great for this) and navigate to http://localhost:3000.

Open DevTools (Cmd+Opt+I or Ctrl+Shift+I) and go to the Application tab. Under Service Workers, you should see your sw.js file is activated and running. Success!

To test the offline functionality:

  • Go to the Network tab in DevTools.
  • Check the Offline checkbox.
  • Refresh the page. Your app should still load!

You can also run a Lighthouse audit (in the Lighthouse tab) and check your PWA score. You should see a green checkmark for "Installable".

A Quick Look at Caching Strategies

next-pwa handles caching for you, but it's helpful to know what's happening. Here are the most common strategies it employs:

StrategyWhat It's ForHow It Works
Cache FirstStatic assets (CSS, JS, Fonts, Images)Serves the file from the cache immediately. If it's not in the cache, it gets it from the network and saves it for next time.
Network FirstFrequently updated data (API calls)Tries the network first. If the network fails (i.e., you're offline), it serves the last-known good version from the cache.
Stale-While-RevalidateAssets that need to be fresh but speed is criticalServes a cached (stale) version instantly for speed, while simultaneously fetching an updated version from the network in the background to update the cache for the *next* visit.

next-pwa is smart enough to apply the right strategy to the right assets. Your page routes, static assets, and Next.js chunks are all precached, giving you a robust offline experience out of the box.

Handling Updates: Keeping It Fresh

A common PWA pitfall is getting stuck with an old, cached version of your site. How do you push updates to users?

The combination of how Next.js builds and how our service worker is configured handles this gracefully. When you run npm run build, Next.js generates new JavaScript and CSS files with unique hashes in their filenames. When a user visits your updated site, the browser detects a change in the service worker file.

Because we set skipWaiting: true, the new service worker activates immediately. The next time the user navigates or reloads, the new service worker takes control and fetches the newly hashed, updated assets, seamlessly updating the app.

Conclusion: Your App, Now Unstoppable

And there you have it! In just a few simple steps, you've transformed your standard Next.js application into a powerful, resilient Progressive Web App. You've made it installable, and more importantly, you've ensured it provides a baseline experience even when the user's connection fails.

By leveraging the power of next-pwa, you've skipped the most difficult parts of service worker management and jumped straight to the benefits. This simple setup provides a huge boost to user experience and perceived performance. Go ahead, deploy it, and give your users an app they can rely on, online or off.

You May Also Like