Skip to content

Progressive Web Application #685

Open

Description

@ngoiyaeric

You are an expert Next.js and PWA developer.

I have an existing Next.js application (using App Router, Next.js 14+). Convert it into a fully functional Progressive Web App (PWA) that can be installed/downloaded as a native-like app on Android (via Chrome) with the following requirements:

Core PWA Features Required:

  1. Web App Manifest (manifest.json)

    • Proper name, short_name, description
    • Multiple icon sizes (192x192, 512x512, maskable icons)
    • start_url, display: standalone, theme_color, background_color
    • orientation: portrait or any as needed
    • Categories and screenshots if possible
  2. Service Worker

    • Workbox or custom service worker for caching (pages, assets, API responses)
    • Offline support (show cached content or offline page)
    • Background sync / push notification readiness (even if not implementing push yet)
    • Proper caching strategies (NetworkFirst for API, CacheFirst for static assets)
  3. Next.js Configuration

    • Use next-pwa plugin if recommended, or manual setup with next.config.js / next.config.mjs
    • Correct handling of App Router (app/ directory)
    • Environment variables and build optimizations for PWA
    • HTTPS enforcement reminder / secure context
  4. Installation Experience on Android

    • Make the "Install" / "Add to home screen" prompt appear reliably in Chrome on Android
    • Proper meta tags (<meta name="theme-color">, apple-touch icons for broader compatibility)
    • Custom install button (optional but recommended) with beforeinstallprompt event
  5. Best Practices

    • Lighthouse PWA score ≥ 90
    • Fast loading (Core Web Vitals optimized)
    • Responsive design already assumed
    • Update handling (new version notification)
    • Fallback offline page

Deliverables:

  • Full updated next.config.js / next.config.mjs
  • Complete public/manifest.json
  • Service worker file(s) location and code
  • Any new components (e.g., InstallPrompt component)
  • Updated app/layout.tsx or _document.js with all necessary <Head> / meta tags
  • Instructions for:
    • Running in development mode with PWA
    • Building and testing the PWA
    • How to test installability on Android (Chrome DevTools + Android device)
    • Deploying to Vercel/Netlify (PWA-compatible hosting)

Assume the project uses TypeScript and Tailwind. Provide the complete code changes with clear file paths and diff-style explanations where helpful.

Start by asking me for any specific details about my app (name, icons, features, current folder structure) if needed, otherwise provide a complete ready-to-use implementation.

