---
name: framework-to-capacitor
description: Guide for integrating modern web frameworks with Capacitor. Covers Next.js static export, React, Vue, Angular, Svelte, and others. Use this skill when converting framework apps to mobile apps with Capacitor.
---
# Framework to Capacitor Integration
Comprehensive guide for integrating web frameworks with Capacitor to build mobile apps.
## When to Use This Skill
- Converting a Next.js app to a mobile app
- Integrating React, Vue, Angular, or Svelte with Capacitor
- Configuring static exports for Capacitor
- Setting up routing for mobile apps
- Optimizing framework builds for native platforms
## Framework Support Matrix
| 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 + Capacitor
Next.js is popular for React apps. Capacitor requires static export.
### Step 1: Create or Update next.config.js
**For Next.js 13+ (App Router):**
```javascript
// 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):**
```javascript
// next.config.js
module.exports = {
output: 'export',
images: {
unoptimized: true,
},
trailingSlash: true,
};
```
### Step 2: Build Static Files
```bash
bun run build
```
This creates an `out/` directory with static files.
### Step 3: Install Capacitor
```bash
bun add @capacitor/core @capacitor/cli
bunx cap init
```
**Configuration:**
- **App name**: Your app name
- **App ID**: com.company.app
- **Web directory**: `out` (Next.js static export output)
### Step 4: Configure Capacitor
**Create capacitor.config.ts:**
```typescript
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;
```
### Step 5: Add Platforms
```bash
bun add @capacitor/ios @capacitor/android
bunx cap add ios
bunx cap add android
```
### Step 6: Build and Sync
```bash
# Build Next.js
bun run build
# Sync with native projects
bunx cap sync
```
### Step 7: Run on Device
**iOS:**
```bash
bunx cap open ios
# Build and run in Xcode
```
**Android:**
```bash
bunx cap open android
# Build and run in Android Studio
```
### Next.js Routing Considerations
**Use hash routing for complex apps:**
```typescript
// next.config.js
const nextConfig = {
output: 'export',
basePath: '',
assetPrefix: '',
};
```
**Or use Next.js's built-in routing** (works with `trailingSlash: true`).
### Next.js Image Optimization
**next/image doesn't work with static export. Use alternatives:**
**Option 1: Use standard img tag**
```jsx
// Instead of next/image
```
**Option 2: Use a custom Image component**
```tsx
// components/CapacitorImage.tsx
import { Capacitor } from '@capacitor/core';
export const CapacitorImage = ({ src, alt, ...props }) => {
const isNative = Capacitor.isNativePlatform();
const imageSrc = isNative ? src : src;
return
;
};
```
### Next.js API Routes
**API routes don't work in static export.** Use alternatives:
1. **External API**: Call a separate backend
2. **Capacitor plugins**: Use native features
3. **Local storage**: Use `@capacitor/preferences`
```typescript
import { 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 || '{}');
```
### Next.js Middleware
**Middleware doesn't work in static export.** Handle logic client-side:
```typescript
// 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
Protected content
;
}
```
### Complete Next.js + Capacitor Example
**package.json:**
```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 + Capacitor
React works great with Capacitor using Vite or Create React App.
### Option 1: Vite (Recommended)
**Create new project:**
```bash
bun create vite my-app --template react-ts
cd my-app
bun install
```
**Install Capacitor:**
```bash
bun add @capacitor/core @capacitor/cli
bunx cap init
```
**Configure vite.config.ts:**
```typescript
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
build: {
outDir: 'dist', // Capacitor webDir
},
});
```
**capacitor.config.ts:**
```typescript
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:**
```bash
bun add @capacitor/ios @capacitor/android
bunx cap add ios
bunx cap add android
bun run build
bunx cap sync
```
### Option 2: Create React App
**Create new project:**
```bash
bunx create-react-app my-app --template typescript
cd my-app
```
**Install Capacitor:**
```bash
bun add @capacitor/core @capacitor/cli
bunx cap init
```
**capacitor.config.ts:**
```typescript
const config: CapacitorConfig = {
appId: 'com.company.app',
appName: 'My App',
webDir: 'build', // CRA outputs to build/
};
```
**Build and sync:**
```bash
bun run build
bunx cap sync
```
### React Router Configuration
**Use HashRouter for mobile:**
```tsx
import { HashRouter as Router, Routes, Route } from 'react-router-dom';
function App() {
return (
} />
} />
);
}
```
---
## Vue + Capacitor
Vue works seamlessly with Capacitor.
### Create Vue + Capacitor Project
**Using Vite:**
```bash
bun create vite my-app --template vue-ts
cd my-app
bun install
```
**Install Capacitor:**
```bash
bun add @capacitor/core @capacitor/cli
bunx cap init
```
**vite.config.ts:**
```typescript
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
build: {
outDir: 'dist',
},
});
```
**capacitor.config.ts:**
```typescript
const config: CapacitorConfig = {
appId: 'com.company.app',
appName: 'My App',
webDir: 'dist',
};
```
**Add platforms:**
```bash
bun add @capacitor/ios @capacitor/android
bunx cap add ios
bunx cap add android
bun run build
bunx cap sync
```
### Vue Router Configuration
**Use hash mode for mobile:**
```typescript
// 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 + Capacitor
Angular has excellent Capacitor integration.
### Create Angular + Capacitor Project
**Create Angular app:**
```bash
bunx @angular/cli new my-app
cd my-app
```
**Install Capacitor:**
```bash
bun add @capacitor/core @capacitor/cli
bunx cap init
```
**capacitor.config.ts:**
```typescript
const config: CapacitorConfig = {
appId: 'com.company.app',
appName: 'My App',
webDir: 'dist/my-app/browser', // Angular 17+ output
};
```
**For Angular 16 and below:**
```typescript
webDir: 'dist/my-app',
```
**Add platforms:**
```bash
bun add @capacitor/ios @capacitor/android
bunx cap add ios
bunx cap add android
bun run build
bunx cap sync
```
### Angular Router Configuration
**HashLocationStrategy for mobile:**
```typescript
// app.config.ts (Angular 17+)
import { provideRouter, withHashLocation } from '@angular/router';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes, withHashLocation()),
],
};
```
**For Angular 16 and below:**
```typescript
// app.module.ts
import { LocationStrategy, HashLocationStrategy } from '@angular/common';
@NgModule({
providers: [
{ provide: LocationStrategy, useClass: HashLocationStrategy }
],
})
export class AppModule {}
```
---
## Svelte + Capacitor
Svelte and SvelteKit work great with Capacitor.
### SvelteKit + Capacitor
**Create SvelteKit app:**
```bash
bunx create-svelte my-app
cd my-app
bun install
```
**Install adapter-static:**
```bash
bun add -D @sveltejs/adapter-static
```
**Configure svelte.config.js:**
```javascript
import adapter from '@sveltejs/adapter-static';
const config = {
kit: {
adapter: adapter({
pages: 'build',
assets: 'build',
fallback: 'index.html',
}),
},
};
export default config;
```
**Install Capacitor:**
```bash
bun add @capacitor/core @capacitor/cli
bunx cap init
```
**capacitor.config.ts:**
```typescript
const config: CapacitorConfig = {
appId: 'com.company.app',
appName: 'My App',
webDir: 'build',
};
```
**Build and sync:**
```bash
bun run build
bunx cap sync
```
### Vite + Svelte (Simpler Option)
**Create with Vite:**
```bash
bun create vite my-app --template svelte-ts
cd my-app
bun install
```
**Install Capacitor:**
```bash
bun add @capacitor/core @capacitor/cli
bunx cap init
```
**capacitor.config.ts:**
```typescript
const config: CapacitorConfig = {
appId: 'com.company.app',
appName: 'My App',
webDir: 'dist',
};
```
---
## Common Patterns Across Frameworks
### 1. Environment Detection
**Detect if running in native app:**
```typescript
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
}
```
### 2. Deep Linking
**Handle deep links in your app:**
```typescript
import { App } from '@capacitor/app';
App.addListener('appUrlOpen', (data) => {
// Handle deep link
const slug = data.url.split('.app').pop();
// Navigate to route
});
```
### 3. Live Updates with Capgo
**Add live updates to any framework:**
```bash
bun add @capgo/capacitor-updater
```
```typescript
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 });
```
### 4. Native UI Components
**Use Ionic Framework for any framework:**
```bash
bun add @ionic/core
```
**React:**
```bash
bun add @ionic/react @ionic/react-router
```
**Vue:**
```bash
bun add @ionic/vue @ionic/vue-router
```
**Angular:**
```bash
bun add @ionic/angular
```
### 5. Storage
**Use Capacitor Preferences for all frameworks:**
```typescript
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();
```
### 6. Camera Access
**Same API across all frameworks:**
```typescript
import { Camera, CameraResultType } from '@capacitor/camera';
const photo = await Camera.getPhoto({
quality: 90,
allowEditing: true,
resultType: CameraResultType.Uri,
});
const imageUrl = photo.webPath;
```
---
## Build Scripts for All Frameworks
**Add these to package.json:**
```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"
}
}
```
---
## Routing Best Practices
### Hash vs. History Mode
**Hash mode (recommended for mobile):**
- Works without server configuration
- URLs look like: `#/about`
- No server-side routing needed
**History mode (requires server):**
- Clean URLs: `/about`
- Requires server fallback to index.html
- Can have issues on mobile
**Recommendation**: Use hash mode for Capacitor apps.
---
## Common Issues and Solutions
### Issue: Blank Screen on Mobile
**Cause**: Incorrect `webDir` or build output.
**Solution:**
1. Check build output directory matches `webDir` in capacitor.config.ts
2. Rebuild: `bun run build`
3. Sync: `bunx cap sync`
4. Check browser console in device
### Issue: Routing Doesn't Work
**Cause**: Using history mode without proper configuration.
**Solution:**
Switch to hash routing:
- React: `HashRouter`
- Vue: `createWebHashHistory()`
- Angular: `HashLocationStrategy`
- SvelteKit: Configure fallback
### Issue: Environment Variables Not Working
**Cause**: Build-time variables not being replaced.
**Solution:**
Use framework-specific env variable patterns:
- Next.js: `NEXT_PUBLIC_`
- Vite: `VITE_`
- Create React App: `REACT_APP_`
- Angular: `environment.ts`
### Issue: API Calls Fail on Device
**Cause**: CORS or localhost URLs.
**Solution:**
1. Use production API URLs
2. Configure CORS on backend
3. Use Capacitor HTTP plugin for native requests:
```typescript
import { CapacitorHttp } from '@capacitor/core';
const response = await CapacitorHttp.get({
url: 'https://api.example.com/data',
});
```
---
## Framework-Specific Plugins
**Ionic Framework provides native UI components:**
- **@ionic/react** - React components
- **@ionic/vue** - Vue components
- **@ionic/angular** - Angular components
**Konsta UI for Tailwind CSS:**
- Works with React, Vue, Svelte
- iOS and Material Design themes
See `ionic-design` and `konsta-ui` skills for details.
---
## Deployment Checklist
- [ ] Configure static export (Next.js: `output: 'export'`)
- [ ] Set correct `webDir` in capacitor.config.ts
- [ ] Use hash routing for mobile
- [ ] Disable image optimization (Next.js)
- [ ] Remove SSR/API routes dependencies
- [ ] Add native permissions (Info.plist, AndroidManifest.xml)
- [ ] Test on physical devices
- [ ] Configure splash screen and icons
- [ ] Set up live updates with Capgo (optional)
- [ ] Build and test on iOS and Android
---
## Resources
- **Capacitor Docs**: https://capacitorjs.com/docs
- **Next.js Static Export**: https://nextjs.org/docs/app/building-your-application/deploying/static-exports
- **Ionic Framework**: https://ionicframework.com
- **Capgo Blog**: https://capgo.app/blog
- **Community Forum**: https://forum.ionicframework.com
---
## Framework-Specific Guides
For detailed guides on specific frameworks:
- **Next.js + Capacitor**: https://capgo.app/blog/how-to-use-capacitor-with-nextjs
- **Ionic Framework**: See `ionic-design` skill
- **Konsta UI**: See `konsta-ui` skill
---
## Next Steps
1. Choose your framework and follow the setup above
2. Configure static export/build
3. Install and configure Capacitor
4. Add platforms (iOS/Android)
5. Build and sync
6. Test on devices
7. Add native features with plugins
8. Set up live updates with Capgo