Build a PWA with Nuxt 4 and Capacitor

by Didin J. on Oct 25, 2025 Build a PWA with Nuxt 4 and Capacitor

Build a fast, offline-ready hybrid app using Nuxt 4, PWA, and Capacitor. Learn to deploy your web app as a native Android and iOS app with one codebase.

Progressive Web Apps (PWAs) have become a powerful way to deliver fast, reliable, and engaging experiences across all platforms — web, mobile, and desktop — using standard web technologies. They combine the best of web and native apps, offering offline access, push notifications, and installability, without the need for app stores.

On the other hand, Capacitor, developed by the Ionic team, bridges the gap between web and native by allowing you to run your web app as a fully functional native app on Android, iOS, and desktop platforms. Capacitor provides access to native device APIs (camera, geolocation, file system, etc.) while maintaining a single codebase.

With the release of Nuxt 4, building performant, modern web apps has become even more seamless. Nuxt 4 introduces improvements in performance, stability, and developer experience, along with full support for modern SSR, hybrid rendering, and Vite-powered builds. Its modular architecture also makes it easy to integrate progressive enhancements like PWAs.

In this tutorial, you’ll learn step-by-step how to build a Progressive Web App using Nuxt 4 and Capacitor, enabling your app to run smoothly as both a web PWA and a native mobile app. You’ll start by setting up a fresh Nuxt 4 project, adding PWA support using the official @vite-pwa/nuxt module, and then wrapping it with Capacitor for mobile deployment.

By the end of this guide, you’ll have a working Nuxt 4 application that:
✅ Functions as a PWA with offline caching and manifest setup
✅ Can be installed on desktop or mobile home screens
✅ Runs as a native app using Capacitor on Android or iOS


Prerequisites

Before diving into building your Progressive Web App (PWA) with Nuxt 4 and Capacitor, make sure your development environment is properly set up. This ensures smooth installation, building, and deployment across both web and mobile platforms.

What You’ll Need

  1. Basic Knowledge

    • Familiarity with Vue.js and Nuxt fundamentals (pages, components, and routing).

    • Understanding of basic web development tools (Node.js, npm, or yarn).

  2. Installed Software

    • Node.js – version 20.x or later
      Check your version:

       
      node -v

       

      If you don’t have it, download it from nodejs.org.

    • npm (comes with Node.js) or yarn/pnpm for package management.
      Example check:

       
      npm -v

       

    • Capacitor CLI – the official command-line tool to add native platforms:

       
      npm install -g @capacitor/cli

       

    • Git – optional but recommended for version control.

  3. Optional (for mobile builds)

    • Android Studio – to build and test on Android devices or emulators.

    • Xcode – for macOS users building iOS apps.

    • A physical or virtual mobile device – to test the app on native platforms.

  4. Recommended Editor

    • Visual Studio Code with the Volar and TypeScript Vue Plugin extensions for the best Nuxt 4 experience.

Project Setup Overview

Here’s what we’ll do in this tutorial:

  1. Create a Nuxt 4 app using the latest nuxi CLI.

  2. Install and configure the PWA module (@vite-pwa/nuxt).

  3. Add a Capacitor and integrate it with the Nuxt build output.

  4. Build and test the PWA both on the web and on native devices.


Create a New Nuxt 4 Project

Let’s start by setting up a fresh Nuxt 4 project that will serve as the foundation for our Progressive Web App. Nuxt 4 uses the new nuxi CLI tool, which simplifies project creation and configuration.

Step 1: Create a New Nuxt 4 App

Open your terminal and run the following command to create a new Nuxt project:

npx nuxi init nuxt4-pwa-app

Then navigate into the project folder:

cd nuxt4-pwa-app

This command scaffolds a minimal Nuxt 4 app with TypeScript, Vite, and a ready-to-use directory structure.

Step 2: Install Dependencies

Once the project has been initialized, install all dependencies:

npm install

Or, if you prefer using Yarn or pnpm:

yarn install
# or
pnpm install

Step 3: Run the Development Server

Start the Nuxt development server to verify that everything works correctly:

npm run dev

You should see output similar to:

Nuxt 4  ready in 2.1s
Local:    http://localhost:3000/

Open your browser at http://localhost:3000/ — you’ll see the default Nuxt 4 welcome screen.

