Skip to content

Instantly share code, notes, and snippets.

@cdnkr
Last active June 5, 2025 18:15
Show Gist options
  • Save cdnkr/25d3746bdb35767d66c7ae6d26c2ed98 to your computer and use it in GitHub Desktop.
Save cdnkr/25d3746bdb35767d66c7ae6d26c2ed98 to your computer and use it in GitHub Desktop.
Setting up a PWA with install button in Next.js 15

Step 1: Create the manifest.json File

The manifest.json file provides metadata for your PWA, like its name, icons, and theme color.

  1. In the root of your public/ folder, create a manifest.json file:

    // public/manifest.json
    {
      "name": "Your App Name",
      "short_name": "App",
      "description": "Your app description",
      "start_url": "/",
      "display": "standalone",
      "background_color": "#ffffff",
      "theme_color": "#ffffff",
      "icons": [
        {
          "src": "/icons/icon-48x48.png",
          "sizes": "48x48",
          "type": "image/png"
        },
        {
          "src": "/icons/icon-72x72.png",
          "sizes": "72x72",
          "type": "image/png"
        },
        {
          "src": "/icons/icon-96x96.png",
          "sizes": "96x96",
          "type": "image/png"
        },
        {
          "src": "/icons/icon-144x144.png",
          "sizes": "144x144",
          "type": "image/png"
        },
        {
          "src": "/icons/icon-192x192.png",
          "sizes": "192x192",
          "type": "image/png"
        },
        {
          "src": "/icons/icon-256x256.png",
          "sizes": "256x256",
          "type": "image/png"
        },
        {
          "src": "/icons/icon-512x512.png",
          "sizes": "512x512",
          "type": "image/png"
        }
      ]
    }
  2. Add Icons: Place the required icon files (e.g., icon-192x192.png, icon-512x512.png, etc.) in the public/icons directory.

Step 2: Update the layout.tsx to Include Manifest and Meta Tags

In a Next.js App Router project, use layout.tsx in the app directory to add the necessary tags.

  1. Open your app/layout.tsx file and add the manifest link, theme color, and icon link in the <head> section.

    // app/layout.tsx
    import './globals.css';
    import { Metadata } from 'next';
    
    export const metadata: Metadata = {
      title: 'Your App Name',
      description: 'Your app description',
      themeColor: '#ffffff',
    };
    
    export default function RootLayout({
      children,
    }: {
      children: React.ReactNode;
    }) {
      return (
        <html lang="en">
          <head>
            <link rel="manifest" href="/manifest.json" />
            <link rel="icon" href="/icons/icon-192x192.png" />
            <meta name="theme-color" content="#ffffff" />
          </head>
          <body>{children}</body>
        </html>
      );
    }

Step 3: Add an Install Button for the PWA

This will allow users to manually trigger the PWA install prompt when they click a button.

  1. Create an InstallButton component that listens for the beforeinstallprompt event, which is triggered when the app meets the PWA installability criteria.

    // components/InstallButton.tsx
    import { useEffect, useState } from 'react';
    
    const InstallButton = () => {
      const [deferredPrompt, setDeferredPrompt] = useState<Event | null>(null);
      const [isInstallable, setIsInstallable] = useState(false);
    
      useEffect(() => {
        const handleBeforeInstallPrompt = (e: Event) => {
          e.preventDefault();
          setDeferredPrompt(e);
          setIsInstallable(true);
        };
    
        window.addEventListener('beforeinstallprompt', handleBeforeInstallPrompt);
    
        return () => window.removeEventListener('beforeinstallprompt', handleBeforeInstallPrompt);
      }, []);
    
      const handleInstallClick = async () => {
        if (deferredPrompt) {
          (deferredPrompt as any).prompt();
          const { outcome } = await (deferredPrompt as any).userChoice;
          setDeferredPrompt(null);
          setIsInstallable(false);
          console.log(`User response to the install prompt: ${outcome}`);
        }
      };
    
      return (
        <>
          {isInstallable && (
            <button onClick={handleInstallClick} className="install-button">
              Install App
            </button>
          )}
        </>
      );
    };
    
    export default InstallButton;
  2. Add InstallButton to your layout or any component where you’d like the install button to appear.

    // app/layout.tsx or another component
    import InstallButton from '@/components/InstallButton';
    
    export default function RootLayout({ children }: { children: React.ReactNode }) {
      return (
        <html lang="en">
          <head>
            <link rel="manifest" href="/manifest.json" />
            <meta name="theme-color" content="#ffffff" />
          </head>
          <body>
            <InstallButton />
            {children}
          </body>
        </html>
      );
    }

