metadata.json
{
"version": "1.1.0",
"organization": "Capgo",
"date": "October 2026",
"abstract": "Review and setup guide for Capacitor 8.5/9 apps: capacitor.config hygiene and env-specific builds, version alignment across @capacitor/* and plugin majors, sync and generated-asset traps, plugin availability, permission and error-code patterns, iOS SPM and UIScene lifecycle, Android variables.gradle and R8, live update readiness, and a release checklist that routes deep topics to sibling skills.",
"triggers": [
"capacitor best practices",
"review capacitor app",
"capacitor code review",
"capacitor.config.ts review",
"capacitor project setup",
"plugin is not implemented on ios",
"Could not find the web assets directory",
"platform has not been added yet",
"server.url in production",
"cap sync",
"cap doctor",
"capacitor version mismatch",
"capacitor release checklist",
"capacitor spm or cocoapods",
"capacitor uiscene appdelegate"
],
"references": [
"https://capacitorjs.com/docs",
"https://capacitorjs.com/docs/config",
"https://capacitorjs.com/docs/guides/environment-specific-configurations",
"https://capacitorjs.com/docs/guides/mocking-plugins",
"https://capacitorjs.com/docs/ios/spm",
"https://capacitorjs.com/docs/updating/8-5",
"https://capgo.app/docs/"
]
}
references/config-and-project.md
# Config and Project Layout
## Layout
```text
my-app/
├── src/ # web source (edit here)
├── dist/ # web build output = webDir (gitignored)
├── android/ # native project: commit it
│ └── app/src/main/assets/public/ # generated by cap copy: never edit
├── ios/ # native project: commit it
│ └── App/App/public/ # generated by cap copy: never edit
├── capacitor.config.ts
└── package.json
```
`.gitignore` essentials (the Capacitor templates already include most of these inside `android/` and `ios/`):
```gitignore
node_modules/
dist/
ios/App/Pods/
ios/App/App/public/
android/app/src/main/assets/public/
android/.gradle/
android/app/build/
android/build/
*.jks
*.keystore
.env*
.capgo_key_v2
```
## capacitor.config.ts
Prefer TypeScript config: it is type-checked against `CapacitorConfig` and can read env vars at `cap sync`/`copy` time.
```typescript
import type { CapacitorConfig } from '@capacitor/cli';
const devUrl = process.env.CAP_SERVER_URL; // only set in a dev shell
const config: CapacitorConfig = {
appId: 'com.company.app', // reverse-domain; changing it after release = new app
appName: 'My App',
webDir: 'dist', // must match the bundler output
...(devUrl && { server: { url: devUrl, cleartext: devUrl.startsWith('http:') } }),
plugins: {
SplashScreen: { launchAutoHide: false }, // hide manually once the app renders
},
};
export default config;
```
Traps:
- The config is evaluated when you run `cap sync`/`copy`, not at app runtime. Env vars present in your shell at sync time get baked into `capacitor.config.json` inside the native projects. Sync release builds from a clean shell or CI.
- `process.env.NODE_ENV === 'development'` is usually not set in the shell running the CLI. Use an explicit variable.
- `webContentsDebuggingEnabled`, `loggingBehavior` and `server.*` defaults are already safe for release. Leave them unset unless you have a reason.
- `capacitor.config.ts` wins over `.js`, which wins over `.json`. A stale `capacitor.config.json` next to a `.ts` is silently ignored: delete it.
- Capacitor 9 removes `cap run -l/--host/--port/--https`; use `npx cap run <platform> --url http://<ip>:<port>`.
## Environment-specific builds
For QA/staging/prod variants, follow the official guide: Xcode schemes/targets and Android product flavors, plus a per-environment Capacitor config selected by an env var at `cap copy` time (`NODE_ENV=qa npx cap copy`). https://capacitorjs.com/docs/guides/environment-specific-configurations
Keep different `appId`s per environment if testers install them side by side.
## Version alignment
```bash
npm ls @capacitor/core @capacitor/cli @capacitor/ios @capacitor/android
npx cap doctor
```
- Upgrade core packages together:
```bash
npm install @capacitor/core@latest @capacitor/ios@latest @capacitor/android@latest
npm install -D @capacitor/cli@latest
npx cap sync
```
`@latest` stays within the current stable major (8.x today). Major jumps go through `capacitor-app-upgrades`.
- `@capacitor/cli` belongs in `devDependencies`.
- Plugins: match the plugin major to the Capacitor major. Upgrading only plugins across a major without Capacitor (or the reverse) is the most common source of native build errors.
- Capacitor 9 is on the `next` tag (alpha). Only install with `@next` in a branch for evaluation.
references/native-projects.md
# Native Projects
## iOS
### Dependency manager
| Signal | Setup |
|--------|-------|
| `ios/App/CapApp-SPM/Package.swift`, no `Podfile` | Swift Package Manager (default for new projects since Capacitor 8) |
| `ios/App/Podfile`, `App.xcworkspace` | CocoaPods |
CocoaPods Trunk is expected to become read-only on December 2, 2026. New pod versions will stop publishing, so recommend migrating with `cocoapods-to-spm`. For a new platform add:
```bash
npx cap add ios # SPM by default
npx cap add ios --packagemanager CocoaPods # only if a required plugin lacks SPM support
```
With SPM, plugin packages are managed by `npx cap sync` through `CapApp-SPM/Package.swift`. Do not add Capacitor plugins by hand in Xcode's package UI; sync will not track them.
### Scene lifecycle (Capacitor 8.5+)
Capacitor 8.5 adopts `UIScene`; Xcode 27 requires it. Adopted projects have `SceneDelegate.swift`, `UIApplicationSceneManifest` in `Info.plist`, and `application(_:configurationForConnecting:options:)` in `AppDelegate`.
Review traps:
- Custom code in `AppDelegate` `application(_:open:options:)`, `application(_:continue:restorationHandler:)`, `applicationDidBecomeActive`, `applicationWillResignActive`, etc. no longer runs after adoption. Move it to the scene delegate equivalents.
- JS `resume`/`pause` events and `App.getLaunchUrl()` keep working through Capacitor's scene proxy.
- Avoid `UIScreen.main`, `UIApplication.shared.windows`, `interfaceOrientation`, and `userInterfaceIdiom`-based layout in native code; resizable windows and foldable iPhones break those assumptions. Use the view's `window.windowScene`, trait collections, and safe areas.
Full procedure: `capacitor-uiscene-migration`.
### Deployment target
- Capacitor 8: iOS 15.0 minimum. Capacitor 9: iOS 16.0.
- Gate newer APIs in native code with `if #available(iOS 17, *)`.
- Capacitor 9 with Swift 6 rejects `@UIApplicationMain`; use `@main` on `AppDelegate`.
## Android
### `android/variables.gradle`
Capacitor 8 template values:
```groovy
ext {
minSdkVersion = 24
compileSdkVersion = 36
targetSdkVersion = 36
// androidx* versions...
}
```
Capacitor 9: `minSdkVersion = 26`, compile/target 37, AGP 9.2.1, Gradle 9.5.1. Do not bump SDK levels independently of the Capacitor major: plugin AARs are built against the matching values. Google Play enforces a minimum `targetSdkVersion` for updates; check current Play policy before release.
### Release build type
The template ships `minifyEnabled false`. Enabling R8 shrinks native code; test every plugin afterwards.
```groovy
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
```
Use `proguard-android-optimize.txt`: AGP 9 (Capacitor 9) removed `proguard-android.txt`, so older templates fail to build after upgrading.
### Capacitor 9 Gradle changes to expect
- Kotlin is built into AGP 9: remove `kotlin-android` plugin and explicit stdlib.
- `jcenter()` is removed: use `google()` and `mavenCentral()`.
- `androidx.core:core-ktx` is folded into `core` 1.19.0.
- The Cordova Gradle modules are only included when a Cordova plugin is installed.
Run these through `capacitor-app-upgrade-v8-to-v9`, not ad hoc.
### Signing
Never commit keystores or passwords. Keep them in CI secrets or `~/.gradle/gradle.properties`. Pipeline setup: `capacitor-ci-cd`; cloud builds: `capgo-native-builds`.
## Both platforms
- Commit `ios/` and `android/`; treat changes there like code review.
- After `npm install` of anything native, run `npx cap sync`, then build from Xcode/Android Studio or `npx cap run`.
- Keep native customizations minimal and documented; every one is a merge risk during major upgrades.
references/plugins.md
# Plugin Usage Patterns
Finding the right plugin for a feature: `capacitor-plugins`. This file covers how to use any plugin correctly.
## Install
```bash
npm install @capgo/capacitor-native-biometric
npx cap sync
```
- `npx cap sync` copies web assets, updates native dependencies (SPM `CapApp-SPM/Package.swift` or `pod install` for CocoaPods) and regenerates plugin registration. You do not need to run `pod install` by hand after sync.
- Rebuild the native app after sync. A running app does not pick up new native code from live reload.
- `npx cap ls` shows what the CLI detected. A plugin missing there was not installed in the project the CLI reads.
- Follow the plugin README for required Info.plist keys, Android manifest entries, or Gradle config. Missing `NS*UsageDescription` keys crash iOS on first permission request.
- Plugins with Cordova code pull in the Cordova compatibility layer. On Capacitor 9 that layer is only added when a Cordova plugin is installed.
## Feature detection
```typescript
import { Capacitor } from '@capacitor/core';
if (Capacitor.isNativePlatform()) { /* ios or android */ }
const platform = Capacitor.getPlatform(); // 'ios' | 'android' | 'web'
if (Capacitor.isPluginAvailable('NativeBiometric')) { /* safe to call */ }
```
Device-level capability is a separate check, done with the plugin's own API:
```typescript
import { NativeBiometric } from '@capgo/capacitor-native-biometric';
const { isAvailable } = await NativeBiometric.isAvailable();
if (!isAvailable) return loginWithPassword();
```
## Permissions
Official plugins that need runtime permissions expose `checkPermissions()` and `requestPermissions()`. Third-party plugins vary; read their API.
```typescript
import { Camera } from '@capacitor/camera';
let status = await Camera.checkPermissions();
if (status.camera === 'prompt' || status.camera === 'prompt-with-rationale') {
status = await Camera.requestPermissions({ permissions: ['camera'] });
}
if (status.camera === 'denied') {
showGoToSettingsHelp(); // the OS will not show the prompt again
return;
}
```
Request at the moment of use with context, not all at launch (App Review flags launch-time prompts without context).
## Errors
Branch on `code`. Messages are human text and differ by platform/version.
```typescript
import { Camera } from '@capacitor/camera';
try {
return await Camera.takePhoto({ /* options */ });
} catch (e: any) {
switch (e?.code) {
case 'OS-PLUG-CAMR-0006': return null; // user cancelled
case 'OS-PLUG-CAMR-0003': showPermissionHelp(); return null; // no camera access
case 'UNIMPLEMENTED': return pickFromFileInput(); // not on this platform
default: throw e;
}
}
```
The `OS-PLUG-CAMR-*` codes apply to `@capacitor/camera` 8.1+ new APIs (`takePhoto`, `chooseFromGallery`, ...). Check each plugin's docs for its codes. Capacitor core exceptions use `UNIMPLEMENTED` and `UNAVAILABLE`.
## Bridge payloads
- Each call serializes arguments to JSON across the bridge. Batch related writes into one call instead of many small ones.
- Do not ship megabytes of base64 across the bridge (photos, PDFs). Use plugin options that return file paths/URIs, then display with `Capacitor.convertFileSrc(path)` or a `webPath` the plugin returns.
- Remove listeners: `const handle = await Plugin.addListener(...)`, then `await handle.remove()` on unmount.
## Lazy loading
Capacitor plugin JS entry points are thin proxies created by `registerPlugin`; importing them at startup costs little. Lazy-load with `await import(...)` when the plugin ships a heavy web implementation or pulls large dependencies, and for your own heavy feature modules. Measure with `capacitor-performance` before optimizing.
## Mocking in tests
Unit tests run in Node/jsdom where native plugins do not exist. Mock the plugin module (Jest `__mocks__/@capacitor/...` or `vi.mock`). Official guide: https://capacitorjs.com/docs/guides/mocking-plugins. Full test strategy: `capacitor-testing`.
references/release-checklist.md
# Release Checklist
Run top to bottom before a store build. Each item names the skill that owns the deep check.
## Config
- [ ] No `server.url`, `server.cleartext`, dev `allowNavigation`, or `webContentsDebuggingEnabled: true` in the synced `capacitor.config.json` (both platforms).
- [ ] `appId`, `appName`, and `webDir` correct; the web build is fresh (`npm run build && npx cap sync` from a clean shell or CI).
- [ ] Only one of `capacitor.config.ts|js|json` exists.
## Versions
- [ ] `@capacitor/core`, `cli`, `ios`, `android` on the same version; plugin majors match (`npx cap doctor`).
- [ ] No Capacitor 9 `next` packages in a production release unless the team has accepted alpha risk.
## Security (`capacitor-security`)
- [ ] `npx @capgo/capgo-sec@latest scan --ci` passes or findings are triaged.
- [ ] No secrets in the built bundle; tokens stored in Keychain/Keystore, not Preferences/localStorage.
- [ ] No `NSAllowsArbitraryLoads`, no `usesCleartextTraffic="true"`; `allowBackup` reviewed.
## iOS
- [ ] UIScene lifecycle adopted (required for Xcode 27 builds) and custom AppDelegate code moved (`capacitor-uiscene-migration`).
- [ ] Every permission the plugins use has an `NS*UsageDescription` string.
- [ ] Privacy manifest (`PrivacyInfo.xcprivacy`) present and accurate (`capacitor-apple-review-preflight`).
- [ ] SPM in use or migration planned before CocoaPods Trunk goes read-only (`cocoapods-to-spm`).
- [ ] Version (`MARKETING_VERSION`) and build number (`CURRENT_PROJECT_VERSION`) bumped.
## Android
- [ ] `targetSdkVersion` meets current Google Play policy; matches the Capacitor major's template.
- [ ] Release build signed with the upload key; keystore not in git.
- [ ] R8 decision made; if enabled, all plugin features tested on a release build.
- [ ] `versionCode` incremented, `versionName` set.
## Behavior on real devices
- [ ] Each plugin feature works, including permission denial and "denied forever" paths.
- [ ] Offline and poor network handled (`capacitor-offline-first`).
- [ ] Background/foreground (`resume`/`pause`) and cold start from a deep link or notification (`capacitor-deep-linking`, `capacitor-push-notifications`).
- [ ] Safe areas, keyboard, splash screen behave (`safe-area-handling`, `capacitor-keyboard`, `capacitor-splash-screen`).
- [ ] Crash reporting wired (`capacitor-native-observability`).
## Live updates (`capgo-live-updates`)
- [ ] `CapacitorUpdater.notifyAppReady()` called on every launch so failed bundles roll back.
- [ ] Channel for this native build set; bundles encrypted if the content is sensitive.
- [ ] Native-only changes (new plugins, permissions, SDK bumps) shipped through the store, not OTA.
## Submission
- [ ] Store metadata, screenshots, and review notes (`capacitor-app-store`, `capacitor-apple-review-preflight`).
SKILL.md
---
name: capacitor-best-practices
description: Review and set up Capacitor apps against current best practices (Capacitor 8.5 stable, 9 alpha via `@next`). Use for a Capacitor codebase review, `capacitor.config.ts` audit, new project setup, aligning `@capacitor/core`/`cli`/`ios`/`android` and plugin majors, plugin patterns (availability checks, `checkPermissions`/`requestPermissions`, error `code` handling, web fallbacks), or release prep; or on `"<Plugin>" plugin is not implemented on ios`, `Could not find the web assets directory`, `platform has not been added yet`, a dev `server.url` shipped to production, lost edits in `android/app/src/main/assets/public`, SPM vs CocoaPods, or AppDelegate/UIScene drift. Do not use for security audits (capacitor-security), performance (capacitor-performance), tests (capacitor-testing), crashes (debugging-capacitor), major upgrades (capacitor-app-upgrades), SPM migration (cocoapods-to-spm), UIScene adoption (capacitor-uiscene-migration), or live updates (capgo-live-updates).
---
# Capacitor Best Practices
Review a Capacitor app for structural mistakes that cause broken builds, lost edits, production leaks, and plugin crashes. Route deep topics to the sibling skill that owns them.
## When to Use
TRIGGER when:
- The user asks for a general code/architecture review of a Capacitor or Ionic + Capacitor app.
- Setting up a new Capacitor project or adding iOS/Android to an existing web app.
- `capacitor.config.ts|json` review: `webDir`, `server.url`, `cleartext`, plugin config, env-specific config.
- Version drift between `@capacitor/core`, `@capacitor/cli`, `@capacitor/ios`, `@capacitor/android`, or plugin majors that do not match Capacitor's major.
- Plugin usage review: missing `npx cap sync`, no availability check, no permission flow, matching on error message strings.
- Errors such as `"<Plugin>" plugin is not implemented on ios`, `Could not find the web assets directory`, `ios platform has not been added yet`.
- Pre-release sanity checklist across config, native projects, and live updates.
Do not use:
- Security audit, secrets, storage, pinning, CSP -> `capacitor-security`; Xcode hardening -> `capacitor-ios-security-hardening`.
- Bundle size, startup time, memory, bridge profiling -> `capacitor-performance`.
- Unit/E2E test setup and plugin mocking -> `capacitor-testing`.
- Crashes, white screen, native logs -> `debugging-capacitor`, `ios-android-logs`.
- Major upgrades -> `capacitor-app-upgrades` (8 -> 9: `capacitor-app-upgrade-v8-to-v9`).
- CocoaPods -> SPM -> `cocoapods-to-spm`; UIScene adoption -> `capacitor-uiscene-migration`.
- Finding or installing a specific plugin -> `capacitor-plugins`.
- Store submission and review -> `capacitor-app-store`, `capacitor-apple-review-preflight`; CI -> `capacitor-ci-cd`; live updates -> `capgo-live-updates`.
## Current baseline (October 2026)
| | Capacitor 8 (stable, `latest` 8.5.x) | Capacitor 9 (`next`, alpha) |
|---|---|---|
| Node | 22+ | 24+ |
| iOS min / Xcode | iOS 15 / Xcode 26 (8.5 adopts UIScene so Xcode 27 builds work) | iOS 16 / Xcode 27+ |
| Android | minSdk 24, compile/target 36, AGP 8.13, Gradle 8.14.3 | minSdk 26, compile/target 37, AGP 9.2.1, Gradle 9.5.1 |
| iOS deps | SPM default for new projects | SPM default |
CocoaPods Trunk is expected to go read-only on December 2, 2026. Recommend SPM for every app still on CocoaPods. Install Capacitor 9 only with `@next` until it reaches GA, and do not recommend it for production apps.
## Review workflow
1. **Inventory (read-only).**
```bash
npx cap doctor # installed vs latest Capacitor packages
npx cap ls # plugins Capacitor detects per platform
cat capacitor.config.ts 2>/dev/null || cat capacitor.config.json
ls ios/App/Podfile ios/App/CapApp-SPM 2>/dev/null # CocoaPods or SPM
grep -nE "minSdkVersion|compileSdkVersion|targetSdkVersion" android/variables.gradle
```
2. **For large codebases, write a TODO list** of areas (config, versions, plugins, native iOS, native Android, live updates, release) and review one at a time.
3. **Report findings before editing**, grouped by severity, each with file:line and the fix. Ask before invasive changes (upgrading a major, migrating to SPM, enabling R8, changing `appId`).
4. **Fix**, loading the matching reference.
5. **Run Verification.**
## Rules that matter most
1. **Keep Capacitor packages on one version.** `@capacitor/core`, `cli`, `ios`, `android` must match. Plugin majors must match the Capacitor major (official and `@capgo/*` plugins follow it: v8 plugins for Capacitor 8).
2. **`npx cap sync` after every plugin install/removal and every config change.** Missing sync produces `"<Plugin>" plugin is not implemented on <platform>`.
3. **Never edit generated web assets.** `android/app/src/main/assets/public/` and `ios/App/App/public/` are overwritten by `cap copy`/`sync`. Edit `src/` and rebuild.
4. **Commit `ios/` and `android/`.** They are source code with your native customizations, not build output. Do commit `capacitor.config.*`; do not commit `node_modules`, `Pods/`, build dirs.
5. **No dev settings in release config.** `server.url`, `server.cleartext`, `allowNavigation` to dev hosts, `webContentsDebuggingEnabled: true`. Gate them on an env var (see references/config-and-project.md). On Capacitor 9, use `npx cap run <platform> --url <dev-url>` for live reload.
6. **Feature-detect before calling plugins.** `Capacitor.isNativePlatform()`, `Capacitor.getPlatform()`, `Capacitor.isPluginAvailable('Name')`. Provide a web fallback or hide the feature.
7. **Use the permission API.** Call `checkPermissions()` then `requestPermissions()` for official plugins that expose them, and handle `denied` with a path to Settings. Add the matching `NS*UsageDescription` keys / Android permissions.
8. **Branch on `error.code`, not `error.message`.** Messages change between versions and platforms. Official plugins expose structured codes (for example `OS-PLUG-CAMR-0006` for a cancelled camera capture in `@capacitor/camera` 8.1+ new APIs); Capacitor core uses `UNIMPLEMENTED` / `UNAVAILABLE`.
9. **Move large binary data as files, not base64.** Pass file paths/URIs across the bridge and render with `Capacitor.convertFileSrc()`.
10. **iOS lifecycle is scene-based from Capacitor 8.5.** After adopting UIScene, AppDelegate `application(_:open:options:)`, `continue userActivity`, and foreground/background callbacks stop firing. Move custom native code to `SceneDelegate` (`capacitor-uiscene-migration`). Avoid `UIScreen.main` and idiom-based layout for resizable windows.
11. **Live updates: call `CapacitorUpdater.notifyAppReady()` on every launch**, and apply manual updates with `next()` (on background/relaunch) rather than `set()` mid-session. Details in `capgo-live-updates`.
12. **Secrets never go in the app.** Anything in the bundle or `capacitor.config` is extractable. Hand off to `capacitor-security` for anything beyond this rule.
## References
Only load a reference when its topic is in play.
| Reference | Load when |
|-----------|-----------|
| [references/config-and-project.md](references/config-and-project.md) | Reviewing `capacitor.config`, env-specific builds, repo layout, `.gitignore`, version alignment |
| [references/plugins.md](references/plugins.md) | Plugin install/sync, availability checks, permissions, error codes, web fallbacks, lazy loading, mocking |
| [references/native-projects.md](references/native-projects.md) | iOS SPM vs CocoaPods, UIScene, deployment targets; Android `variables.gradle`, R8, Capacitor 9 Gradle changes |
| [references/release-checklist.md](references/release-checklist.md) | Preparing a release build or reviewing release readiness |
## Verification
```bash
# Versions aligned (all @capacitor/* core packages show the same version)
npm ls @capacitor/core @capacitor/cli @capacitor/ios @capacitor/android
npx cap doctor
# Web build exists where webDir points, then sync succeeds without errors
npm run build && npx cap sync
# No dev server or debug flags in the synced native config
grep -nE "\"url\"|cleartext|webContentsDebuggingEnabled" \
android/app/src/main/assets/capacitor.config.json ios/App/App/capacitor.config.json
# No hand edits in generated web assets (should show nothing)
git status --porcelain android/app/src/main/assets/public ios/App/App/public
# Error handling does not depend on message text
grep -rnE "error\.message\s*(===|==|\.includes)" src/
# Native builds
cd android && ./gradlew assembleRelease && cd ..
xcodebuild -project ios/App/App.xcodeproj -scheme App -configuration Release -destination 'generic/platform=iOS' build CODE_SIGNING_ALLOWED=NO
```
With CocoaPods, build `ios/App/App.xcworkspace` with `-workspace` instead of `-project`. Finally run on a real device and exercise each plugin-backed feature, including permission denial.
## Error handling
| Error | Cause | Fix |
|-------|-------|-----|
| `"<Plugin>" plugin is not implemented on ios` (or `android`) | Plugin installed but not synced, plugin has no implementation for that platform, or plugin major does not match Capacitor | `npx cap sync`; check `npx cap ls`; install the plugin version matching your Capacitor major; rebuild the native app |
| `"<Plugin>.<method>()" is not implemented on web` | Calling a native-only method in the browser | Guard with `Capacitor.isNativePlatform()` or `isPluginAvailable`, provide a web fallback |
| `Could not find the web assets directory: ./dist.` | `webDir` wrong or web app not built | Build first; set `webDir` to the real output folder (`dist`, `www`, `build`, `out`) |
| `ios platform has not been added yet.` | Native project missing | `npx cap add ios` (SPM default) or restore the committed `ios/` folder |
| `Invalid App ID "<id>"` | `appId` not reverse-domain or contains invalid characters | Use `com.company.app` style; changing it on a published app creates a new store listing |
| App loads a LAN IP or blank screen in production | `server.url` shipped from a dev config | Remove it, rebuild, `npx cap sync`, rebuild the native app |
| `net::ERR_CLEARTEXT_NOT_PERMITTED` | HTTP request on Android | Use HTTPS; `cleartext` only in dev config (see `capacitor-security`) |
| `pod install` fails / CocoaPods spec errors | CocoaPods trunk deprecation, outdated specs | Migrate with `cocoapods-to-spm` |
| Deep links or `resume` handling stopped on iOS after 8.5 | Custom code still in AppDelegate after UIScene adoption | Move it to `SceneDelegate` (`capacitor-uiscene-migration`) |
## Resources
- Capacitor docs: https://capacitorjs.com/docs
- Config reference: https://capacitorjs.com/docs/config
- Environment-specific configurations: https://capacitorjs.com/docs/guides/environment-specific-configurations
- Updating to 8.5 (UIScene): https://capacitorjs.com/docs/updating/8-5
- Capgo docs: https://capgo.app/docs/