>_
EngineeringNotes
Back to Next.js Mastery

PWA & Offline Support

Transforming static and dynamic websites into high-performance, installable applications that function seamlessly without an internet connection.

Critical for Next.js 16+ (Turbopack Conflict)

Next.js 16 enables Turbopack by default. However, most PWA plugins (like @ducanh2912/next-pwa) currently rely on Webpack internal hooks. To ensure your Service Worker is generated correctly, you must explicitly force a Webpack build.

1. Update package.json scripts

package.json
json
"scripts": {
  "dev": "next dev --webpack",
  "build": "next build --webpack",
  "start": "next start --webpack"
}

2. Silence Turbopack warnings

next.config.ts
typescript
const nextConfig: NextConfig = {
  output: "export",
  turbopack: {},
};

The PWA Concept

Reliable

Loads instantly and never shows the 'downasaur', even in uncertain network conditions.

Fast

Responds quickly to user interactions with silky smooth animations and no janky scrolling.

Engaging

Feels like a natural app on the device, with an immersive user experience.

Architecture & Lifecycle

The magic behind a PWA lies in two key components working together:Service Workers and the Web App Manifest.

  • Service Worker: A programmable proxy that intercepts network requests.
  • Web Manifest: defines app icons, theme colors, and its standalone appearance.
User UI
Service Worker
Network
Cache Storage

Full PWA Transformation Roadmap

1

Installation & Plugin Setup

Install the modern PWA plugin for Next.js.

Terminal
bash
npm install @ducanh2912/next-pwa --legacy-peer-deps
2

Configuration (next.config.ts)

Wrap your configuration with the PWA initializer.

typescript
typescript
import withPWAInit from "@ducanh2912/next-pwa";

const withPWA = withPWAInit({
  dest: "public",
  disable: process.env.NODE_ENV === "development",
  register: true,
  cacheOnFrontEndNav: true,
  aggressiveFrontEndNavCaching: true,
});

export default withPWA(nextConfig);
3

Creating the Web Manifest

Define your app's identity and icons in public/manifest.json.

public/manifest.json
json
{
  "name": "Engineering Notes",
  "short_name": "EngNotes",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#09090b",
  "theme_color": "#09090b",
  "icons": [
    { "src": "/icons/icon-192x192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/icons/icon-512x512.png", "sizes": "512x512", "type": "image/png" }
  ]
}
4

Updating Layout Metadata

Link your manifest and define the theme color in your root layout.

src/app/layout.tsx
typescript
export const metadata: Metadata = {
  title: "Engineering Notes",
  manifest: "/manifest.json",
  themeColor: "#09090b",
  appleWebApp: { capable: true },
};
5

Building & Deployment

Always use the webpack flag in scripts to avoid Turbopack conflicts.

Terminal
bash
npm run build --webpack

Beyond Static: Handling Dynamic Sites

Precaching (Static)

Files like HTML, CSS, and JS chunks are cached during the Installationphase of the Service Worker. These are assets that don't change until a new version of the app is deployed.

  • • Guaranteed offline availability for UI
  • • Requires deployment to update

Runtime Caching (Dynamic)

API responses and dynamic images are cached during the Fetch phase. This allows data fetched from a backend to be available even if the user goes offline later.

Common Strategies:
StaleWhileRevalidateNetworkFirst

Interview Check-In

What is 'Stale-While-Revalidate'?

A caching strategy where the app serves cached content immediately (stale) while fetching fresh content from the network in the background to update the cache.

What is the Service Worker Lifecycle?

Registration -> Installation -> Activation. During activation, old caches are typically purged.

Why use 'output: export' for PWAs?

It produces a predictable, strictly static set of assets that can be entirely cached for offline-only environments without complex dynamic routing failures.

Difference between Cache API and LocalStorage?

Cache API is designed for network request/response objects and handles larger data (like images/JS), whereas LocalStorage is for small key-value strings.