Build a PWA with Nuxt 4 and Capacitor - npm run dev

Step 4: Project Structure Overview

After creation, your project structure will look like this:

nuxt4-pwa-app/
│
├─ .nuxt/                 # Auto-generated build files
├─ node_modules/          # Installed dependencies
├─ public/                # Static assets (icons, manifest, etc.)
├─ pages/                 # Application pages
├─ components/            # Vue components
├─ nuxt.config.ts         # Nuxt 4 configuration file
├─ package.json           # Project dependencies and scripts
└─ tsconfig.json          # TypeScript configuration

We’ll modify these files in the next steps to add PWA features and integrate Capacitor.

At this point, you have a working Nuxt 4 application ready to be enhanced with PWA capabilities.


Add and Configure the PWA Module

Now that your Nuxt 4 app is up and running, it’s time to make it a Progressive Web App (PWA). We’ll use the official @vite-pwa/nuxt module, which provides full integration between Nuxt 4 and the Vite PWA plugin.

This module automatically generates a web manifest, service worker, and handles offline caching — everything your app needs to be installable and work offline.

Step 1: Install the PWA Module

Run the following command inside your Nuxt project:

npm install @vite-pwa/nuxt

Step 2: Enable the PWA Module in nuxt.config.ts

Open your nuxt.config.ts file and add @vite-pwa/nuxt to the modules array:

// nuxt.config.ts
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
  modules: ['@vite-pwa/nuxt'],
  compatibilityDate: '2025-07-15',
  devtools: { enabled: true },
  pwa: {
    registerType: 'autoUpdate',
    manifest: {
      name: 'Nuxt 4 PWA App',
      short_name: 'NuxtPWA',
      description: 'A Progressive Web App built with Nuxt 4 and Capacitor',
      theme_color: '#ffffff',
      background_color: '#ffffff',
      display: 'standalone',
      start_url: '/',
      icons: [
        {
          src: '/pwa-192x192.png',
          sizes: '192x192',
          type: 'image/png'
        },
        {
          src: '/pwa-512x512.png',
          sizes: '512x512',
          type: 'image/png'
        }
      ]
    },
    workbox: {
      cleanupOutdatedCaches: true,
      clientsClaim: true
    },
    devOptions: {
      enabled: true, // enable PWA during development
      type: 'module'
    }
  },
  nitro: {
    preset: 'static' // ✅ ensures static files are generated
  }
})

Step 3: Add PWA Icons

Add your app icons inside the /public directory.
For now, you can create placeholder icons or download sample ones:

public/
├─ pwa-192x192.png
└─ pwa-512x512.png

These icons will be referenced in your web manifest and used for the app installation prompt.

Step 4: Test the PWA

Run your development server again:

npm run dev

Then open http://localhost:3000/ in Chrome.
Open Developer Tools → Application → Manifest to verify:

  • A manifest file (manifest.webmanifest) is detected.

  • The service worker is registered under Service Workers.

  • The app is installable.

You can also click “Install” in the browser’s address bar to test installation.

At this point, your Nuxt 4 application is fully PWA-enabled — complete with caching, offline access, and installability.


Add a Capacitor to the Project

Now that your Nuxt 4 app is a working PWA, it’s time to extend its reach to native mobile platforms using Capacitor. A capacitor allows you to run your web app as a native app on Android, iOS, and even desktop, while maintaining a single codebase.

In this section, you’ll install Capacitor, initialize it in your Nuxt project, and prepare it for mobile builds.

Step 1: Install Capacitor Packages

From your project root, install the Capacitor core libraries and CLI:

npm install @capacitor/core
npm install -D @capacitor/cli

Step 2: Initialize Capacitor

Next, initialize Capacitor to create its configuration files:

npx cap init

You’ll be prompted to enter a few details:

  • App name: Nuxt 4 PWA App

  • App ID: something unique like com.djamware.nuxt4pwa

This creates two key files:

  • capacitor.config.ts – main Capacitor configuration file

  • package.json updates with @capacitor/cli scripts

Step 3: Configure the Build Output Directory

By default, Nuxt builds its production files into the .output/public directory.
We need to tell Capacitor to load that directory when building the native app.

Open capacitor.config.json and update it like this:

{
  "appId": "com.djamware.nuxt4pwa",
  "appName": "Nuxt 4 PWA App",
  "webDir": ".output/public",
  "bundledWebRuntime": false
}

This ensures Capacitor knows where your Nuxt production build files are located.

Step 4: Add a Build Script for Capacitor

In your package.json, add the following script under "scripts":

  "scripts": {
    "build": "nuxt build",
    "dev": "nuxt dev",
    "generate": "nuxt generate",
    "preview": "nuxt preview",
    "postinstall": "nuxt prepare",
    "build:cap": "nuxt build && npx cap copy"
  },

The build:cap command will:

  1. Build the Nuxt app for production.

  2. Copy the generated files into Capacitor’s native project directories.

Step 5: Add a Native Platform (Android or iOS)

You can now add a native platform:

npm install @capacitor/android
npx cap add android
# or
npm install @capacitor/ios
npx cap add ios

This command will create an android/ or ios/ folder in your project, each containing a complete native project ready to run in Android Studio or Xcode.

Step 6: Sync and Run the Project

After adding a platform, sync your web build with Capacitor:

npm run build:cap

Then open the native project in your IDE:

npx cap open android
# or
npx cap open ios

This will launch Android Studio or Xcode, where you can build and run your Nuxt PWA natively on an emulator or device.

At this stage, your Nuxt 4 app is integrated with Capacitor and can run as a native Android or iOS application while still functioning as a PWA in browsers.


Integrate Capacitor with Nuxt Build

Now that your Nuxt 4 app can generate a proper static output, and Capacitor can successfully copy it to native platforms, let’s verify and fine-tune the integration so everything works seamlessly — both as a PWA in the browser and as a native app on Android or iOS.

Step 1: Verify the Build Output

Make sure Nuxt generates your static files correctly:

npm run build

After the build completes, confirm that the .output/public directory contains:

index.html
manifest.webmanifest
_sw.js (if generated by @vite-pwa/nuxt)
assets/

If you see these files, Nuxt has successfully produced a static version that Capacitor can use.

Step 2: Sync and Copy the Build to Capacitor

Run the following commands to sync and copy your web build into the native project folders:

npx cap sync
npx cap copy

These commands:

  • Ensure Capacitor recognizes your latest web build.

  • Copy the contents of .output/public to the corresponding Android/iOS project’s www directory.

You should see output similar to:

✔ copy android - done in 1.2s
✔ copy ios - done in 0.9s

Step 3: Test the App in the Native Environment

Open your native project in its respective IDE:

npx cap open android
# or
npx cap open ios

This will launch Android Studio or Xcode.

From there:

  • Run the app on a simulator or connected device.

  • You should see your Nuxt 4 app rendered inside a native web view.

  • Check that offline access and installability still work (Capacitor includes the same PWA behavior by default).

Step 4: Automate the Nuxt–Capacitor Build Flow

To streamline your workflow, update your package.json with a new build script that runs all the necessary steps in one command:

  "scripts": {
    "build": "nuxt build",
    "dev": "nuxt dev",
    "generate": "nuxt generate",
    "preview": "nuxt preview",
    "postinstall": "nuxt prepare",
    "build:cap": "nuxt build && npx cap sync && npx cap copy"
  },

Now, you can just run:

npm run build:cap

to rebuild Nuxt, sync Capacitor, and update your native projects automatically.

Step 5: Handle Environment Differences (Web vs. Native)

Sometimes you may need to load different configurations depending on where the app runs (web or native). Capacitor exposes a simple API to detect this:

import { Capacitor } from '@capacitor/core';

if (Capacitor.isNativePlatform()) {
  console.log('Running in a native app!');
} else {
  console.log('Running on the web!');
}

You can use this logic to:

  • Adjust API endpoints.

  • Disable browser-only features.

  • Modify layouts or navigation for native builds.

Step 6: Confirm PWA + Native Coexistence

Your Nuxt app should now:
✅ Load normally as a PWA in browsers (with offline caching).
✅ Run as a native app with Capacitor on Android or iOS.
✅ Share a single codebase with platform-specific behavior when needed.

To verify everything works together:

  • Run npm run dev and test the web app’s PWA features.

  • Run npm run build:cap and test the native app build.
    Both should function identically, except for native integrations.