Activity

  1. ngoiyaeric commented on Jun 26, 2026

    @ngoiyaeric
    CollaboratorAuthor
  2. coderabbitai commented on Jun 26, 2026

    @coderabbitai
    Contributor

    Coding Plan

    Summary

    • Adopt @serwist/next (modern Workbox successor) for Next.js 15 App Router compatibility, wrapping the existing next.config.mjs without disturbing current settings.
    • Deliver the declarative install surface — manifest, generated icon set (from eva-logo.png), and Metadata/Viewport-based head tags using the earth theme colors — to make the Android/Chrome install prompt eligible.
    • Implement a TypeScript service worker with route-appropriate caching (CacheFirst for static, NetworkFirst for pages/APIs, no caching of mutations), an offline fallback page, and push/background-sync-ready scaffolding.
    • Add client-side InstallPrompt and update-notification UX (reusing the existing Toaster), plus concise dev/build/test/deploy documentation.
    Design Choices
    Design Choice 1: PWA tooling/library for service worker and build integration

    Options Considered:

    1. next-pwa — classic plugin, widely documented but unmaintained and unreliable on Next 15/App Router.
    2. @ducanh2912/next-pwa — community fork of next-pwa, works but largely superseded.
    3. @serwist/next (Serwist) — actively maintained Workbox successor with first-class App Router + Next 15 support, TypeScript service worker, and built-in injection/precache manifest.

    Chosen Option: 3

    Rationale: Option 3 (@serwist/next): it is the modern, maintained choice with proven Next.js 15 App Router compatibility, satisfies the Workbox/caching-strategy requirements, and avoids the build fragility of next-pwa.

    Design Choice 2: Source asset for generating required PWA icons (192, 512, maskable)

    Options Considered:

    1. Use public/images/eva-logo.png (252 KB PNG) as the source to generate sized/maskable icons.
    2. Use public/images/logo.svg (2.2 MB) rasterized to required sizes.

    Chosen Option: 1

    Rationale: Option 1: eva-logo.png is the most appropriately sized raster source and the simplest path to standard square + maskable icon variants.

    💡 User Tips

    Regenerate the plan with different choices with @coderabbitai <feedback>.

    Implementation Steps

    Phase 1: PWA Tooling and Build Configuration

    Introduce the service-worker build tooling and wire it into the existing Next.js configuration so the rest of the PWA assets have a working compilation/injection pipeline. This phase establishes the foundation without yet changing runtime UI.

    Task 1: Add Serwist dependencies

    Install the chosen PWA tooling and supporting types.

    • Add @serwist/next and serwist to package.json dependencies using bun.
    • Confirm versions are compatible with Next.js 15 / React 19; no other PWA packages (next-pwa, workbox-*) should be added.
    Task 2: Wrap next.config.mjs with the Serwist plugin

    Integrate service-worker generation into the build while preserving existing config.

    • Update next.config.mjs to import and apply the Serwist withSerwist/withSerwistInit wrapper around the current exported config object.
    • Preserve existing fields (output: 'standalone', experimental.serverActions, transpilePackages).
    • Configure the plugin to point at the TypeScript service-worker source (added in Phase 3) and the output destination (public/sw.js), and disable service-worker generation in development to avoid caching friction during local work.
    • Add a brief comment noting that output: 'standalone' requires the generated sw.js to be served from the public root scope (which it is, as a static public asset).
    🤖 Prompt for AI agents
    Set up the PWA build foundation by installing Serwist and integrating it into
    the Next.js config.
    
    - Install `@serwist/next` and `serwist` as dependencies using `bun`; do not
    install any other PWA packages such as `next-pwa` or `workbox-*`.
    - Update `next.config.mjs`:
    - Import and apply the Serwist `withSerwist`/`withSerwistInit` wrapper around
    the existing exported config object.
    - Preserve all existing config fields: `output: 'standalone'`,
    `experimental.serverActions`, and `transpilePackages`.
    - Configure the plugin with the TypeScript service-worker source path (to be
    created in Phase 3) and output destination `public/sw.js`.
    - Disable service-worker generation in development mode to avoid caching
    friction during local development.
    - Add a brief comment noting that `output: 'standalone'` requires the generated
    `sw.js` to be served from the public root scope (which it is, as a static public
    asset).
    

    Phase 2: Manifest, Icons, and Installability Metadata

    Provide the declarative install surface Android/Chrome needs: a valid manifest, a proper icon set, and the head metadata/theme-color tags that make the install prompt eligible and improve cross-platform (Apple) compatibility.

    Task 1: Generate the PWA icon set

    Create the standard and maskable icons required for installability.

    • Derive icons from public/images/eva-logo.png and place them under public/icons/.
    • Produce at minimum 192x192 and 512x512 "any" icons plus a 512x512 "maskable" variant (with adequate safe-zone padding) and an apple-touch-icon (180x180).
    • Keep file sizes small to support Core Web Vitals.
    Task 2: Author the web app manifest

    Define the installable app identity and presentation.

    • Create public/manifest.json (or app/manifest.ts route handler) with name, short_name, description, start_url (/), display: standalone, orientation (portrait or any), theme_color, and background_color.
    • Source colors from the default earth theme (e.g., background #ede8df, theme/accent #246024) documented in app/globals.css.
    • Reference the icon set from Task 2.1 including the maskable entry; add categories; include optional screenshots entries only if assets are available.
    Task 3: Add PWA head metadata to the root layout

    Make the manifest, theme color, and Apple compatibility tags discoverable.

    • Update the Metadata export in app/layout.tsx to add manifest (pointing to the manifest), an icons field (standard + apple-touch), and appleWebApp settings (capable, status bar style, title).
    • Update the Viewport export to add themeColor (optionally light/dark media variants matching earth/dark theme backgrounds), keeping the existing viewport zoom settings intact.
    • Avoid duplicating tags as raw <head> elements where the Metadata/Viewport exports already cover them; leave the existing HTMX shim script untouched.
    🤖 Prompt for AI agents
    Deliver the declarative install surface by generating the icon set, authoring
    the manifest, and wiring up PWA metadata in the root layout.
    
    **Icon Set (`public/icons/`):**
    - Derive all icons from `public/images/eva-logo.png`.
    - Produce at minimum: `192x192` and `512x512` "any" icons, a `512x512`
    "maskable" variant with adequate safe-zone padding, and an `apple-touch-icon` at
    `180x180`.
    - Keep file sizes small to support Core Web Vitals.
    
    **Web App Manifest (`public/manifest.json` or `app/manifest.ts`):**
    - Include fields: `name`, `short_name`, `description`, `start_url: "/"`,
    `display: "standalone"`, `orientation` (`portrait` or `any`), `theme_color`, and
    `background_color`.
    - Source colors from the default `earth` theme as documented in
    `app/globals.css` (e.g., background `#ede8df`, theme/accent `#246024`).
    - Reference the full icon set including the maskable entry; add `categories`;
    only include `screenshots` if assets are available.
    
    **Root Layout Metadata (`app/layout.tsx`):**
    - Update the `Metadata` export to add: `manifest` pointing to the manifest file,
    `icons` field covering standard and apple-touch icons, and `appleWebApp`
    settings (capable, status bar style, title).
    - Update the `Viewport` export to add `themeColor` (optionally with light/dark
    media variants matching `earth`/`dark` theme backgrounds), while keeping
    existing viewport zoom settings intact.
    - Do not duplicate tags as raw `<head>` elements where `Metadata`/`Viewport`
    exports already cover them; leave the existing HTMX shim script untouched.
    

    Phase 3: Service Worker, Caching Strategies, and Offline Fallback

    Implement the runtime offline behavior: a service worker that precaches the app shell, applies route-appropriate caching strategies, serves an offline fallback page, and is structured to be push/background-sync ready.

    Task 1: Create the service worker source

    Define the Serwist-based service worker with caching strategies.

    • Add a TypeScript service-worker entry (e.g., app/sw.ts) referenced by the Phase 1 plugin config.
    • Use Serwist's default precache manifest injection for the app shell/static build assets, with CacheFirst for immutable static assets (_next/static, images, fonts, icons).
    • Apply NetworkFirst for navigation/page requests and for /api/* responses (so authenticated, user-specific data stays fresh while remaining available offline); explicitly avoid caching mutating routes (POST /api/chat, DELETE /api/chats/all).
    • Optionally apply a short-TTL StaleWhileRevalidate for the deterministic GET /api/embeddings route.
    • Include placeholder/no-op event wiring (e.g., push/sync listeners or Serwist plugin hooks) to establish push/background-sync readiness without implementing a backend.
    Task 2: Add an offline fallback page

    Provide cached content when the network is unavailable.

    • Create an offline route under app/offline/ (a simple, statically renderable page consistent with existing layout/theming).
    • Configure the service worker to precache this page and serve it as the navigation fallback when a page request fails offline.
    🤖 Prompt for AI agents
    Implement the service worker with caching strategies and an offline fallback
    page.
    
    **Service Worker (`app/sw.ts`):**
    - Create a TypeScript service-worker entry at the path referenced in the Phase 1
    plugin config.
    - Use Serwist's default precache manifest injection for app shell/static build
    assets.
    - Apply `CacheFirst` strategy for immutable static assets: `_next/static`,
    images, fonts, and icons.
    - Apply `NetworkFirst` strategy for:
      - Navigation/page requests.
    - `/api/*` responses (to keep authenticated, user-specific data fresh while
    remaining available offline).
    - Explicitly exclude mutating routes from caching: `POST /api/chat` and `DELETE
    /api/chats/all`.
    - Optionally apply a short-TTL `StaleWhileRevalidate` strategy for `GET
    /api/embeddings`.
    - Include placeholder/no-op event wiring for `push` and `sync` listeners (or
    equivalent Serwist plugin hooks) to establish push/background-sync readiness
    without implementing a backend.
    
    **Offline Fallback Page (`app/offline/`):**
    - Create a simple, statically renderable offline page consistent with the
    existing app layout and theming.
    - Configure the service worker to precache this page and serve it as the
    navigation fallback when a page request fails while offline.
    

    Phase 4: Install/Update UX and Documentation

    Add the client-side experience that improves install reliability and surfaces new-version notifications, then document how to run, test, and deploy the PWA.

    Task 1: Build a custom Install prompt component

    Capture and trigger the Android install flow.

    • Create an InstallPrompt client component in components/ that listens for the beforeinstallprompt event, suppresses the default prompt, stores the deferred event, and renders a custom install button that calls prompt() on click.
    • Hide the button when already installed (display-mode: standalone) or when the event is unavailable; follow existing component/styling conventions (Tailwind, theme-aware).
    • Mount the component within app/layout.tsx's persistent UI region.
    Task 2: Add service-worker update notification handling

    Notify users when a new version is available.

    • Add client logic (a small component or hook in components/) that registers/observes the Serwist service-worker lifecycle, detects a waiting/updated worker, and surfaces a non-intrusive notification (reuse the existing Toaster/sonner setup) prompting a reload to activate the new version.
    • Mount alongside the install prompt in the root layout.
    Task 3: Document development, testing, and deployment

    Provide the operational instructions called out in the ticket deliverables.

    • Add a PWA section to project docs (e.g., a docs/PWA.md or README addition) covering: running locally (noting SW is disabled in dev), production build/serve to exercise the SW, testing installability via Chrome DevTools (Application tab → Manifest/Service Workers/Lighthouse) and on a physical Android device over HTTPS, and the HTTPS/secure-context requirement.
    • Include deployment notes for Vercel/Netlify confirming public/manifest.json, generated public/sw.js, and icons are served from the root and that output: 'standalone' remains compatible.
    🤖 Prompt for AI agents
    Implement the install prompt component, update notification handling, and add
    PWA documentation.
    
    **Install Prompt Component (`components/InstallPrompt`):**
    - Create a client component that listens for the `beforeinstallprompt` event,
    suppresses the default browser prompt, stores the deferred event, and renders a
    custom install button that calls `prompt()` on click.
    - Hide the button when already installed (detected via `display-mode:
    standalone` media query) or when the event is unavailable.
    - Follow existing component/styling conventions (Tailwind, theme-aware).
    - Mount the component in `app/layout.tsx`'s persistent UI region.
    
    **Service Worker Update Notification (component or hook in `components/`):**
    - Create a small component or hook that registers/observes the Serwist
    service-worker lifecycle.
    - Detect a waiting/updated worker and surface a non-intrusive notification using
    the existing `Toaster`/sonner setup, prompting a reload to activate the new
    version.
    - Mount this alongside the install prompt in the root layout.
    
    **Documentation (`docs/PWA.md` or README addition):**
    - Cover: running locally (noting SW is disabled in dev), production build/serve
    to exercise the SW, testing installability via Chrome DevTools (Application tab
    → Manifest/Service Workers/Lighthouse) and on a physical Android device over
    HTTPS, and the HTTPS/secure-context requirement.
    - Include deployment notes for Vercel/Netlify confirming `public/manifest.json`,
    generated `public/sw.js`, and icons are served from the root and that `output:
    'standalone'` remains compatible.
    
    Research

    This is a Next.js 15.3.8 / React 19 App Router project managed with bun. The root app/layout.tsx uses the modern Metadata and Viewport exports (currently only OG/Twitter cards, a locked-zoom viewport, and an inline HTMX shim in <head>), and wraps the app in a stack of context providers plus next-themes (default theme earth). next.config.mjs is minimal with output: 'standalone', server-action origins, and transpilePackages; it has no PWA tooling. middleware.ts is effectively pass-through and excludes _next/static, so a root-scoped service worker is unobstructed. public/ contains only images/ (with eva-logo.png as the sole viable icon source) — there is no manifest, no service worker, and no PWA icon set. API routes under app/api/ (chat/chats/embeddings/health) currently set no cache headers.


    🚀 Next Steps

    🤖 All AI agent prompts combined
    Task: 1
    
    Set up the PWA build foundation by installing Serwist and integrating it into
    the Next.js config.
    
    - Install `@serwist/next` and `serwist` as dependencies using `bun`; do not
    install any other PWA packages such as `next-pwa` or `workbox-*`.
    - Update `next.config.mjs`:
    - Import and apply the Serwist `withSerwist`/`withSerwistInit` wrapper around
    the existing exported config object.
    - Preserve all existing config fields: `output: 'standalone'`,
    `experimental.serverActions`, and `transpilePackages`.
    - Configure the plugin with the TypeScript service-worker source path (to be
    created in Phase 3) and output destination `public/sw.js`.
    - Disable service-worker generation in development mode to avoid caching
    friction during local development.
    - Add a brief comment noting that `output: 'standalone'` requires the generated
    `sw.js` to be served from the public root scope (which it is, as a static public
    asset).
    ===============================================================================
    
    Task: 2
    
    Deliver the declarative install surface by generating the icon set, authoring
    the manifest, and wiring up PWA metadata in the root layout.
    
    **Icon Set (`public/icons/`):**
    - Derive all icons from `public/images/eva-logo.png`.
    - Produce at minimum: `192x192` and `512x512` "any" icons, a `512x512`
    "maskable" variant with adequate safe-zone padding, and an `apple-touch-icon` at
    `180x180`.
    - Keep file sizes small to support Core Web Vitals.
    
    **Web App Manifest (`public/manifest.json` or `app/manifest.ts`):**
    - Include fields: `name`, `short_name`, `description`, `start_url: "/"`,
    `display: "standalone"`, `orientation` (`portrait` or `any`), `theme_color`, and
    `background_color`.
    - Source colors from the default `earth` theme as documented in
    `app/globals.css` (e.g., background `#ede8df`, theme/accent `#246024`).
    - Reference the full icon set including the maskable entry; add `categories`;
    only include `screenshots` if assets are available.
    
    **Root Layout Metadata (`app/layout.tsx`):**
    - Update the `Metadata` export to add: `manifest` pointing to the manifest file,
    `icons` field covering standard and apple-touch icons, and `appleWebApp`
    settings (capable, status bar style, title).
    - Update the `Viewport` export to add `themeColor` (optionally with light/dark
    media variants matching `earth`/`dark` theme backgrounds), while keeping
    existing viewport zoom settings intact.
    - Do not duplicate tags as raw `<head>` elements where `Metadata`/`Viewport`
    exports already cover them; leave the existing HTMX shim script untouched.
    ===============================================================================
    
    Task: 3
    
    Implement the service worker with caching strategies and an offline fallback
    page.
    
    **Service Worker (`app/sw.ts`):**
    - Create a TypeScript service-worker entry at the path referenced in the Phase 1
    plugin config.
    - Use Serwist's default precache manifest injection for app shell/static build
    assets.
    - Apply `CacheFirst` strategy for immutable static assets: `_next/static`,
    images, fonts, and icons.
    - Apply `NetworkFirst` strategy for:
      - Navigation/page requests.
    - `/api/*` responses (to keep authenticated, user-specific data fresh while
    remaining available offline).
    - Explicitly exclude mutating routes from caching: `POST /api/chat` and `DELETE
    /api/chats/all`.
    - Optionally apply a short-TTL `StaleWhileRevalidate` strategy for `GET
    /api/embeddings`.
    - Include placeholder/no-op event wiring for `push` and `sync` listeners (or
    equivalent Serwist plugin hooks) to establish push/background-sync readiness
    without implementing a backend.
    
    **Offline Fallback Page (`app/offline/`):**
    - Create a simple, statically renderable offline page consistent with the
    existing app layout and theming.
    - Configure the service worker to precache this page and serve it as the
    navigation fallback when a page request fails while offline.
    ===============================================================================
    
    Task: 4
    
    Implement the install prompt component, update notification handling, and add
    PWA documentation.
    
    **Install Prompt Component (`components/InstallPrompt`):**
    - Create a client component that listens for the `beforeinstallprompt` event,
    suppresses the default browser prompt, stores the deferred event, and renders a
    custom install button that calls `prompt()` on click.
    - Hide the button when already installed (detected via `display-mode:
    standalone` media query) or when the event is unavailable.
    - Follow existing component/styling conventions (Tailwind, theme-aware).
    - Mount the component in `app/layout.tsx`'s persistent UI region.
    
    **Service Worker Update Notification (component or hook in `components/`):**
    - Create a small component or hook that registers/observes the Serwist
    service-worker lifecycle.
    - Detect a waiting/updated worker and surface a non-intrusive notification using
    the existing `Toaster`/sonner setup, prompting a reload to activate the new
    version.
    - Mount this alongside the install prompt in the root layout.
    
    **Documentation (`docs/PWA.md` or README addition):**
    - Cover: running locally (noting SW is disabled in dev), production build/serve
    to exercise the SW, testing installability via Chrome DevTools (Application tab
    → Manifest/Service Workers/Lighthouse) and on a physical Android device over
    HTTPS, and the HTTPS/secure-context requirement.
    - Include deployment notes for Vercel/Netlify confirming `public/manifest.json`,
    generated `public/sw.js`, and icons are served from the root and that `output:
    'standalone'` remains compatible.
    
    💡 Iterate on the plan with: `@coderabbitai` <feedback>
    Example Feedback
    - `@coderabbitai` You can skip phase 3. Add a simple unit test case for phase 2.
    - `@coderabbitai` For design choice 1 go ahead with option 3 and replan.
    

    💬 Have feedback or questions? Drop into our discord!

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions