Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save nurmdrafi/f3ddef604f80dfd1a4139a7a8ddc76c8 to your computer and use it in GitHub Desktop.
Save nurmdrafi/f3ddef604f80dfd1a4139a7a8ddc76c8 to your computer and use it in GitHub Desktop.
Building a Progressive Web App (PWA) in Next.js

Related Docs

Benefits of Progressive Web App (PWA)

  1. Offline Functionality: PWAs can operate seamlessly offline, providing users with uninterrupted access to content and functionality even in low or no connectivity environments. This is achieved through service workers, enabling cached content to be served when the network is unavailable.

  2. Background Sync: PWAs can sync data in the background, allowing for seamless updates and interactions even when the app is not actively in use. This ensures that the user experience remains smooth and up-to-date.

  3. Installability: PWAs can be installed directly from the browser, eliminating the need for users to visit an app store. This streamlined installation process increases accessibility and encourages user adoption. Additionally, PWAs can be added to the device's home screen, providing a native-like experience.

  4. Cross-Platform Compatibility: PWAs are built using web technologies like HTML, CSS, and JavaScript, making them compatible with various devices and platforms, including desktops, tablets, and smartphones. This cross-platform functionality simplifies development and maintenance, as a single codebase can serve multiple platforms.

  5. Cost-Effectiveness: Developing a PWA often requires less time and resources compared to building separate native apps for different platforms. With a single codebase, developers can reach a broader audience while minimizing development and maintenance costs.

  6. Improved Performance: PWAs are designed to deliver fast and responsive user experiences. By leveraging techniques such as code splitting, lazy loading, and caching, PWAs can significantly reduce loading times and improve overall performance compared to traditional websites and even some native apps.

  7. Engagement and Retention: PWAs can leverage features like push notifications to re-engage users and drive repeat visits. Additionally, the seamless installation process and offline capabilities contribute to higher user retention rates compared to traditional web experiences.

  8. Enhanced Security: PWAs utilize HTTPS to ensure secure communication between the app and the server, protecting user data and mitigating security risks. This commitment to security enhances user trust and confidence in the app.

  9. Scalability: PWAs can scale effortlessly to accommodate growing user bases and evolving business needs. With built-in support for progressive enhancement, PWAs can adapt to a wide range of devices and network conditions without sacrificing functionality or performance.

  10. SEO-Friendly: PWAs are indexable by search engines, allowing them to be discovered and ranked in search results like traditional websites. This improves visibility and organic traffic, driving more users to the app over time.

  11. Easy Updates: PWAs can be updated seamlessly, with changes reflected instantly for all users upon the next visit. This eliminates the need for manual updates and ensures that users always have access to the latest features and content.

  12. Analytics and Insights: PWAs can leverage web analytics tools to gather valuable insights into user behavior, preferences, and interactions. This data-driven approach enables continuous optimization and refinement of the app to better meet user needs and business objectives.

Why I choose Serwist

I initially used next-pwa to easily enhance the Next.js project with PWA powers but the package seems to not be maintained anymore, so I switched to @ducanh2912/next-pwa which is a forked version maintained by DuCanhGH. Since then, DuCanhGH has been a busy bee and we now have serwist which provides various tools for service workers based on Google's workbox and next-pwa.

Getting started

Install

Run the following command

npm i @serwist/next && npm i -D serwist

Step 1: Wrap your Next config with withSerwist

Update next.config.mjs with

import withSerwistInit from '@serwist/next'

// Configuration options for Next.js
const nextConfig = {
  // output: "standalone",
  reactStrictMode: false,
  // swcMinify: true,
  eslint: {
    dirs: ["."],
  }
}

// Serwist
const withSerwist = withSerwistInit({
  swSrc: 'service-worker/index.ts',
  swDest: 'public/sw.js',
  reloadOnOnline: true,
})

export default withSerwist(nextConfig)

Step 2: Add a site.manifest.json file

Create a site.manifest.json file in your public folder

{
  "id": "PWA_1",
  "name": "My awesome PWA app",
  "short_name": "PWA App",
  "description": "My awesome PWA app",
  "start_url": ".",
  "display": "standalone",
  "theme_color": "#80CDB3",
  "background_color": "#202038",
  "icons": [
    {
      "src": "/icons/android-chrome-192x192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "any maskable"
    },
    {
      "src": "/icons/android-chrome-384x384.png",
      "sizes": "384x384",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

Step 3: Add <meta /> and <link /> tags to your <head />

Add the following to your app/layout.tsx or pages/_app.tsx or pages/_document.tsx

<Head>
  {/* PWA */}
  <meta name="viewport" content="initial-scale=1, viewport-fit=cover, width=device-width" />
  <link rel='manifest' href='/site.manifest.json' />
</Head>

Step 4: Update tsconfig.json file

{
  "compilerOptions": {
    "lib": [ "webworker"],
    "types": ["@serwist/next/typings"]
  },
} 

Step 5: Add service worker file

service-worker/index.ts

import { defaultCache } from "@serwist/next/worker"
import type { PrecacheEntry, SerwistGlobalConfig } from "serwist"
import { Serwist } from "serwist"

// This declares the value of `injectionPoint` to TypeScript.
// `injectionPoint` is the string that will be replaced by the
// actual precache manifest. By default, this string is set to
// `"self.__SW_MANIFEST"`.
declare global {
  interface WorkerGlobalScope extends SerwistGlobalConfig {
    __SW_MANIFEST: (PrecacheEntry | string)[] | undefined;
  }
}

declare const self: ServiceWorkerGlobalScope

const serwist = new Serwist({
  precacheEntries: self.__SW_MANIFEST,
  skipWaiting: true,
  clientsClaim: true,
  importScripts: ['notification.ts'], // push notification
  navigationPreload: true,
  runtimeCaching: defaultCache,
  fallbacks: {
    entries: [
      {
        url: '/offline', // the page that'll display if user goes offline
        matcher({ request }) {
          return request.destination === 'document'
        },
      },
    ],
  },
})

serwist.addEventListeners()

Final Step: Build and test your app

npm run build && npm run start --no-optimized

Debugging

  • Analyze with lighthouse
  • Need to connect mobile with data cable and choose file transfer chrome://inspect/#devices
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment