All Articles
Reading Time
12 min read
Published
2026-08-06
Word Count
2,940words

Grab a coffee — this one is a deep dive!

Move Your Web App to Mobile: Capacitor 8 or Native Rewrite?

Summary

We compare the move-web-app-to-mobile-with-Capacitor-8 decision using real cost, UIScene support, performance ceiling, and store acceptance risk, all sourced.

  • Capacitor 8.5.0 (July 31, 2026) brought UIScene support on iOS and a CLI migrator that eases the transition.
  • The current core library still supports the legacy AppDelegate path; adopting UIScene needs one new file, one Info.plist entry, and one AppDelegate method.
  • Official sources give no numeric figure for Capacitor's performance ceiling or App Store rejection rate; the decision should follow your product's render intensity.
  • The 8.5.2 patch fixed a bug where scene lifecycle events were forwarded before the page had loaded (#8595).
Move Your Web App to Mobile: Capacitor 8 or Native Rewrite?

If you already have a working web app, is the fastest path to mobile wrapping it with Capacitor 8, or writing native from scratch? This decision requires weighing cost against performance ceiling, especially for teams facing the move web app to mobile Capacitor 8 question in 2026. This article gives you a concrete, sourced decision framework — from what Capacitor 8.5 brings to App Store acceptance risk.

💡 Pro Tip: Before moving to Capacitor, test your existing web app's DOM-heavy animations and large list renders on a real device (not a simulator) — the WebView performance ceiling doesn't show up in a simulator.

Table of Contents

Decision framework: which product fits Capacitor

Capacitor's own documentation defines it as a runtime that creates "Web Native apps": it stays as close to web standards as possible while offering full access to native SDKs when needed. The official statement is clear: "If it works in the browser, it probably works in a mobile app when using Capacitor." This definition is also the foundation of the decision framework.

In practice, ask yourself this: is the app's core value _content and interaction_, or _performance and fluidity_? For content-heavy, form-heavy, CRUD-heavy products (dashboards, admin panels, catalogs, booking flows), moving your web team's existing codebase to mobile via Capacitor is a sound engineering call. For products needing heavy animation, game logic, AR/camera-heavy or frame-sensitive interaction, the WebView's render layer may fall behind native — worth comparing native or a solution with its own render engine like Flutter (see Flutter vs SwiftUI comparison).

The second question is team composition: a web team that knows React/Vue/Svelte but not Swift/Kotlin can still add native capability through Capacitor's plugin API (detailed below) — without spinning up a native team from scratch.

The third question is time horizon: is the priority a quick MVP in the stores, or the right long-term architecture from day one? Capacitor's "your web code runs almost as-is" positioning is a natural MVP-speed advantage, but not an unlimited one. As the product matures and its user base grows, where the performance ceiling starts (next section) matters more. Treat this as a revisitable decision tied to the roadmap, not a one-time choice.

What Capacitor 8.5 brought (UIScene, TypeScript 7)

Capacitor 8.5.0 was released on July 31, 2026. Three changes in the release notes directly affect this decision:

  • iOS UIScene support — listed in the official changelog as "ios: UIScene Support (#8536)." Preparation for the requirement Apple announced at WWDC25: per Flutter's documentation, starting with the release following iOS 26, every UIKit app compiled with the latest SDK will need to use the UIScene lifecycle, or the app won't launch.
  • CLI migrator — a CLI tool was added to ease the transition, via "cli: add migrator functionality for adopting UIScene (#8544)."
  • TypeScript 7 support — the CLI now supports TypeScript 7 when loading capacitor.config.ts ("cli: support TypeScript 7 when loading capacitor.config.ts (#8534)"), listed in the official changelog as a bug fix rather than a feature.

In practice, this means capacitor.config.ts now loads cleanly with the newer TS compiler, with no need to rework your type definitions.

ts
1// capacitor.config.ts — a typical structure that loads cleanly with TypeScript 7
2import type { CapacitorConfig } from "@capacitor/cli";
3 
4const config: CapacitorConfig = {
5 appId: "com.example.webapp",
6 appName: "Example Web App",
7 webDir: "dist",
8 server: {
9 androidScheme: "https",
10 },
11};
12 
13export default config;

Critical point: Capacitor's core library still supports the legacy AppDelegate path. The official 8.5 upgrade guide says this explicitly: "The core library still supports the AppDelegate path, so updating the dependency alone won't break your app. To build with Xcode 27, though, your app project needs to adopt the scene lifecycle."

json
1{
2 "dependencies": {
3 "@capacitor/core": "^8.5.0",
4 "@capacitor/cli": "^8.5.0",
5 "@capacitor/ios": "^8.5.0",
6 "@capacitor/android": "^8.5.0"
7 }
8}

The real cost: wrapping vs. rewriting

Per the 8.5 upgrade guide, adopting UIScene concretely comes down to three parts: "one new file, one Info.plist entry, and one method in your AppDelegate" — namely SceneDelegate.swift, one Info.plist entry, and one AppDelegate method. That's concrete evidence of how low the integration cost is for "wrapping" an existing web app with Capacitor; there's no exact "N days" figure in official sources, so calibrate the time estimate against your own team's experience.

xml
1<key>UIApplicationSceneManifest</key>
2<dict>
3 <key>UIApplicationSupportsMultipleScenes</key>
4 <false/>
5 <key>UISceneConfigurations</key>
6 <dict>
7 <key>UIWindowSceneSessionRoleApplication</key>
8 <array>
9 <dict>
10 <key>UISceneConfigurationName</key>
11 <string>Default Configuration</string>
12 <key>UISceneDelegateClassName</key>
13 <string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
14 <key>UISceneStoryboardFile</key>
15 <string>Main</string>
16 </dict>
17 </array>
18 </dict>
19</dict>

The AppDelegate method that completes this step (from the official guide):

swift
1func application(_ application: UIApplication,
2 configurationForConnecting connectingSceneSession: UISceneSession,
3 options: UIScene.ConnectionOptions) -> UISceneConfiguration {
4 let config = UISceneConfiguration(name: "Default Configuration",
5 sessionRole: connectingSceneSession.role)
6 config.delegateClass = SceneDelegate.self
7 return config
8}

A from-scratch native rewrite, by contrast, brings its own costs: two separate codebases (iOS + Android) to maintain, porting existing web business logic to native, and a new team skillset (Swift + Kotlin). These three items explain why "wrap" is the relatively lower-risk starting point — but they also determine the final performance ceiling (next section).

bash
1# Adding Capacitor to an existing web project (real CLI commands)
2npm install @capacitor/core@^8.5.0 @capacitor/cli@^8.5.0
3npx cap init
4npm install @capacitor/ios@^8.5.0 @capacitor/android@^8.5.0
5npx cap add ios
6npx cap add android
7npx cap sync ios

There's no extra "scene lifecycle" step on the Android side — this change is iOS-specific only. A separate command is enough to sync the Android build:

bash
1# Sync on the Android side (no UIScene step required)
2npx cap sync android
3npx cap open android

Where the performance ceiling starts

Capacitor's WebView-based architecture sits on a different layer than native UI kits' render pipeline (UIKit/SwiftUI, Jetpack Compose). That cost can go unnoticed in most everyday scenarios (form filling, list scrolling, page transitions); but where 60fps is critical — continuous animation, complex canvas rendering, real-time camera processing — the WebView's render layer may fall behind native engines.

No official source quantifies this ceiling numerically, so a precise "don't use Capacitor below this FPS" threshold would be misleading. The practical approach: test critical screens (e.g. the main flow, an onboarding animation) on a real device early in a prototype, and measure the ceiling against your own requirements. If heavy visual interaction is your product's center of gravity, solutions with their own GPU layer, like Flutter's Impeller render engine, are worth comparing.

Don't settle for a single device: WebView behavior can differ between iOS and Android, even between older and newer versions of the same platform. Testing on both a low-to-mid-range Android device and an iPhone that's a few years old gives a realistic answer specific to your product — since there's no general official threshold, this measurement responsibility falls on the team.

Access to native features (plugin ecosystem)

Capacitor's native access model relies on three languages: the official documentation says "a Plugin API for Swift on iOS, Java on Android, and JavaScript for the web." So if a native feature is missing (say, a specific sensor API), you can write your own Swift/Java plugin and call it from JavaScript — this effectively removes the "wrap" approach's ceiling on native access, though writing a plugin requires native knowledge.

Version 8.5 also proves this ecosystem keeps evolving: new scene-lifecycle-specific notifications were added (.capacitorSceneWillConnect, .capacitorSceneOpenURL, .capacitorSceneOpenUniversalLink), but the official guide warns: "Note they're only posted on 8.5 and later, so plugins that also support earlier Capacitor 8 versions should stay on the legacy notifications."

The same version also removed some legacy APIs: "Removed: TmpViewController and the long-deprecated CapacitorBridge.tmpWindow property and tmpViewControllerAppeared notification." This shows plugin maintenance is an ongoing job requiring release tracking — not "wrap once and forget."

In practice: if your project uses community plugins (such as camera, push notifications, biometric auth), check those plugins' changelogs before every Capacitor major/minor upgrade. Official core plugins (under @capacitor/core) generally update in sync with the main release, but third-party plugins' update cadence varies — one of the "wrap" approach's hidden maintenance costs.

swift
1// SceneDelegate.swift from the official 8.5 upgrade guide — the same file
2// works in both SPM and CocoaPods projects; Capacitor's migrator
3// generates it automatically
4import UIKit
5import Capacitor
6 
7class SceneDelegate: UIResponder, UIWindowSceneDelegate {
8 var window: UIWindow?
9 
10 func scene(_ scene: UIScene, willConnectTo session: UISceneSession,
11 options connectionOptions: UIScene.ConnectionOptions) {
12 guard let windowScene = scene as? UIWindowScene else { return }
13 window = UIWindow(windowScene: windowScene)
14 window?.rootViewController = CAPBridgeViewController()
15 window?.makeKeyAndVisible()
16 SceneDelegateProxy.shared.scene(scene, willConnectTo: session, options: connectionOptions)
17 }
18 
19 func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
20 SceneDelegateProxy.shared.scene(scene, openURLContexts: URLContexts)
21 }
22 
23 func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
24 SceneDelegateProxy.shared.scene(scene, continue: userActivity)
25 }
26}

