Angular + Capacitor: a blank WebView after sync is usually webDir
Blank or stale WebView after npx cap sync? Usually webDir: Angular's application builder writes to dist/project/browser, not www or bare dist/project. Build, sync, live-reload traps.
ng build, then npx cap sync, open the app on a phone or emulator, and you get a blank WebView. Or worse, last Tuesday's UI. Angular is innocent again. Capacitor copied whatever folder you pointed at with webDir, and that folder either has no index.html at the root or still holds yesterday's bundle.
Last week was the blank window after packaging Electron: dist/.../browser paths, base href, and the load protocol. This post is the mobile twin of that story. Same Angular app, different native shell: Ionic UI optional, Capacitor required as the runtime.
The problem: webDir must point at the folder that holds index.html
Capacitor does not build your frontend. It takes already-built assets from webDir and copies them into the Android/iOS projects during npx cap sync. The docs are blunt: webDir is the directory of compiled web assets, and it should contain the final index.html (Capacitor Configuration). The workflow is build web, sync, run (Development Workflow).
Enter Angular. The application builder (default since Angular 17) writes the bundle to dist/project-name/browser, not dist/project-name. Same Output location change I called out for Electron: Angular application build system.
Meanwhile npx cap init often suggests www (the Ionic starter convention) or another framework default. The official getting started guide even warns you: the CLI tries to detect the right folder, but you should still verify it on the first sync (Installing Capacitor). If sync cannot find the web assets directory, the docs say to fix webDir.
What that looks like in practice (example, not a production war story):
webDir: 'www'whileng buildwrites todist/my-app/browser→ sync has nothing useful to copy.webDir: 'dist/my-app'(missingbrowser) → native gets a folder that contains abrowsersubfolder, the WebView looks forindex.htmlat the root, blank screen.webDiris correct, but you rancap syncwithout a freshng build→ the device shows stale UI.
Meat: set webDir, then keep build → sync order
For a plain ng new project on Angular 17+, a typical config looks like this:
import type { CapacitorConfig } from "@capacitor/cli";
const config: CapacitorConfig = {
appId: "com.example.myapp",
appName: "My App",
// my-app = project name in angular.json; match outputPath / what ng build actually writes
webDir: "dist/my-app/browser",
};
export default config;Or JSON if you are not using TypeScript for the config:
"appId": "com.example.myapp",
"appName": "My App",
"webDir": "dist/my-app/browser"
}Then the order Capacitor expects:
ng build --configuration production
# 2. copy webDir into android/ and ios/ + update native deps
npx cap sync
# 3. run on a device / emulator
npx cap run android
# or: npx cap run iosIf you start from the Ionic CLI, Capacitor is already in new projects (ionic start), and wrappers like ionic capacitor sync do the same work underneath (Using Capacitor with Ionic Framework). Ionic starters often set outputPath to www. Then webDir: 'www' is correct. The trap shows up when you take a plain Angular app that writes to dist/.../browser and keep an Ionic-shaped www in the config.
Capacitor itself does not require Ionic UI. It wraps any web bundle. Ionic gives you mobile components (ion-button, ion-tabs, gestures). If you add Ionic to Angular and use ion-router-outlet, bootstrapping usually registers IonicRouteStrategy as the RouteReuseStrategy (that is what the official scaffolds do, e.g. Your First Ionic App). That is a separate layer from webDir, but worth knowing: native shell and routing stack are two different concerns.
Quick smoke check after sync (example):
ls android/app/src/main/assets/public/index.htmlMissing file means bad webDir or no build before sync. File exists but the screen is blank: open remote WebView debugging and look for asset 404s (often a stale sync or wrong root).
Pitfalls: live-reload, www vs browser, stale sync
Live-reload left in the config. server.url can point at a dev server. The docs mark it for live-reload only, not production (Capacitor Configuration). Leave http://192.168.x.x:4200 in the config, ship a release sync, and the phone aims at a laptop that is not in the user's pocket. For a production sync, keep server.url off.
www is not universal. It is the convention many Ionic starters use. A plain Angular CLI app on the application builder writes into browser. Always align webDir with what ng build actually emits, not with muscle memory from an old tutorial.
Sync without a build. npx cap sync copies what is already on disk. It does not run ng build for you. Changed a component → build first, then sync (or cap copy if you only changed web assets).
The ng add @capacitor/angular path. The official Angular + Capacitor page shows ng add @capacitor/angular, then build and npx cap add (Angular & Capacitor). The schematic does not excuse you from checking webDir after the first build.
Ionic CLI wrappers. ionic capacitor sync is convenient, but it still needs a correct webDir and a fresh build. The wrapper will not fix a wrong path.
Verdict
A blank or stale WebView after the first cap sync is almost always webDir and the build → sync order, not "Angular hates mobile." Point webDir at the folder that actually contains index.html (for a modern Angular app, usually dist/), build before every production sync, and do not leave a live-reload server.url in a release config. Ionic UI and IonicRouteStrategy are the comfort layer. First make Capacitor serve the right bundle.
Desktop had path bugs after electron-forge make. Mobile has path bugs after cap sync. Same Angular, same class of mistake, different shell.