Project Structure
Ionic project layout: where pages, components, services, and Capacitor config live. The structure underneath ionic start.
Ionic — project structure
EXAMPLE
# ===== After 'ionic start my-app blank --type=angular' =====
# my-app/
# src/
# app/
# app.module.ts Root module
# app.component.ts Root component
# home/
# home.module.ts
# home.page.ts
# home.page.html
# home.page.scss
# services/
# auth.service.ts
# guards/
# interceptors/
# assets/
# theme/
# variables.scss Ionic CSS variables (theming)
# index.html
# main.ts
# polyfills.ts
# capacitor.config.ts Native config
# ionic.config.json Ionic CLI config
# angular.json
# package.json
# tsconfig.json
# ios/ (after npx cap add ios)
# android/ (after npx cap add android)
# ===== capacitor.config.ts =====
import { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.example.myapp',
appName: 'My App',
webDir: 'www',
bundledWebRuntime: false,
plugins: {
SplashScreen: { launchShowDuration: 2000 },
},
};
export default config;
# ===== ionic.config.json =====
{
"name": "my-app",
"integrations": { "capacitor": {} },
"type": "angular"
}
# ===== Pages =====
ionic generate page settings
# Creates src/app/settings/settings.{module,page}.{ts,html,scss}
# Auto-registers a route in app-routing.module.ts.
# ===== Routing =====
# src/app/app-routing.module.ts
import { Routes } from '@angular/router';
const routes: Routes = [
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: 'home', loadChildren: () => import('./home/home.module').then(m => m.HomePageModule) },
{ path: 'settings', loadChildren: () => import('./settings/settings.module').then(m => m.SettingsPageModule) },
];
# Lazy-loaded by default — each page is its own bundle.
# ===== Theme (variables.scss) =====
:root {
--ion-color-primary: #2563eb;
--ion-color-primary-rgb: 37, 99, 235;
--ion-color-primary-contrast: #ffffff;
--ion-color-primary-shade: #1d4ed8;
--ion-color-primary-tint: #3b82f6;
}
@media (prefers-color-scheme: dark) {
:root { --ion-background-color: #121212; }
}
# ===== Services =====
ionic generate service services/api
# Provided in root by default; inject via constructor.
@Injectable({ providedIn: 'root' })
export class ApiService {
constructor(private http: HttpClient) {}
list() { return this.http.get('/api/items'); }
}
# ===== Adding native plugins =====
npm install @capacitor/camera
npx cap sync
# Then import + call:
import { Camera, CameraResultType } from '@capacitor/camera';
const photo = await Camera.getPhoto({ resultType: CameraResultType.Uri });
# ===== Builds =====
ionic build --prod # Angular AOT + minify
npx cap sync # copy web assets to ios/android
npx cap open ios # archive in Xcode
npx cap open android # build AAB in Android Studio
# ===== Patterns to internalise =====
# - Generate pages + services via CLI; do not copy-paste folders
# - One service per concern (auth, api, cache, ...)
# - theme/variables.scss as the only place for brand colours
# - capacitor.config.ts as the source of truth for native config
# ===== Pitfalls =====
# - Editing ios/android files directly without 'npx cap sync' aftermath
# - Mixing Bootstrap / Tailwind with Ionic CSS variables -> drift
# - Forgetting providedIn: 'root' -> multiple instances
# - Hard-coding URLs instead of environment files
Why it matters
Ionic projects layer Angular (or React/Vue) + Capacitor + Ionic UI components. Pages under src/app, services in root-provided injectables, theming via CSS variables in src/theme, native config in capacitor.config.ts. Generate with the CLI and the structure stays predictable.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// myApp/ // src/ pages, components, theme // public/ static assets // ios/ android/ generated by Capacitor // ionic.config.json, capacitor.config.tsTry it Yourself »
Discussion
Loading…