framework-to-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):
// 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,
};
Step 2: Build Static Files
bun run build
This creates an out/ directory with static files.
Step 3: Install Capacitor
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:
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
bun add @capacitor/ios @capacitor/android
bunx cap add ios
bunx cap add android
Step 6: Build and Sync
# Build Next.js
bun run build
# Sync with native projects
bunx cap sync
Step 7: Run on Device
iOS:
bunx cap open ios
# Build and run in Xcode
Android:
bunx cap open android
# Build and run in Android Studio
Next.js Routing Considerations
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.js Image Optimization
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} />;
};
Next.js API Routes
API routes don't work in static export. Use alternatives:
- External API: Call a separate backend
- Capacitor plugins: Use native features
- Local storage: Use
@capacitor/preferences
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:
// 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>;
}
Complete Next.js + Capacitor Example
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 + Capacitor
React works great with Capacitor using Vite or Create React App.
Option 1: Vite (Recommended)
Create new project:
bun create vite my-app --template react-ts
cd my-app
bun install
Install Capacitor:
bun add @capacitor/core @capacitor/cli
bunx 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:
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:
bunx create-react-app my-app --template typescript
cd my-app
Install Capacitor:
bun add @capacitor/core @capacitor/cli
bunx cap init
capacitor.config.ts:
const config: CapacitorConfig = {
appId: 'com.company.app',
appName: 'My App',
webDir: 'build', // CRA outputs to build/
};
Build and sync:
bun run build
bunx cap sync
React Router Configuration
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 + Capacitor
Vue works seamlessly with Capacitor.
Create Vue + Capacitor Project
Using Vite:
bun create vite my-app --template vue-ts
cd my-app
bun install
Install Capacitor:
bun add @capacitor/core @capacitor/cli
bunx 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:
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:
// 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:
bunx @angular/cli new my-app
cd my-app
Install Capacitor:
bun add @capacitor/core @capacitor/cli
bunx 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:
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:
// 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 + Capacitor
Svelte and SvelteKit work great with Capacitor.
SvelteKit + Capacitor
Create SvelteKit app:
bunx create-svelte my-app
cd my-app
bun install
Install adapter-static:
bun add -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:
bun add @capacitor/core @capacitor/cli
bunx cap init
capacitor.config.ts:
const config: CapacitorConfig = {
appId: 'com.company.app',
appName: 'My App',
webDir: 'build',
};
Build and sync:
bun run build
bunx cap sync
Vite + Svelte (Simpler Option)
Create with Vite:
bun create vite my-app --template svelte-ts
cd my-app
bun install
Install Capacitor:
bun add @capacitor/core @capacitor/cli
bunx cap init
capacitor.config.ts:
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:
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:
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:
bun add @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 });
4. Native UI Components
Use Ionic Framework for any framework:
bun add @ionic/core
React:
bun add @ionic/react @ionic/react-router
Vue:
bun add @ionic/vue @ionic/vue-router
Angular:
bun add @ionic/angular
5. Storage
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();
6. Camera Access
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;
Build Scripts for All Frameworks
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"
}
}
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:
- Check build output directory matches
webDirin capacitor.config.ts - Rebuild:
bun run build - Sync:
bunx cap sync - 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:
- Use production API URLs
- Configure CORS on backend
- Use Capacitor HTTP plugin for native requests:
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
webDirin 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-designskill - Konsta UI: See
konsta-uiskill
Next Steps
- Choose your framework and follow the setup above
- Configure static export/build
- Install and configure Capacitor
- Add platforms (iOS/Android)
- Build and sync
- Test on devices
- Add native features with plugins
- Set up live updates with Capgo