| name | capacitor-best-practices |
| description | Best practices for Capacitor app development including project structure, plugin usage, performance optimization, security, and deployment. Use this skill when reviewing Capacitor code, setting up new projects, or optimizing existing apps. |
Capacitor Best Practices
Comprehensive guidelines for building production-ready Capacitor applications.
When to Use This Skill
- Setting up a new Capacitor project
- Reviewing Capacitor app architecture
- Optimizing app performance
- Implementing security measures
- Preparing for app store submission
Project Structure
Recommended Directory Layout
my-app/
├── src/ # Web app source
├── android/ # Android native project
├── ios/ # iOS native project
├── capacitor.config.ts # Capacitor configuration
├── package.json
└── tsconfig.json
Configuration Best Practices
capacitor.config.ts (CORRECT):
import type { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.company.app',
appName: 'My App',
webDir: 'dist',
server: {
...(process.env.NODE_ENV === 'development' && {
url: 'http://localhost:5173',
cleartext: true,
}),
},
plugins: {
SplashScreen: {
launchAutoHide: false,
},
},
};
export default config;
capacitor.config.json (AVOID):
{
"server": {
"url": "http://localhost:5173",
"cleartext": true
}
}
Never commit development server URLs to production
Plugin Usage
CRITICAL: Always Use Latest Capacitor
Keep Capacitor core packages in sync:
bun add @capacitor/core@latest @capacitor/cli@latest
bun add @capacitor/ios@latest @capacitor/android@latest
bunx cap sync
Plugin Installation Pattern
CORRECT:
bun add @capgo/capacitor-native-biometric
bunx cap sync
cd ios/App && pod install && cd ../..
INCORRECT:
bun add @capgo/capacitor-native-biometric
Plugin Initialization
CORRECT - Check availability before use:
import { NativeBiometric, BiometryType } from '@capgo/capacitor-native-biometric';
async function authenticate() {
const { isAvailable, biometryType } = await NativeBiometric.isAvailable();
if (!isAvailable) {
return authenticateWithPassword();
}
try {
await NativeBiometric.verifyIdentity({
reason: 'Authenticate to access your account',
title: 'Biometric Login',
});
return true;
} catch (error) {
return false;
}
}
INCORRECT - No availability check:
await NativeBiometric.verifyIdentity({ reason: 'Login' });
Performance Optimization
CRITICAL: Lazy Load Plugins
CORRECT - Dynamic imports:
async function scanDocument() {
const { DocumentScanner } = await import('@capgo/capacitor-document-scanner');
return DocumentScanner.scanDocument();
}
INCORRECT - Import everything at startup:
import { DocumentScanner } from '@capgo/capacitor-document-scanner';
import { NativeBiometric } from '@capgo/capacitor-native-biometric';
import { Camera } from '@capacitor/camera';
HIGH: Optimize WebView Performance
CORRECT - Use hardware acceleration:
<application
android:hardwareAccelerated="true"
android:largeHeap="true">
<key>UIViewGroupOpacity</key>
<false/>
HIGH: Minimize Bridge Calls
CORRECT - Batch operations:
await Storage.set({
key: 'userData',
value: JSON.stringify({ name, email, preferences }),
});
INCORRECT - Multiple bridge calls:
await Storage.set({ key: 'name', value: name });
await Storage.set({ key: 'email', value: email });
await Storage.set({ key: 'preferences', value: JSON.stringify(preferences) });
MEDIUM: Image Optimization
CORRECT:
import { Camera, CameraResultType } from '@capacitor/camera';
const photo = await Camera.getPhoto({
quality: 80,
width: 1024,
resultType: CameraResultType.Uri,
correctOrientation: true,
});
INCORRECT:
const photo = await Camera.getPhoto({
quality: 100,
resultType: CameraResultType.Base64,
});
Security Best Practices
CRITICAL: Secure Storage
CORRECT - Use secure storage for sensitive data:
import { NativeBiometric } from '@capgo/capacitor-native-biometric';
await NativeBiometric.setCredentials({
username: 'user@example.com',
password: 'secret',
server: 'api.myapp.com',
});
const credentials = await NativeBiometric.getCredentials({
server: 'api.myapp.com',
});
INCORRECT - Plain storage:
import { Preferences } from '@capacitor/preferences';
await Preferences.set({
key: 'password',
value: 'secret',
});
CRITICAL: Certificate Pinning
For production apps handling sensitive data:
const config: CapacitorConfig = {
plugins: {
CapacitorHttp: {
enabled: true,
},
},
server: {
cleartext: false,
},
};
HIGH: Root/Jailbreak Detection
import { IsRoot } from '@capgo/capacitor-is-root';
async function checkDeviceSecurity() {
const { isRooted } = await IsRoot.isRooted();
if (isRooted) {
showSecurityWarning('Device appears to be rooted/jailbroken');
}
}
HIGH: App Tracking Transparency (iOS)
import { AppTrackingTransparency } from '@capgo/capacitor-app-tracking-transparency';
async function requestTracking() {
const { status } = await AppTrackingTransparency.requestPermission();
if (status === 'authorized') {
}
}
Error Handling
CRITICAL: Always Handle Plugin Errors
CORRECT:
import { Camera, CameraResultType } from '@capacitor/camera';
async function takePhoto() {
try {
const image = await Camera.getPhoto({
quality: 90,
resultType: CameraResultType.Uri,
});
return image;
} catch (error) {
if (error.message === 'User cancelled photos app') {
return null;
}
if (error.message.includes('permission')) {
showPermissionDialog();
return null;
}
console.error('Camera error:', error);
throw error;
}
}
INCORRECT:
const image = await Camera.getPhoto({ quality: 90 });
Live Updates
Using Capacitor Updater
import { CapacitorUpdater } from '@capgo/capacitor-updater';
CapacitorUpdater.notifyAppReady();
CapacitorUpdater.addListener('updateAvailable', async (update) => {
const bundle = await CapacitorUpdater.download({
url: update.url,
version: update.version,
});
await CapacitorUpdater.set(bundle);
});
Update Strategy
CORRECT - Background download, apply on restart:
const bundle = await CapacitorUpdater.download({ url, version });
await CapacitorUpdater.set(bundle);
INCORRECT - Interrupt user:
const bundle = await CapacitorUpdater.download({ url, version });
await CapacitorUpdater.reload();
Native Project Management
iOS: Use Swift Package Manager (SPM)
Modern approach - prefer SPM over CocoaPods:
target 'App' do
capacitor_pods
end
Android: Gradle Configuration
// android/app/build.gradle
android {
defaultConfig {
minSdkVersion 22
targetSdkVersion 34
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
Testing
Plugin Mocking
jest.mock('@capgo/capacitor-native-biometric', () => ({
NativeBiometric: {
isAvailable: jest.fn().mockResolvedValue({
isAvailable: true,
biometryType: 'touchId',
}),
verifyIdentity: jest.fn().mockResolvedValue({}),
},
}));
Platform Detection
import { Capacitor } from '@capacitor/core';
if (Capacitor.isNativePlatform()) {
} else {
}
if (Capacitor.getPlatform() === 'ios') {
}
Deployment Checklist
Resources