Capacitor is the logical choice when you already have a solid web app (Angular, React, Vue) and want to deploy it to iOS and Android without rewriting in native code. Unlike Ionic, which builds on top of it, Capacitor works as a bridge layer: your code stays web-based, but you access native APIs through plugins. Deployment isn't trivial—you must manage certificates, provisioning profiles, build configurations—but once the workflow clicks, it's repeatable and maintainable. The key insight is that Capacitor *automates* the bridge-building, not the entire native toolchain. You still need Xcode and Android Studio for signing, configuration, and final packaging.
How Capacitor Actually Works
Capacitor embeds your web app in a WebView (UIWebView on iOS, WebView on Android). Capacitor plugins (Camera, Geolocation, Storage, etc.) create a JavaScript-to-Native bridge: you call Capacitor.Plugins.Camera.getPhoto() , it triggers native code, and the result returns to JavaScript. For deployment, Capacitor generates Xcode (iOS) and Android Studio (Android) projects that you then configure and compile. This is crucial: Capacitor does *not* replace Xcode or Android Studio—it partially automates them. You still must touch these tools for signing, provisioning profiles, and platform-specific configuration. Think of it as a scaffold, not a replacement.
Preparing Your Web App for Mobile Production
Before touching Capacitor, your web app must be solid. This means: responsive design working from 375px screens (iPhone SE) to 1024px (iPad), correct viewport metadata ( <meta name="viewport" content="width=device-width, initial-scale=1.0"> ), and zero console errors. Test on Safari DevTools (iOS) and Chrome DevTools (Android) with realistic network and CPU throttling. Capacitor inherits your web app's performance, so if you have scroll jank or TTI > 3s, deployment won't fix it. Enable minification and gzip compression in your bundler (Webpack, Vite). Also verify permissions: on iOS, camera/location access requires Info.plist entries; on Android, it's AndroidManifest.xml and runtime permissions. Capacitor generates templates, but you must fill them in manually. Don't skip this step—a sloppy web app becomes a sloppy native app.
Installation and Initialization
Install Capacitor in your existing project: npm install @capacitor/core @capacitor/cli . Then npx cap init creates the configuration files ( capacitor.config.ts ). This file is critical: it defines the appId (e.g., com.mycompany.myapp ), app name, web build directory ( webDir: 'dist' ), and used plugins. Minimal example: { appId: 'com.example.myapp', appName: 'MyApp', webDir: 'dist', plugins: { SplashScreen: { autoHide: false } } } . Then npx cap add ios and npx cap add android generate native projects. Heads up: this creates /ios and /android directories you *must* commit to git (unlike node_modules ). These contain Xcode and Gradle code that you'll maintain and version.
Building and Syncing
Before each deploy, build your web app: npm run build . Capacitor watches the webDir (e.g., dist/ ) for HTML/JS/CSS files. Then npx cap sync copies web files into native projects and updates plugins. This step is easy to forget: if you change Angular code, you must npm run build && npx cap sync , or you'll test the old version. For local development, npx cap open ios opens Xcode and npx cap open android opens Android Studio. You can then launch an emulator/device and compile from the native IDE. Some devs also set up npx cap run ios --live-reload , which reloads the web app on each file change—great for iteration, but disable it in production.
Signing and Provisioning on iOS
This is where many devs hit a wall. On iOS, you need: an Apple Developer account ($99/year), a code-signing certificate, and a provisioning profile. In Xcode, open the Capacitor project, go to Signing & Capabilities, select your team, and Xcode auto-generates certificates and profiles if you're logged in. For TestFlight (beta), build in Release mode ( Product > Build or Cmd+B ), then use Product > Archive to create an archive. Validate and upload to App Store Connect. For production, it's the same but with additional review. A common trap: Bundle IDs must match between capacitor.config.ts and Xcode. If one says com.example.myapp and the other says com.example.myapp2 , the build explodes. Also verify entitlements (e.g., Push Notifications) are enabled in Xcode and listed in capacitor.config.ts .
Signing and Android Deployment
Android is simpler in theory. Generate a signing key with keytool -genkey -v -keystore my-release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias my-key-alias . Store this file securely (ideally not in git). Then configure Gradle in /android/app/build.gradle to sign with this key. In Android Studio, go to Build > Generate Signed Bundle / APK , select the key, and generate an AAB (Android App Bundle) for Google Play or an APK for testing. AAB is the modern format: Google Play compiles it into device-optimized APKs. Watch out for versionCode and versionName in build.gradle —they must increment with each release, or Play Store rejects the upload. Also ensure your applicationId in build.gradle matches your Capacitor appId.
A Common Pitfall: Plugin Dependencies and Native Compatibility
Capacitor makes native APIs accessible, but each plugin brings dependencies. For example, @capacitor/camera depends on androidx on Android and specific frameworks on iOS. Install a poorly maintained third-party plugin, and it might bring an incompatible dependency that breaks your build. Always check Capacitor version / plugin version compatibility before installing. Also, some features (e.g., biometrics) require platform-specific configuration. Ignoring this leads to runtime crashes on real devices, even though the emulator works fine. Always test on a real device or simulator with production-like configuration.
Conclusion and Practical Workflow
The complete workflow: (1) develop and test your web app locally, (2) npm run build , (3) npx cap sync , (4) test in the emulator with npx cap run ios/android , (5) build and sign via Xcode/Android Studio, (6) upload to TestFlight/Play Console, (7) iterate on feedback. Automate as much as possible: use GitHub Actions or GitLab CI to build and sign (with secrets for keys). Document your capacitor.config.ts , iOS entitlements, and Gradle configurations. Capacitor isn't magic—it demands rigor in certificate and version management—but it's infinitely faster than writing native code. Once this workflow is solid, deploying to iOS and Android becomes nearly as straightforward as deploying to the web.