App Store / Play acceptance risk

There's no official source from Apple or Google publishing a numeric rejection rate specific to Capacitor or hybrid apps — so giving a percentage here would be fabrication. What's known: Capacitor's own positioning rests on the principle "if it works in the browser, it probably also works on mobile," meaning the app goes through store review like a native app — there's no separate "hybrid" category or special approval process.

The practical risk comes from the stores' general principle: an app shouldn't be perceived as a "WebView wrapper" that merely displays a website without adding native value. The concrete counterpart to this is using Capacitor's plugin ecosystem (push notifications, biometric authentication, native sharing, etc.) to add real native interaction to the app — not just showing a web page like an <iframe>.

Exit strategy: how to move to native later

There's no official "exiting Capacitor for native" document, but the architecture itself is designed to make this transition easier: Capacitor's plugin API already requires you to write native code in Swift/Java. In practice, a gradual transition works like this: first rewrite a single performance-critical screen (e.g. the main flow animation) as a native plugin or a native subview, and leave the rest in the WebView. As the proportion of native screens grows over time, you end up with a lower-risk, piece-by-piece path to a full native rewrite. This strategy isn't based on an official source — read it as an editorial suggestion, to be adapted to your team's risk tolerance.

The biggest advantage of this gradual transition is that it removes the "all or nothing" decision. When a team starts with Capacitor and moves a specific screen to native, it's not a problem for the rest of the app to stay WebView-based — Capacitor's plugin architecture already allows web and native code to coexist within the same app. This makes the "exit fast first, move critical screens to native later" strategy technically workable.

