capacitor-best-practices

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Capacitor Best Practices

Capacitor应用开发最佳实践

Comprehensive guidelines for building production-ready Capacitor applications.
构建可投入生产的Capacitor应用的综合指南。

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
  • 搭建新的Capacitor项目
  • 评审Capacitor应用架构
  • 优化应用性能
  • 实施安全防护措施
  • 为应用商店提交做准备

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
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):
typescript
import type { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
  appId: 'com.company.app',
  appName: 'My App',
  webDir: 'dist',
  server: {
    // Only enable for development
    ...(process.env.NODE_ENV === 'development' && {
      url: 'http://localhost:5173',
      cleartext: true,
    }),
  },
  plugins: {
    SplashScreen: {
      launchAutoHide: false,
    },
  },
};

export default config;
capacitor.config.json (AVOID):
json
{
  "server": {
    "url": "http://localhost:5173",
    "cleartext": true
  }
}
Never commit development server URLs to production
capacitor.config.ts (正确示例):
typescript
import type { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
  appId: 'com.company.app',
  appName: 'My App',
  webDir: 'dist',
  server: {
    // Only enable for development
    ...(process.env.NODE_ENV === 'development' && {
      url: 'http://localhost:5173',
      cleartext: true,
    }),
  },
  plugins: {
    SplashScreen: {
      launchAutoHide: false,
    },
  },
};

export default config;
capacitor.config.json (不推荐示例):
json
{
  "server": {
    "url": "http://localhost:5173",
    "cleartext": true
  }
}
切勿将开发服务器URL提交到生产环境

Plugin Usage

插件使用

CRITICAL: Always Use Latest Capacitor

重要提示:始终使用最新版本的Capacitor

Keep Capacitor core packages in sync:
bash
bun add @capacitor/core@latest @capacitor/cli@latest
bun add @capacitor/ios@latest @capacitor/android@latest
bunx cap sync
保持Capacitor核心包版本同步:
bash
bun add @capacitor/core@latest @capacitor/cli@latest
bun add @capacitor/ios@latest @capacitor/android@latest
bunx cap sync

Plugin Installation Pattern

插件安装规范

CORRECT:
bash
undefined
正确示例:
bash
undefined

1. Install the package

1. Install the package

bun add @capgo/capacitor-native-biometric
bun add @capgo/capacitor-native-biometric

2. Sync native projects

2. Sync native projects

bunx cap sync
bunx cap sync

3. For iOS: Install pods (or use SPM)

3. For iOS: Install pods (or use SPM)

cd ios/App && pod install && cd ../..

**INCORRECT**:
```bash
cd ios/App && pod install && cd ../..

**错误示例**:
```bash

Missing sync step

Missing sync step

bun add @capgo/capacitor-native-biometric
bun add @capgo/capacitor-native-biometric

App crashes because native code not linked

App crashes because native code not linked

undefined
undefined

Plugin Initialization

插件初始化

CORRECT - Check availability before use:
typescript
import { NativeBiometric, BiometryType } from '@capgo/capacitor-native-biometric';

async function authenticate() {
  const { isAvailable, biometryType } = await NativeBiometric.isAvailable();

  if (!isAvailable) {
    // Fallback to password
    return authenticateWithPassword();
  }

  try {
    await NativeBiometric.verifyIdentity({
      reason: 'Authenticate to access your account',
      title: 'Biometric Login',
    });
    return true;
  } catch (error) {
    // User cancelled or biometric failed
    return false;
  }
}
INCORRECT - No availability check:
typescript
// Will crash if biometrics not available
await NativeBiometric.verifyIdentity({ reason: 'Login' });
正确示例 - 使用前检查可用性:
typescript
import { NativeBiometric, BiometryType } from '@capgo/capacitor-native-biometric';

async function authenticate() {
  const { isAvailable, biometryType } = await NativeBiometric.isAvailable();

  if (!isAvailable) {
    // Fallback to password
    return authenticateWithPassword();
  }

  try {
    await NativeBiometric.verifyIdentity({
      reason: 'Authenticate to access your account',
      title: 'Biometric Login',
    });
    return true;
  } catch (error) {
    // User cancelled or biometric failed
    return false;
  }
}
错误示例 - 未检查可用性:
typescript
// Will crash if biometrics not available
await NativeBiometric.verifyIdentity({ reason: 'Login' });

Performance Optimization

性能优化

CRITICAL: Lazy Load Plugins

重要提示:延迟加载插件

CORRECT - Dynamic imports:
typescript
// Only load when needed
async function scanDocument() {
  const { DocumentScanner } = await import('@capgo/capacitor-document-scanner');
  return DocumentScanner.scanDocument();
}
INCORRECT - Import everything at startup:
typescript
// Increases initial bundle size
import { DocumentScanner } from '@capgo/capacitor-document-scanner';
import { NativeBiometric } from '@capgo/capacitor-native-biometric';
import { Camera } from '@capacitor/camera';
// ... 20 more plugins
正确示例 - 动态导入:
typescript
// Only load when needed
async function scanDocument() {
  const { DocumentScanner } = await import('@capgo/capacitor-document-scanner');
  return DocumentScanner.scanDocument();
}
错误示例 - 启动时导入所有插件:
typescript
// Increases initial bundle size
import { DocumentScanner } from '@capgo/capacitor-document-scanner';
import { NativeBiometric } from '@capgo/capacitor-native-biometric';
import { Camera } from '@capacitor/camera';
// ... 20 more plugins

HIGH: Optimize WebView Performance

高优先级:优化WebView性能

CORRECT - Use hardware acceleration:
xml
<!-- android/app/src/main/AndroidManifest.xml -->
<application
    android:hardwareAccelerated="true"
    android:largeHeap="true">
xml
<!-- ios/App/App/Info.plist -->
<key>UIViewGroupOpacity</key>
<false/>
正确示例 - 启用硬件加速:
xml
<!-- android/app/src/main/AndroidManifest.xml -->
<application
    android:hardwareAccelerated="true"
    android:largeHeap="true">
xml
<!-- ios/App/App/Info.plist -->
<key>UIViewGroupOpacity</key>
<false/>

HIGH: Minimize Bridge Calls

高优先级:减少桥接调用

CORRECT - Batch operations:
typescript
// Single call with batch data
await Storage.set({
  key: 'userData',
  value: JSON.stringify({ name, email, preferences }),
});
INCORRECT - Multiple bridge calls:
typescript
// Each call crosses the JS-native bridge
await Storage.set({ key: 'name', value: name });
await Storage.set({ key: 'email', value: email });
await Storage.set({ key: 'preferences', value: JSON.stringify(preferences) });
正确示例 - 批量操作:
typescript
// Single call with batch data
await Storage.set({
  key: 'userData',
  value: JSON.stringify({ name, email, preferences }),
});
错误示例 - 多次桥接调用:
typescript
// Each call crosses the JS-native bridge
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:
typescript
import { Camera, CameraResultType } from '@capacitor/camera';

const photo = await Camera.getPhoto({
  quality: 80,           // Not 100
  width: 1024,           // Reasonable max
  resultType: CameraResultType.Uri,  // Not Base64 for large images
  correctOrientation: true,
});
INCORRECT:
typescript
const photo = await Camera.getPhoto({
  quality: 100,
  resultType: CameraResultType.Base64,  // Memory intensive
  // No size limits
});
正确示例:
typescript
import { Camera, CameraResultType } from '@capacitor/camera';

const photo = await Camera.getPhoto({
  quality: 80,           // Not 100
  width: 1024,           // Reasonable max
  resultType: CameraResultType.Uri,  // Not Base64 for large images
  correctOrientation: true,
});
错误示例:
typescript
const photo = await Camera.getPhoto({
  quality: 100,
  resultType: CameraResultType.Base64,  // Memory intensive
  // No size limits
});

Security Best Practices

安全最佳实践

CRITICAL: Secure Storage

重要提示:安全存储

CORRECT - Use secure storage for sensitive data:
typescript
import { NativeBiometric } from '@capgo/capacitor-native-biometric';

// Store credentials securely
await NativeBiometric.setCredentials({
  username: 'user@example.com',
  password: 'secret',
  server: 'api.myapp.com',
});

// Retrieve with biometric verification
const credentials = await NativeBiometric.getCredentials({
  server: 'api.myapp.com',
});
INCORRECT - Plain storage:
typescript
import { Preferences } from '@capacitor/preferences';

// NEVER store sensitive data in plain preferences
await Preferences.set({
  key: 'password',
  value: 'secret',  // Stored in plain text!
});
正确示例 - 使用安全存储保存敏感数据:
typescript
import { NativeBiometric } from '@capgo/capacitor-native-biometric';

// Store credentials securely
await NativeBiometric.setCredentials({
  username: 'user@example.com',
  password: 'secret',
  server: 'api.myapp.com',
});

// Retrieve with biometric verification
const credentials = await NativeBiometric.getCredentials({
  server: 'api.myapp.com',
});
错误示例 - 明文存储:
typescript
import { Preferences } from '@capacitor/preferences';

// NEVER store sensitive data in plain preferences
await Preferences.set({
  key: 'password',
  value: 'secret',  // Stored in plain text!
});

CRITICAL: Certificate Pinning

重要提示:证书固定

For production apps handling sensitive data:
typescript
// capacitor.config.ts
const config: CapacitorConfig = {
  plugins: {
    CapacitorHttp: {
      enabled: true,
    },
  },
  server: {
    // Disable cleartext in production
    cleartext: false,
  },
};
对于处理敏感数据的生产应用:
typescript
// capacitor.config.ts
const config: CapacitorConfig = {
  plugins: {
    CapacitorHttp: {
      enabled: true,
    },
  },
  server: {
    // Disable cleartext in production
    cleartext: false,
  },
};

HIGH: Root/Jailbreak Detection

高优先级:Root/越狱检测

typescript
import { IsRoot } from '@capgo/capacitor-is-root';

async function checkDeviceSecurity() {
  const { isRooted } = await IsRoot.isRooted();

  if (isRooted) {
    // Show warning or restrict functionality
    showSecurityWarning('Device appears to be rooted/jailbroken');
  }
}
typescript
import { IsRoot } from '@capgo/capacitor-is-root';

async function checkDeviceSecurity() {
  const { isRooted } = await IsRoot.isRooted();

  if (isRooted) {
    // Show warning or restrict functionality
    showSecurityWarning('Device appears to be rooted/jailbroken');
  }
}

HIGH: App Tracking Transparency (iOS)

高优先级:应用跟踪透明度(iOS)

typescript
import { AppTrackingTransparency } from '@capgo/capacitor-app-tracking-transparency';

async function requestTracking() {
  const { status } = await AppTrackingTransparency.requestPermission();

  if (status === 'authorized') {
    // Enable analytics
  }
}
typescript
import { AppTrackingTransparency } from '@capgo/capacitor-app-tracking-transparency';

async function requestTracking() {
  const { status } = await AppTrackingTransparency.requestPermission();

  if (status === 'authorized') {
    // Enable analytics
  }
}

Error Handling

错误处理

CRITICAL: Always Handle Plugin Errors

重要提示:始终处理插件错误

CORRECT:
typescript
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') {
      // User cancelled, not an error
      return null;
    }
    if (error.message.includes('permission')) {
      // Permission denied
      showPermissionDialog();
      return null;
    }
    // Unexpected error
    console.error('Camera error:', error);
    throw error;
  }
}
INCORRECT:
typescript
// No error handling
const image = await Camera.getPhoto({ quality: 90 });
正确示例:
typescript
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') {
      // User cancelled, not an error
      return null;
    }
    if (error.message.includes('permission')) {
      // Permission denied
      showPermissionDialog();
      return null;
    }
    // Unexpected error
    console.error('Camera error:', error);
    throw error;
  }
}
错误示例:
typescript
// No error handling
const image = await Camera.getPhoto({ quality: 90 });

Live Updates

实时更新

Using Capacitor Updater

使用Capacitor Updater

typescript
import { CapacitorUpdater } from '@capgo/capacitor-updater';

// Notify when app is ready
CapacitorUpdater.notifyAppReady();

// Listen for updates
CapacitorUpdater.addListener('updateAvailable', async (update) => {
  // Download in background
  const bundle = await CapacitorUpdater.download({
    url: update.url,
    version: update.version,
  });

  // Apply on next app start
  await CapacitorUpdater.set(bundle);
});
typescript
import { CapacitorUpdater } from '@capgo/capacitor-updater';