Step 4: Set Up the Service Worker (Optional)

To further optimize your PWA for offline capabilities, you can set up a service worker using next-pwa or another library. Here’s an example using next-pwa:

  1. Install next-pwa:

    npm install next-pwa
  2. Configure next-pwa in your next.config.js file:

    // next.config.js
    const withPWA = require('next-pwa')({
      dest: 'public',
    });
    
    module.exports = withPWA({
      // other Next.js configurations
    });
  3. After setting up next-pwa, a sw.js file will be generated automatically in the public/ directory during the build process. This file enables caching for offline use.

Step 5: Add Generated Service Worker Files to .gitignore

Since service workers like sw.js and workbox-xxxxxx.js are generated during the build process, add them to .gitignore:

# .gitignore
/public/sw.js
/public/workbox-*.js

Step 6: Test the PWA Setup

  1. Build and serve the app in production mode:

    npm run build && npm start
  2. Open your app in Chrome or another browser that supports PWAs, and check for the "Install" option in the browser’s address bar or try the custom install button.

  3. Test the offline functionality by going offline after the initial load to see if the app is available.

@chefDossier
Copy link

instead of manifest.json i use manifest.ts in the app folder can it work ?

@cdnkr
Copy link
Author

cdnkr commented Feb 19, 2025

@chefDossier Yeah, from my understanding you can use manifest.ts in the app/ folder, but you'd need to create a route to serve it. Something like:

app/manifest/route.ts

import { NextResponse } from 'next/server';

export function GET() {
  return NextResponse.json({
    name: "Your App Name",
    short_name: "App",
    description: "Your app description",
    start_url: "/",
    display: "standalone",
    background_color: "#ffffff",
    theme_color: "#ffffff",
    icons: [
      { src: "/icons/icon-192x192.png", sizes: "192x192", type: "image/png" },
      { src: "/icons/icon-512x512.png", sizes: "512x512", type: "image/png" }
    ]
  });
}

Then, reference it in layout.tsx like this:

<link rel="manifest" href="/manifest" />

I've never tried it like this but this is how I understand it...

@chefDossier
Copy link

like this

import type { MetadataRoute } from 'next'

export default function manifest(): MetadataRoute.Manifest {
return {
name: 'Fist',
short_name: 'Fist',
description: 'discotheque, restaurant, café, supermarché, snacks, glacier, cabaret',
start_url: '/',
display: 'standalone',
orientation: "portrait",
background_color: '#ffffff',
theme_color: '#fdfdfd',
icons: [
{
src: "/web-app-manifest-192x192.png",
sizes: "192x192",
type: "image/png",
purpose: "maskable"
},
{
src: "/web-app-manifest-512x512.png",
sizes: "512x512",
type: "image/png",
purpose: "maskable"
}
],
}
}

@cdnkr
Copy link
Author

cdnkr commented Feb 19, 2025

I see now, with your manifest.ts you'll have to reference it in app/layout.tsx so add this if you haven't already:

import type { Metadata } from 'next';

export const metadata: Metadata = {
  manifest: '/manifest.webmanifest',
  // ...any other metadata you already have
};

Then from the example above (Step 2), DON'T add this to your app/layout.tsx:

<link rel="manifest" href="/manifest.json" />

Since Next.jst will automatically add the manifest meta tag.

@B1u3f331
Copy link

B1u3f331 commented Mar 2, 2025

you are my hero!

@Jscripter-pk
Copy link

Jscripter-pk commented Apr 9, 2025

@cdnkr thanks for chromium based broswers but it don't seem to be working with safari. How can we make it work for safari do you have any idea?

@cdnkr
Copy link
Author

cdnkr commented Apr 9, 2025

@Jscripter-pk Safari doesn't have functionality for the native install prompt but users can still manually add the site to their home screen by following these steps:

  1. Click the share button (box with arrow) in the browser toolbar
  2. Select "Add to Home Screen"
  3. Click "Add"

You can see the full list of browser support here: https://caniuse.com/?search=install-app

It's a pain... But the way i've gotten around this is to show info text with the above steps if the user is using Safari.

@Jscripter-pk
Copy link

@cdnkr Thanks alot man. I have go through some of the articles and what you mentioned in the above comment is only solution we can do for the app as a work-around.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment