Skip to content
Mehedi Hasan Sagor

// blog

Hybrid Mobile Architecture: Combining Ionic UI with React Native Core Logic

Mehedi Hasan Sagor Oct 11, 2026 5 min read
Hybrid Mobile Architecture: Combining Ionic UI with React Native Core Logic

Why a Hybrid Approach?

Startups often face a classic trade‑off: speed of delivery versus native performance. Ionic gives you fast web‑based UI development with a single HTML/CSS/JS codebase, but complex animations or heavy device‑level interactions can suffer. React Native, on the other hand, offers near‑native rendering and a rich ecosystem of native modules, but maintaining two separate codebases for iOS and Android can double the effort.

A hybrid architecture lets you keep the rapid UI iteration of Ionic while moving core business logic and performance‑critical modules into a shared React Native layer. The result is:

  • One source of truth for data models, validation, and API clients.
  • UI teams can iterate with web tooling (Live Reload, Chrome DevTools).
  • Native teams can tap into platform APIs without rewriting the whole app.

1. Structure the Monorepo

A monorepo gives you a single package.json at the root and separate workspace packages for each platform:

my-hybrid-app/
├─ packages/
│  ├─ core/          # Shared TypeScript business logic
│  ├─ ionic/         # Ionic UI project (Angular or Vue)
│  └─ react-native/  # RN app (Expo or bare)
├─ .gitignore
└─ package.json

In the root package.json enable Yarn workspaces (or npm workspaces):

{
  "private": true,
  "workspaces": ["packages/*"]
}

Both ionic and react-native can import from core using the workspace alias, e.g. import { fetchUser } from "@my-hybrid-app/core";. This eliminates duplicate API client code and keeps validation rules consistent.

2. Shared Business Logic in TypeScript

Write all data‑fetching, caching, and validation in plain TypeScript. Avoid any DOM or React‑specific APIs. A minimal example:

// packages/core/src/api.ts
import axios from "axios";

export interface User {
  id: number;
  name: string;
  email: string;
}

export const fetchUser = async (id: number): Promise<User> => {
  const { data } = await axios.get<User>(`/api/users/${id}`);
  return data;
};

Because the code is framework‑agnostic, you can run unit tests once and reuse the same implementation in both UI layers.

3. UI Layer Strategies

Ionic UI

Use the core package for data and state management. In an Ionic/Angular component:

// packages/ionic/src/app/pages/profile.page.ts
import { Component, OnInit } from "@angular/core";
import { fetchUser, User } from "@my-hybrid-app/core";

@Component({ selector: "app-profile", templateUrl: "./profile.page.html" })
export class ProfilePage implements OnInit {
  user!: User;

  async ngOnInit() {
    this.user = await fetchUser(1);
  }
}

The UI stays lightweight; all heavy lifting lives in core.

React Native UI

In React Native you can also import the same functions:

// packages/react-native/src/screens/ProfileScreen.tsx
import React, { useEffect, useState } from "react";
import { View, Text } from "react-native";
import { fetchUser, User } from "@my-hybrid-app/core";

export const ProfileScreen = () => {
  const [user, setUser] = useState<User | null>(null);

  useEffect(() => {
    fetchUser(1).then(setUser);
  }, []);

  if (!user) return <Text>Loading…</Text>;
  return (
    <View>
      <Text>{user.name}</Text>
      <Text>{user.email}</Text>
    </View>
  );
};

Because the UI code lives in separate packages, you can still use Ionic’s web‑centric styling while React Native leverages native components.

4. Native Plugin Integration via Capacitor Bridge

When you need a device feature that Ionic can’t access directly (e.g., advanced Bluetooth, AR), you have two options:

  1. Capacitor Plugin – Write a custom Capacitor plugin in native Swift/Java/Kotlin and expose it to the web layer. Ionic can call Capacitor.Plugins.MyPlugin.doThing().
  2. React Native Module – Implement the same native code as a React Native module and expose a JS wrapper.

To avoid duplication, implement the native code once (e.g., in a Swift package) and expose two thin wrappers: one for Capacitor and one for React Native. The shared core package can then import a small abstraction layer:

// packages/core/src/native.ts
export interface NativeBridge {
  scanDevices(): Promise<string[]>;
}

let bridge: NativeBridge;

export const setBridge = (b: NativeBridge) => (bridge = b);

export const scanDevices = () => bridge.scanDevices();

In Ionic’s bootstrap:

import { Plugins } from "@capacitor/core";
import { setBridge } from "@my-hybrid-app/core";

setBridge({
  scanDevices: () => Plugins.MyPlugin.scanDevices()
});

In React Native’s entry point:

import { NativeModules } from "react-native";
import { setBridge } from "@my-hybrid-app/core";

setBridge({
  scanDevices: () => NativeModules.MyNativeModule.scanDevices()
});

Now the business logic in core can call scanDevices() without caring which UI layer invoked it.

5. CI/CD and Automated Testing

A monorepo makes it straightforward to run a single pipeline that validates all packages:

# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Install dependencies
        run: yarn install --frozen-lockfile
      - name: Lint & TypeCheck
        run: yarn workspaces run lint && yarn workspaces run typecheck
      - name: Unit tests (core)
        run: yarn workspace core test
      - name: UI tests (Ionic)
        run: yarn workspace ionic test
      - name: UI tests (React Native)
        run: yarn workspace react-native test

Because the core package is tested once, you get confidence that API contracts stay stable across both front‑ends. For end‑to‑end testing, you can spin up a Docker container that serves the API and run Detox (React Native) and Cypress (Ionic) in parallel.

6. Takeaway

A hybrid architecture that pairs Ionic for rapid UI prototyping with React Native for performance‑critical screens gives startups the best of both worlds. The key ingredients are:

  • A monorepo with a framework‑agnostic core package.
  • TypeScript‑only business logic to avoid duplication.
  • Unified native bridge so the same feature works in both UI layers.
  • A single CI pipeline that validates all packages.

Adopting this pattern lets you ship to the App Store and Google Play faster, keep code maintainable, and still deliver a native feel where it matters.

  • Ionic
  • React Native
  • Architecture
  • Mobile
  • Ci Cd