Decision table

Criterion
Capacitor 8.5 (wrap)
Native rewrite
Integration cost
Low — 1 file + 1 Info.plist entry + 1 AppDelegate method added to existing web code (for UIScene)
High — two separate codebases, written from scratch
Team skillset
Existing web team + Swift/Java when native plugins are needed
Swift (iOS) + Kotlin (Android) expertise required
Access to native features
Via Plugin API (Swift/Java/JS) — comprehensive but requires writing code
Direct, no intermediary
Performance ceiling
WebView render layer — editorial caution needed for heavy animation/AR (no official benchmark)
Native render engine — higher upper bound
Store acceptance
General principle: treated like native if it has real native interaction (no official statistics)
Standard native review process
Version tracking
Requires tracking plugin compatibility across 8.x → 9.x transitions (see 8.5 notification change)
Requires tracking platform SDK updates

FAQ

How do I move my web app to mobile with Capacitor?

You install the @capacitor/core and @capacitor/cli packages into your existing web project and initialize with npx cap init, then create the platform projects with npx cap add ios / npx cap add android and copy your web build into the native project with npx cap sync. For builds after iOS 26, adopting UIScene (a SceneDelegate file + an Info.plist entry + an AppDelegate method) is required.

What changed in Capacitor 8?

Capacitor 8.5.0 (July 31, 2026) brought UIScene support for iOS along with a CLI migrator that eases the transition, and also added TypeScript 7 support when loading capacitor.config.ts.