At this stage, you’ve successfully integrated Capacitor with the Nuxt 4 build process — achieving a unified workflow for web, PWA, and native mobile deployment.


Build and Run on Android/iOS

Now that Capacitor and Nuxt 4 are fully integrated, it’s time to build, run, and test your Progressive Web App as a native mobile application on Android and iOS.
This section will guide you through the complete build process — from creating a production-ready web build to launching it inside a native environment.

Step 1: Build the Production Version

Start by generating the static build of your Nuxt 4 app that Capacitor will package into the native project:

npm run build

Then, sync the build with Capacitor to copy it into your platform projects:

npx cap sync

Alternatively, you can use your all-in-one command:

npm run build:cap

This ensures that the latest Nuxt build is deployed into your android/ or ios/ folders.

Step 2: Run the Android App

If you’ve already added Android as a platform (npx cap add android), open the project in Android Studio:

npx cap open android

In Android Studio:

  1. Wait for Gradle to sync and build.

  2. Select your test device or emulator.

  3. Click Run ▶️ to build and launch the app.

Your Nuxt 4 app should appear as a native Android application, loading instantly just like a PWA — but now with full native capabilities such as access to the camera, file system, or notifications via Capacitor plugins.

Step 3: Run the iOS App

For macOS users with Xcode installed, open the iOS project:

npx cap open ios

Then in Xcode:

  1. Choose a simulator (e.g., iPhone 15) or connect your physical device.

  2. Click the Run button (▶).

  3. Wait for the app to compile and install.

You’ll see the same Nuxt 4 interface running natively inside a WebView on iOS.

Step 4: Debugging and Live Reload (Optional)

Capacitor supports a development workflow where you can serve the app locally and view changes in real time on a connected device.

To enable live reload during development:

  1. Run the Nuxt development server:

     
    npm run dev

     

  2. Edit your capacitor.config.json temporarily to point to your local dev server:

     
    {
      "server": {
        "url": "http://YOUR_LOCAL_IP:3000",
        "cleartext": true
      }
    }

     

  3. Sync the project:

     
    npx cap sync

     

Now, when you run your native app, it will load the Nuxt app directly from your dev server — any saved changes will instantly appear on your phone or simulator.

💡 Tip: Don’t forget to remove the server.url section before creating production builds.

Step 5: Build and Ship to Stores (Optional)

Once your PWA + Capacitor app is stable and ready:

  • For Android: use Android Studio → Build → Generate Signed App Bundle to prepare an .aab or .apk for Google Play.

  • For iOS: use Xcode → Product → Archive to upload your app to TestFlight or the App Store.

Your Nuxt 4 + Capacitor app can now live both as a web PWA and a published native app — powered by a single codebase.

At this point, you’ve successfully built, run, and tested your Nuxt 4 PWA as a native app on Android and iOS.


Best Practices for PWA + Capacitor Apps

Combining Nuxt 3, PWA, and Capacitor gives you the best of both worlds — web performance and native-like experience. However, to ensure your app remains maintainable, efficient, and production-ready, follow these best practices:

1. Use a Unified Codebase

Keep your web and native app code in a single Nuxt project. Use platform detection (like Capacitor’s Capacitor.isNativePlatform()) to conditionally load native plugins only when running on a device.

import { Capacitor } from '@capacitor/core'

if (Capacitor.isNativePlatform()) {
  // Use native plugins here
} else {
  // Fallback for web
}

2. Always Test Both Web and Native Builds

Before every release:

  • Run npm run dev or npm run build to ensure your PWA works perfectly in the browser.

  • Run npx cap sync android or npx cap sync ios to confirm all native assets and configs are up to date.

3. Optimize for Offline Experience

Since your Nuxt PWA uses caching and service workers:

  • Cache critical assets and API responses with the Nuxt PWA module.

  • Gracefully handle offline states by showing cached content or a custom “You’re offline” page.

Example (using PWA module config in nuxt.config.ts):

pwa: {
  workbox: {
    navigateFallback: '/offline.html',
  },
}

4. Manage Permissions Carefully

Capacitor plugins often require permissions (camera, location, etc.).
Always request them at runtime and only when needed to avoid unnecessary prompts and app store rejections.

Example for Camera plugin:

import { Camera } from '@capacitor/camera'

