How do you structure a Cordova project for multi-platform scale?
Cordova Developer
answer
A scalable Cordova project structure centralizes shared app code (TypeScript + modules) and isolates platform specifics behind a plugin architecture. Keep UI/state in src/ with strict linting, tests, and CI; generate platform bundles per target. Encapsulate native APIs in custom plugins; never call them directly from views. Use environment config, feature flags, and Capacitor-style shims where helpful. Enforce code quality via Prettier/ESLint, unit/e2e tests, and reproducible builds.
Long Answer
Designing for code quality and scalability in a Cordova app means treating the project like any modern cross-platform product: a clean separation of concerns, deterministic builds, and a plugin-first approach to native differences. The core idea is simple—write once in a shared layer, hide platform variance behind clearly versioned adapters, and let CI enforce consistency across iOS, Android, and Web.
1) Foldering and ownership
Create a monorepo (or single repo with workspaces) where the app lives in src/ and platform outputs live in platforms/ (generated) and plugins/ (source-of-truth). A pragmatic layout:
/src
/app (views, routing, screens)
/core (domain, state, services, DTOs)
/ui (design system, components)
/bridge (TS wrappers around Cordova plugins)
/config (envs, feature flags)
/tests (unit, contract, e2e)
/scripts (build/release)
/plugins (custom plugins source)
/www (compiled assets for Cordova)
Only src and plugins are hand-edited; platforms is ephemeral. The Cordova project structure remains predictable and reviewable.
2) Shared code, typed boundaries
Write application logic in TypeScript. Domain and state management (Redux/Pinia/MobX or RxJS) live in /core. Views consume typed services; services talk to /bridge wrappers that expose promises/observables for each plugin call. No UI imports native code directly. This keeps multiplatform code DRY and gives the compiler a say in code quality.
3) Plugin architecture and adapters
All native features (camera, files, auth, biometrics, in-app purchase) should be accessed via plugins. Prefer maintained ecosystem plugins; when gaps exist, build custom plugins with a stable TS interface and platform adapters:
- bridge/camera.ts (TS) → calls cordova.plugins.Camera
- iOS adapter (Swift/Obj-C) and Android adapter (Kotlin/Java) live in /plugins/camera/
Version plugins semantically and pin them in config.xml/package.json. Keep changelogs so Android/iOS upgrades are intentional, minimizing regressions.
4) Build system, environments, and reproducibility
Use a two-stage build:
(1) Web build: bundle src with Vite/Webpack (tree-shaking, code-split), emit to /www.
(2) Cordova prepare: sync /www into platform projects.
Manage environment configs (dev/stage/prod) and feature flags under /config, injected at build time. Pin Node/Java/Gradle/Xcode via CI containers. Lock dependencies with a clean lockfile and cache per platform.
5) Quality gates: lint, test, type, e2e
- Static checks: ESLint + Prettier + TypeScript strict mode; run on every PR.
- Unit tests: Jest/Vitest for services and utilities; mock plugin bridges.
- Contract tests: verify TS ↔ native plugin payloads (e.g., JSON schemas).
- E2E: Appium/Detox (or WebDriver + real devices) for iOS and Android smoke paths; Playwright for Web.
- Accessibility: axe-core checks on web build; basic VoiceOver/TalkBack scenarios in e2e scripts.
6) UI system and theming
Keep a lightweight design system in /ui (buttons, lists, forms) with responsive tokens and platform nuances toggled via CSS vars (safe areas, touch sizes, font stacks). This reduces divergence between platforms and improves scalability when screens grow.
7) Data layer and offline strategy
Abstract network and storage. Provide a Repository pattern in /core with retry, caching, and an offline queue. Storage uses a bridge (e.g., SQLite or IndexedDB) hidden behind one API. This keeps business logic stable while platform storage varies.
8) Performance and startup
Optimize /www payload: code-split routes, prefetch critical chunks, defer heavy plugins until needed (lazy load bridge methods). On Android, keep minSdk/targetSdk aligned; on iOS, manage WKWebView settings and asset compression. Use a warmup screen rendered purely in web to mask plugin init. Measure with Web Vitals (web) and native trace tools.
9) Governance and conventions
Document naming, error handling, and logging. Each bridge method returns a typed result and a normalized error shape. Centralize analytics/telemetry in /core. Require ADRs (architecture decision records) when adding new plugins or platform permissions. Keep config.xml minimal; push complexity to code and scripts where it can be tested.
10) Scaling teams and releases
Automate store builds with CI (Fastlane for iOS/Android), signing via secure secrets, and a release pipeline that tags versions, bumps plugin deps, runs full test matrices, and uploads artifacts. On Web, deploy the same /www bundle behind feature flags to keep parity and reduce drift.
This approach yields a multiplatform Cordova app that’s pleasant to evolve: shared, typed core; strict boundaries to native; reproducible builds; and quality gates that keep code quality high as the product, team, and platforms scale.
Table
Common Mistakes
- Calling native plugins directly from views (no bridge/ layer), coupling UI to platforms.
- Treating platforms/ as source and committing generated code.
- Skipping TypeScript strict mode and contract tests for plugin payloads.
- Mixing environment logic across templates and scripts; no typed config.
- One huge bundle in /www (no code-split), causing slow startup.
- No automated e2e on real devices; “it works on my emulator” drift.
- Version-floating Cordova/plugins; builds differ between devs and CI.
- Overusing WebViews for heavy work; not deferring non-critical plugins.
- No ADRs or permissions review; capability creep and store rejections.
Sample Answers
Junior:
“I keep shared logic in src/ with TypeScript and use Cordova plugins only via a bridge/ wrapper. Platform projects are generated; I don’t edit them. Lint and unit tests run in CI, and I bundle to /www before cordova prepare.”
Mid:
“I organize features by modules, add a small design system, and pin toolchains for reproducible builds. Custom plugins expose a typed TS interface; I test payloads with schemas. E2E runs on Android and iOS with Appium, plus Playwright for Web.”
Senior:
“We run a monorepo with workspaces for app and plugins, strict TS, and a repository data layer with offline support. CI builds a matrix (iOS/Android/Web), signs mobile artifacts, and gates merges on unit/e2e. Releases use Fastlane, feature flags, and ADR-governed plugin/version upgrades.”
Evaluation Criteria
- Architecture: Clear Cordova project structure; shared code vs. platform adapters; platforms/ treated as build output.
- Abstractions: Plugin access through a typed bridge/; no UI→native coupling.
- Quality: Lint, type checks, unit + e2e across iOS, Android, Web; reproducible CI.
- Builds/Config: Two-stage build; pinned toolchains; typed env + feature flags.
- Performance: Code-split, lazy plugin init, asset discipline.
- Governance: ADRs, permissions policy, semantic versioning for plugins.
Red flags: Editing generated platform code; floating versions; no device e2e; direct plugin calls in views; giant /www bundle; missing typed config.
Preparation Tips
- Convert to TypeScript with strict mode; add ESLint/Prettier.
- Create bridge/ for all plugin calls; replace direct usage in views.
- Split features into modules; add a minimal design system in /ui.
- Introduce typed ConfigProvider with env files and feature flags.
- Add Jest/Vitest unit tests; mock the bridge.
- Stand up Appium/Detox for smoke e2e on real devices; Playwright for Web parity.
- Script the build: web → /www → cordova prepare; cache toolchains in CI.
- Pin Cordova CLI, platforms, and plugins; record versions in ADRs.
- Measure first paint and interaction; defer non-critical plugin init.
Real-world Context
- Fintech wallet: Moved native calls to a bridge/ and added schema-checked payloads; production crashes from plugin misuse fell 70%.
- Retail app: Two-stage build and code-split reduced /www by 45%, cutting cold start by ~800 ms on low-end Android.
- Field ops suite: Custom camera/storage plugins with typed TS wrappers; offline queue let techs sync later. E2E device tests caught an iOS permission regression before release.
- Consumer portal: Monorepo for app + plugins; Fastlane pipelines signed and shipped both stores from a single tag; Web received the same bundle behind feature flags for near-zero drift.
Key Takeaways
- Centralize shared TS code; isolate platforms behind a typed plugin/bridge layer.
- Treat platforms/ as generated; pin toolchains for reproducibility.
- Enforce code quality with lint, types, unit + e2e on real devices.
- Use env configs and feature flags to ship safely across iOS/Android/Web.
- Optimize /www: code-split, lazy plugin init, and measure.
Practice Exercise
Scenario:
Your Cordova app must ship to iOS, Android, and Web in 4 weeks. Current repo mixes direct plugin calls in views, giant bundles, and emulator-only testing. You need a Cordova project structure that scales and safeguards code quality.
Tasks:
- Create src/core, src/ui, and src/bridge. Migrate all plugin usages (camera, filesystem, biometrics) into typed bridge/* modules returning promises.
- Introduce a typed ConfigProvider with dev/stage/prod envs and two feature flags (e.g., enableBiometrics, useNewCheckout).
- Split routes by feature; enable code-splitting. Ensure /www is reproducible from src.
- Add Jest tests for core services and schema checks for bridge payloads.
- Set up Appium smoke tests: login, take photo, upload, and logout on a real Android and an iOS simulator; add a Playwright run for the Web bundle.
- Pin Cordova CLI/platforms/plugins; cache toolchains in CI; fail builds on lint/type/test errors.
- Write an ADR for a new custom plugin (e.g., secure storage), describing TS interface, iOS/Android adapters, and permission prompts.
- Implement a minimal design system (buttons, inputs, toasts) and apply across two screens to remove divergent styles.
Deliverable:
A PR that replaces direct native calls with the bridge/, adds tests and CI checks, produces smaller /www, and demonstrates green e2e on iOS, Android, Web with feature-flagged behavior documented in a short README.