Does a Capacitor app get accepted to the App Store?

There's no separate approval category or published rejection-rate statistic from Apple or Google specific to Capacitor. Capacitor's own positioning rests on the principle that an app that works in the browser will also work on mobile; having the app include real native interaction (push, biometric, native sharing, etc.) reduces the risk of it being perceived as a mere web page wrapper.

Is Capacitor or from-scratch native cheaper?

There's no exact cost figure in official sources. But the technical evidence shows: moving to Capacitor (including UIScene) requires small, targeted additions to existing code, while a from-scratch native rewrite requires building two separate codebases from the ground up — this asymmetry generally makes Capacitor the lower initial-cost option for teams with an existing web codebase.

GOLDEN TIP

The most valuable insight in this article

This tip holds the article's most important takeaway.

Easter Egg

You found a hidden gem!

There's a hidden detail in this section. Want to uncover it?

Reader Reward

Here's the technical checklist to review before moving your web app to mobile with Capacitor 8. This list is a practical summary of the sourced information in the article, and can be used as a final check before the transition.

Update (September 2026)

Since this article was published on August 6, 2026, Capacitor has received two more patch releases: 8.5.1 and 8.5.2 (September 11, 2026). One of the changes in 8.5.2 is directly related to UIScene support: "ios: do not forward scene lifecycle events to the page before it has loaded (#8595)" — meaning scene lifecycle events are no longer forwarded to the JavaScript side before the page has fully loaded. This is concrete evidence that UIScene support from 8.5.0 continues to mature. Also, 9.0.0-alpha.7 was released on September 18, 2026, but it's still in alpha and shouldn't yet be treated as a reference for a production decision. If you want to keep this article current, we recommend checking the current latest tag (queryable on npm) before going to production.

Conclusion

Moving a web app to mobile with Capacitor 8 is a technically low-friction starting point if you already have a web codebase. Capacitor 8.5's UIScene support and migrator make it easier to prepare for the requirement Apple announced at WWDC25; but the final decision depends on your product's performance profile. For products requiring heavy animation or game-like interaction, it's worth also checking out the Flutter vs SwiftUI comparison or the React Native vs Flutter comparison. If you're coming from the Kotlin/Compose ecosystem, take a look at the Compose Multiplatform production example and the Kotlin Multiplatform 1.1 case study. If you need to set up subscription infrastructure on mobile, our RevenueCat cross-platform subscription guide and Mobile DevOps best practices for CI/CD setup cover the next steps of this transition.

Sources

Tags

#Capacitor#Cross-Platform#iOS#WebView#UIScene#Migration#TypeScript#Mobile
Muhittin Çamdalı

Muhittin Çamdalı

Lead Mobile Engineer

Lead Mobile Engineer with 12+ years of experience. Expert in iOS, Android and cross-platform architectures with Swift, SwiftUI, Kotlin and Flutter. I build performant, user-friendly mobile apps.

iOS Development News

Weekly Swift tips, SwiftUI tricks and iOS best practices. No spam, only valuable content.

We respect your privacy. You can unsubscribe at any time.

Share