const photo = await Camera.getPhoto({
  quality: 90,
  allowEditing: false,
  resultType: 'base64'
})

5. Use Environment Variables

Separate configurations for development, staging, and production using .env files and Nuxt’s runtime configuration:

// nuxt.config.ts
runtimeConfig: {
  public: {
    apiBase: process.env.API_BASE_URL || 'https://api.example.com'
  }
}

Then in code:

const config = useRuntimeConfig()
console.log(config.public.apiBase)

6. Optimize App Performance

  • Enable lazy loading for components and routes.

  • Use image optimization via Nuxt Image module.

  • Keep bundle size small by importing only necessary Capacitor plugins.

7. Update Dependencies Regularly

Capacitor, Nuxt, and plugin ecosystems evolve quickly. Run:

npm outdated

and periodically update your dependencies to maintain compatibility and security.

8. Test with Real Devices

Emulators are good for initial testing, but real devices reveal platform-specific issues — especially around file systems, camera access, or push notifications.

9. Automate Builds

Integrate CI/CD pipelines (like GitHub Actions or GitLab CI) to automate:

  • Nuxt build

  • Capacitor sync

  • App store deployment

10. Keep a Consistent App Manifest

Your manifest.json should accurately reflect your PWA and native app identity:

{
  "name": "Nuxt PWA App",
  "short_name": "NuxtPWA",
  "theme_color": "#00DC82",
  "background_color": "#ffffff",
  "display": "standalone",
  "start_url": "/"
}

This ensures consistent branding across web and native versions.

Pro Tip:
If your PWA already supports installation, make sure it gracefully transitions between web-installed and native-installed states by syncing user data via local storage or APIs.


Conclusion and Next Steps

You’ve now successfully transformed your Nuxt 3 app into a Progressive Web App (PWA) and integrated it with Capacitor, allowing it to run seamlessly as a native app on Android and iOS — all from a single codebase.

This setup gives you a modern, scalable architecture capable of serving both web users and mobile users without maintaining separate projects.

What You’ve Learned

By following this tutorial, you covered the full workflow of hybrid app development:

  1. Created a Nuxt 3 app — using the latest version of Vue and Vite-powered builds.

  2. Added the Nuxt PWA module — to enable offline caching, installability, and app manifest.

  3. Configured and installed Capacitor — bridging your web app to native device capabilities.

  4. Built and tested on Android/iOS — with working PWA and native versions.

  5. Applied best practices — for offline support, environment separation, and performance.

Benefits of This Approach

  • 🧩 Single Codebase — One project for web and mobile.

  • Fast Updates — Web updates automatically reflect in PWA; native builds just need sync.

  • 📱 Native Features — Access camera, filesystem, push notifications, and more with Capacitor plugins.

  • 🌐 Offline-First Experience — Works even without an internet connection.

Next Steps

Here’s how to extend your hybrid Nuxt app even further:

  1. Add Native Features
    Integrate Capacitor plugins like:

    • @capacitor/geolocation (location tracking)

    • @capacitor/haptics (vibration feedback)

    • @capacitor/push-notifications (push messages)

  2. Deploy Your Web App
    Host your Nuxt PWA on:

    • Vercel

    • Netlify

    • Cloudflare Pages

    Example:

    npm run build
    npm run generate
    vercel deploy
  3. Publish to App Stores

    • For Android: use Android Studio → Build → Generate Signed APK/AAB

    • For iOS: open the project in Xcode → Product → Archive → Distribute App

  4. Automate with CI/CD
    Use GitHub Actions or GitLab pipelines to automate Nuxt builds, Capacitor sync, and deployment steps.

  5. Monitor Performance
    Track web and app analytics with Google Analytics 4, Firebase Analytics, or Vercel Insights.

Final Thoughts

By combining Nuxt 3, PWA, and Capacitor, you’ve built a future-ready app that merges the power of the web with the reach of native platforms.

This setup is perfect for startups, MVPs, or production-grade applications that need to serve users across multiple devices — efficiently and cost-effectively.

💡 Tip: Continue exploring Capacitor plugins and Nuxt’s ecosystem (such as Nuxt UI and Nuxt Image) to keep improving user experience and performance.

You can get the full source code on our GitHub.

That's just the basics. If you need more deep learning about Nuxt.js, you can take the following cheap course:

Thanks!