npx claudepluginhub cap-go/capgo-skills --plugin capacitor-app-migrationsThis skill is limited to using the following tools:
Comprehensive guide for integrating web frameworks with Capacitor to build mobile apps.
Migrates web apps, PWAs, SPAs to store-ready Capacitor iOS/Android apps. Addresses thin WebView rejections, native UX, permissions, offline support, billing, testing, Capgo updates.
Recommends official Capacitor plugins first, with Capgo and community alternatives for native iOS/Android features like camera, biometrics, geolocation, and payments. Use for plugin selection and comparisons.
Guides building pixel-perfect iOS and Material Design UIs in Capacitor apps using Konsta UI with React, Vue, Svelte, and Tailwind CSS.
Share bugs, ideas, or general feedback.
Comprehensive guide for integrating web frameworks with Capacitor to build mobile apps.
Detected framework and build dependencies:
!node -e "const fs=require('fs');if(!fs.existsSync('package.json'))process.exit(0);const pkg=JSON.parse(fs.readFileSync('package.json','utf8'));const matchers=['next','react','vue','@angular/core','@sveltejs/kit','@builder.io/qwik','@remix-run/react','solid-js','vite','@capacitor/core','@capacitor/cli'];const out=[];for(const section of ['dependencies','devDependencies']){for(const [name,version] of Object.entries(pkg[section]||{})){if(matchers.includes(name))out.push(section+'.'+name+'='+version)}}for(const [name,cmd] of Object.entries(pkg.scripts||{})){if(['build','export','sync','cap:sync'].includes(name))out.push('scripts.'+name+'='+cmd)}console.log(out.join('\n'))"
Relevant framework and Capacitor config paths:
!find . -maxdepth 3 \( -name 'next.config.js' -o -name 'next.config.mjs' -o -name 'vite.config.ts' -o -name 'vite.config.js' -o -name 'angular.json' -o -name 'svelte.config.js' -o -name 'capacitor.config.json' -o -name 'capacitor.config.ts' -o -name 'capacitor.config.js' \)
| Framework | Static Export | SSR Support | Recommended Approach |
|---|---|---|---|
| Next.js | ✅ Yes | ❌ No | Static export (output: 'export') |
| React | ✅ Yes | N/A | Create React App or Vite |
| Vue | ✅ Yes | ❌ No | Vite or Vue CLI |
| Angular | ✅ Yes | ❌ No | Angular CLI |
| Svelte | ✅ Yes | ❌ No | SvelteKit with adapter-static |
| Remix | ✅ Yes | ❌ No | SPA mode |
| Solid | ✅ Yes | ❌ No | Vite |
| Qwik | ✅ Yes | ❌ No | Static site mode |
CRITICAL: Capacitor requires static HTML/CSS/JS files. SSR (Server-Side Rendering) does not work in native apps.
Next.js is popular for React apps. Capacitor requires static export.
For Next.js 13+ (App Router):
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
images: {
unoptimized: true, // Required for static export
},
trailingSlash: true, // Helps with routing on mobile
};
module.exports = nextConfig;
For Next.js 12 (Pages Router):
// next.config.js
module.exports = {
output: 'export',
images: {
unoptimized: true,
},
trailingSlash: true,
};
npm run build
This creates an out/ directory with static files.
npm install @capacitor/core @capacitor/cli
npx cap init
Configuration:
out (Next.js static export output)Create capacitor.config.ts:
import type { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.company.app',
appName: 'My App',
webDir: 'out', // Next.js static export directory
server: {
androidScheme: 'https',
},
};
export default config;
npm install @capacitor/ios @capacitor/android
npx cap add ios
npx cap add android
# Build Next.js
npm run build
# Sync with native projects
npx cap sync
iOS:
npx cap open ios
# Build and run in Xcode
Android:
npx cap open android
# Build and run in Android Studio
Use hash routing for complex apps:
// next.config.js
const nextConfig = {
output: 'export',
basePath: '',
assetPrefix: '',
};
Or use Next.js's built-in routing (works with trailingSlash: true).
next/image doesn't work with static export. Use alternatives:
Option 1: Use standard img tag
// Instead of next/image
<img src="/images/photo.jpg" alt="Photo" />
Option 2: Use a custom Image component
// components/CapacitorImage.tsx
import { Capacitor } from '@capacitor/core';
export const CapacitorImage = ({ src, alt, ...props }) => {
const isNative = Capacitor.isNativePlatform();
const imageSrc = isNative ? src : src;
return <img src={imageSrc} alt={alt} {...props} />;
};
API routes don't work in static export. Use alternatives:
@capacitor/preferencesimport { Preferences } from '@capacitor/preferences';
// Save data
await Preferences.set({
key: 'user',
value: JSON.stringify(userData),
});
// Load data
const { value } = await Preferences.get({ key: 'user' });
const userData = JSON.parse(value || '{}');
Middleware doesn't work in static export. Handle logic client-side:
// In your React components
import { useEffect } from 'react';
import { useRouter } from 'next/router';
export default function ProtectedPage() {
const router = useRouter();
useEffect(() => {
const checkAuth = async () => {
const { value } = await Preferences.get({ key: 'token' });
if (!value) {
router.push('/login');
}
};
checkAuth();
}, []);
return <div>Protected content</div>;
}
package.json:
{
"name": "my-capacitor-app",
"scripts": {
"dev": "next dev",
"build": "next build",
"build:mobile": "next build && cap sync",
"ios": "cap open ios",
"android": "cap open android"
},
"dependencies": {
"next": "^14.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"@capacitor/core": "^6.0.0",
"@capacitor/ios": "^6.0.0",
"@capacitor/android": "^6.0.0",
"@capacitor/camera": "^6.0.0"
},
"devDependencies": {
"@capacitor/cli": "^6.0.0",
"typescript": "^5.0.0"
}
}
React works great with Capacitor using Vite or Create React App.
Create new project:
npx create-vite@latest my-app --template react-ts
cd my-app
npm install
Install Capacitor:
npm install @capacitor/core @capacitor/cli
npx cap init
Configure vite.config.ts:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
build: {
outDir: 'dist', // Capacitor webDir
},
});
capacitor.config.ts:
import type { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.company.app',
appName: 'My App',
webDir: 'dist',
};
export default config;
Add platforms and build:
npm install @capacitor/ios @capacitor/android
npx cap add ios
npx cap add android
npm run build
npx cap sync
Create new project:
npx create-react-app my-app --template typescript
cd my-app
Install Capacitor:
npm install @capacitor/core @capacitor/cli
npx cap init
capacitor.config.ts:
const config: CapacitorConfig = {
appId: 'com.company.app',
appName: 'My App',
webDir: 'build', // CRA outputs to build/
};
Build and sync:
npm run build
npx cap sync
Use HashRouter for mobile:
import { HashRouter as Router, Routes, Route } from 'react-router-dom';
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Router>
);
}
Vue works seamlessly with Capacitor.
Using Vite:
npx create-vite@latest my-app --template vue-ts
cd my-app
npm install
Install Capacitor:
npm install @capacitor/core @capacitor/cli
npx cap init
vite.config.ts:
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
build: {
outDir: 'dist',
},
});
capacitor.config.ts:
const config: CapacitorConfig = {
appId: 'com.company.app',
appName: 'My App',
webDir: 'dist',
};
Add platforms:
npm install @capacitor/ios @capacitor/android
npx cap add ios
npx cap add android
npm run build
npx cap sync
Use hash mode for mobile:
// router/index.ts
import { createRouter, createWebHashHistory } from 'vue-router';
const router = createRouter({
history: createWebHashHistory(),
routes: [
{ path: '/', component: Home },
{ path: '/about', component: About },
],
});
export default router;
Angular has excellent Capacitor integration.
Create Angular app:
npx @angular/cli new my-app
cd my-app
Install Capacitor:
npm install @capacitor/core @capacitor/cli
npx cap init
capacitor.config.ts:
const config: CapacitorConfig = {
appId: 'com.company.app',
appName: 'My App',
webDir: 'dist/my-app/browser', // Angular 17+ output
};
For Angular 16 and below:
webDir: 'dist/my-app',
Add platforms:
npm install @capacitor/ios @capacitor/android
npx cap add ios
npx cap add android
npm run build
npx cap sync
HashLocationStrategy for mobile:
// app.config.ts (Angular 17+)
import { provideRouter, withHashLocation } from '@angular/router';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes, withHashLocation()),
],
};
For Angular 16 and below:
// app.module.ts
import { LocationStrategy, HashLocationStrategy } from '@angular/common';
@NgModule({
providers: [
{ provide: LocationStrategy, useClass: HashLocationStrategy }
],
})
export class AppModule {}
Svelte and SvelteKit work great with Capacitor.
Create SvelteKit app:
npx create-svelte my-app
cd my-app
npm install
Install adapter-static:
npm install -D @sveltejs/adapter-static
Configure svelte.config.js:
import adapter from '@sveltejs/adapter-static';
const config = {
kit: {
adapter: adapter({
pages: 'build',
assets: 'build',
fallback: 'index.html',
}),
},
};
export default config;
Install Capacitor:
npm install @capacitor/core @capacitor/cli
npx cap init
capacitor.config.ts:
const config: CapacitorConfig = {
appId: 'com.company.app',
appName: 'My App',
webDir: 'build',
};
Build and sync:
npm run build
npx cap sync
Create with Vite:
npx create-vite@latest my-app --template svelte-ts
cd my-app
npm install
Install Capacitor:
npm install @capacitor/core @capacitor/cli
npx cap init
capacitor.config.ts:
const config: CapacitorConfig = {
appId: 'com.company.app',
appName: 'My App',
webDir: 'dist',
};
Detect if running in native app:
import { Capacitor } from '@capacitor/core';
const isNative = Capacitor.isNativePlatform();
const platform = Capacitor.getPlatform(); // 'ios', 'android', or 'web'
if (isNative) {
// Use native plugins
} else {
// Use web APIs
}
Handle deep links in your app:
import { App } from '@capacitor/app';
App.addListener('appUrlOpen', (data) => {
// Handle deep link
const slug = data.url.split('.app').pop();
// Navigate to route
});
Add live updates to any framework:
npm install @capgo/capacitor-updater
import { CapacitorUpdater } from '@capgo/capacitor-updater';
// Check for updates
const { id } = await CapacitorUpdater.download({
url: 'https://api.capgo.app/updates',
});
// Apply update
await CapacitorUpdater.set({ id });
Use Ionic Framework for any framework:
npm install @ionic/core
React:
npm install @ionic/react @ionic/react-router
Vue:
npm install @ionic/vue @ionic/vue-router
Angular:
npm install @ionic/angular
Use Capacitor Preferences for all frameworks:
import { Preferences } from '@capacitor/preferences';
// Set value
await Preferences.set({ key: 'theme', value: 'dark' });
// Get value
const { value } = await Preferences.get({ key: 'theme' });
// Remove value
await Preferences.remove({ key: 'theme' });
// Clear all
await Preferences.clear();
Same API across all frameworks:
import { Camera, CameraResultType } from '@capacitor/camera';
const photo = await Camera.getPhoto({
quality: 90,
allowEditing: true,
resultType: CameraResultType.Uri,
});
const imageUrl = photo.webPath;
Add these to package.json:
{
"scripts": {
"dev": "vite", // or next dev, ng serve, etc.
"build": "vite build", // or next build, ng build, etc.
"build:mobile": "vite build && cap sync",
"ios": "cap run ios",
"android": "cap run android",
"sync": "cap sync"
}
}
Hash mode (recommended for mobile):
#/aboutHistory mode (requires server):
/aboutRecommendation: Use hash mode for Capacitor apps.
Cause: Incorrect webDir or build output.
Solution:
webDir in capacitor.config.tsnpm run buildnpx cap syncCause: Using history mode without proper configuration.
Solution: Switch to hash routing:
HashRoutercreateWebHashHistory()HashLocationStrategyCause: Build-time variables not being replaced.
Solution: Use framework-specific env variable patterns:
NEXT_PUBLIC_VITE_REACT_APP_environment.tsCause: CORS or localhost URLs.
Solution:
import { CapacitorHttp } from '@capacitor/core';
const response = await CapacitorHttp.get({
url: 'https://api.example.com/data',
});
Ionic Framework provides native UI components:
Konsta UI for Tailwind CSS:
See ionic-design and konsta-ui skills for details.
output: 'export')webDir in capacitor.config.tsFor detailed guides on specific frameworks:
ionic-design skillkonsta-ui skill