// Notify when app is ready
CapacitorUpdater.notifyAppReady();

// Listen for updates
CapacitorUpdater.addListener('updateAvailable', async (update) => {
  // Download in background
  const bundle = await CapacitorUpdater.download({
    url: update.url,
    version: update.version,
  });

  // Apply on next app start
  await CapacitorUpdater.set(bundle);
});

Update Strategy

更新策略

CORRECT - Background download, apply on restart:
typescript
// Download silently
const bundle = await CapacitorUpdater.download({ url, version });

// User continues using app...

// Apply when they close/reopen
await CapacitorUpdater.set(bundle);
INCORRECT - Interrupt user:
typescript
// Don't force reload while user is active
const bundle = await CapacitorUpdater.download({ url, version });
await CapacitorUpdater.reload();  // Disrupts user
正确示例 - 后台下载,重启时应用:
typescript
// Download silently
const bundle = await CapacitorUpdater.download({ url, version });

// User continues using app...

// Apply when they close/reopen
await CapacitorUpdater.set(bundle);
错误示例 - 干扰用户操作:
typescript
// Don't force reload while user is active
const bundle = await CapacitorUpdater.download({ url, version });
await CapacitorUpdater.reload();  // Disrupts user

Native Project Management

原生项目管理

iOS: Use Swift Package Manager (SPM)

iOS: 使用Swift Package Manager (SPM)

Modern approach - prefer SPM over CocoaPods:
ruby
undefined
现代方案 - 优先使用SPM而非CocoaPods:
ruby
undefined

Podfile - Remove plugin pods, use SPM instead

Podfile - Remove plugin pods, use SPM instead

target 'App' do capacitor_pods

Plugin dependencies via SPM in Xcode

end
undefined
target 'App' do capacitor_pods

Plugin dependencies via SPM in Xcode

end
undefined

Android: Gradle Configuration

Android: Gradle配置

groovy
// android/app/build.gradle
android {
    defaultConfig {
        minSdkVersion 22
        targetSdkVersion 34
    }

    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
groovy
// 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

插件模拟

typescript
// Mock for web testing
jest.mock('@capgo/capacitor-native-biometric', () => ({
  NativeBiometric: {
    isAvailable: jest.fn().mockResolvedValue({
      isAvailable: true,
      biometryType: 'touchId',
    }),
    verifyIdentity: jest.fn().mockResolvedValue({}),
  },
}));
typescript
// Mock for web testing
jest.mock('@capgo/capacitor-native-biometric', () => ({
  NativeBiometric: {
    isAvailable: jest.fn().mockResolvedValue({
      isAvailable: true,
      biometryType: 'touchId',
    }),
    verifyIdentity: jest.fn().mockResolvedValue({}),
  },
}));

Platform Detection

平台检测

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

if (Capacitor.isNativePlatform()) {
  // Native-specific code
} else {
  // Web fallback
}

// Or check specific platform
if (Capacitor.getPlatform() === 'ios') {
  // iOS-specific code
}
typescript
import { Capacitor } from '@capacitor/core';

if (Capacitor.isNativePlatform()) {
  // Native-specific code
} else {
  // Web fallback
}

// Or check specific platform
if (Capacitor.getPlatform() === 'ios') {
  // iOS-specific code
}

Deployment Checklist

部署检查清单

  • Remove development server URLs from config
  • Enable ProGuard for Android release builds
  • Set appropriate iOS deployment target
  • Test on real devices, not just simulators
  • Verify all permissions are declared
  • Test with poor network conditions
  • Verify deep links work correctly
  • Test app backgrounding/foregrounding
  • Verify push notifications work
  • Test biometric authentication edge cases
  • 从配置中移除开发服务器URL
  • 为Android发布版本启用ProGuard
  • 设置合适的iOS部署目标版本
  • 在真实设备上测试,而非仅依赖模拟器
  • 验证所有权限已正确声明
  • 在弱网络条件下测试
  • 验证深度链接功能正常
  • 测试应用前后台切换
  • 验证推送通知功能正常
  • 测试生物识别认证的边缘情况

Resources

参考资源