KhueApps
Home/React Native/Is React Native Expo Ready for Large-Scale Web Apps?

Is React Native Expo Ready for Large-Scale Web Apps?

Last updated: October 07, 2025

TL;DR

  • Yes, Expo for Web can power large apps when you’re building SPAs, dashboards, or internal tools with shared native code.
  • For SEO-critical, SSR-heavy, or edge-rendered web experiences, pair Expo (native) with a Next.js web app that uses react-native-web and shared UI packages.
  • The sweet spot: share components and business logic across platforms, but don’t force web-specific needs through native constraints.

What “Expo for Web” actually is

Expo for Web runs your React Native code on the web via react-native-web, mapping core primitives (View, Text, Pressable) to DOM/CSS. You get:

  • One codebase for iOS, Android, and Web
  • Expo modules (where web support exists) and unified tooling
  • File-based routing with Expo Router (SPA on web)

What you do not get by default:

  • Server-side rendering (SSR) or static site generation (SSG)
  • Advanced web bundling features you may expect from Next.js (e.g., built-in image optimization, RSC features)

When Expo Web is a good fit

  • Internal tools, admin dashboards, B2B apps
  • Authenticated SPAs where SEO is not critical
  • Microfrontends that can be embedded as SPAs
  • Teams optimizing for maximum code sharing with mobile

When to pair with Next.js (or keep web separate)

  • SEO, social previews, and fast first contentful paint via SSR/SSG/ISR
  • Edge rendering, middleware, and complex CDN cache strategies
  • Large teams that need advanced web-only ergonomics (RSC, image/fonts pipelines)

Pattern options:

  1. Single app (Expo only): fastest to ship, SPA web.
  2. Monorepo: Expo (native) + Next.js (web) sharing UI and logic via packages.

Minimal working example (Expo Web SPA)

This shows a tiny Expo Router app that runs on iOS, Android, and Web.

// File: app/_layout.tsx
import { Stack } from 'expo-router';

export default function Layout() {
  return <Stack />;
}

// File: app/index.tsx
import { View, Text, Pressable, StyleSheet } from 'react-native';
import { useState } from 'react';

export default function Home() {
  const [count, setCount] = useState(0);
  return (
    <View style={styles.container}>
      <Text accessibilityRole="header" style={styles.title}>Hello, Web + Native</Text>
      <Text>Count: {count}</Text>
      <Pressable onPress={() => setCount(c => c + 1)} style={styles.btn}>
        <Text style={styles.btnText}>Increment</Text>
      </Pressable>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: 12 },
  title: { fontSize: 20, fontWeight: '600' },
  btn: { backgroundColor: '#2563eb', paddingHorizontal: 12, paddingVertical: 8, borderRadius: 6 },
  btnText: { color: 'white', fontWeight: '600' },
});

Run on web: npx expo start --web

Quickstart

A) Build a large SPA with Expo only

  1. Create app: npx create-expo-app@latest --template tabs
  2. Add Expo Router if not present: npx expo install expo-router react-native-safe-area-context react-native-screens
  3. Configure Entry: add "main": "expo-router/entry" in package.json
  4. Start web: npx expo start --web
  5. Scale with shared modules, env configs, and platform-specific files (.web.tsx, .native.tsx)

B) Monorepo: Expo (native) + Next.js (web)

Goal: Keep mobile in Expo, get SSR/SSG in Next.js, and share UI via react-native-web.

  1. Create a monorepo (pnpm/yarn workspaces). Structure:
    • apps/mobile (Expo)
    • apps/web (Next.js)
    • packages/ui (shared RN components)
  2. In packages/ui, write RN components only using core RN APIs.
// packages/ui/src/Button.tsx
import { Pressable, Text, StyleSheet } from 'react-native';

export function Button({ title, onPress }: { title: string; onPress: () => void }) {
  return (
    <Pressable onPress={onPress} style={styles.btn}>
      <Text style={styles.txt}>{title}</Text>
    </Pressable>
  );
}

const styles = StyleSheet.create({
  btn: { backgroundColor: '#16a34a', paddingHorizontal: 14, paddingVertical: 8, borderRadius: 6 },
  txt: { color: 'white', fontWeight: '600' },
});
  1. In apps/web (Next.js), configure react-native-web aliases and transpilation.
// apps/web/next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  transpilePackages: [
    'react-native',
    'react-native-web',
    'expo',
    'expo-modules-core',
    'packages/ui',
  ],
  webpack: (config) => {
    config.resolve.alias = {
      ...(config.resolve.alias || {}),
      'react-native$': 'react-native-web',
    };
    config.resolve.extensions = [
      '.web.tsx', '.web.ts', '.web.jsx', '.web.js',
      ...config.resolve.extensions,
    ];
    return config;
  },
};
module.exports = nextConfig;
// apps/web/babel.config.js (Next will switch to Babel if present)
module.exports = { presets: ['babel-preset-expo'] };
// apps/web/app/page.tsx (Next.js App Router)
'use client';
import { Button } from 'packages/ui/src/Button';
import { View, Text } from 'react-native';

export default function Page() {
  return (
    <View style={{ padding: 24 }}>
      <Text accessibilityRole="header">Next.js + react-native-web</Text>
      <Button title="Click" onPress={() => alert('Hello')} />
    </View>
  );
}
  1. In apps/mobile (Expo), consume the same UI package and run npx expo start.
  2. Deploy Next.js for web (SSR/SSG/ISR) and keep Expo for native releases.

Performance notes

  • Component choice: Prefer core primitives (View, Text, Pressable). Avoid heavy DOM wrappers or web-only CSS tricks inside shared components.
  • Styles: Use StyleSheet.create for better RNW performance; avoid inline objects in tight loops.
  • Lists: Use FlatList/SectionList with getItemLayout when possible; keep item components pure.
  • Code-splitting: Use dynamic import on web routes or large modules.
  • Images: On Expo-only web, pre-size images and cache aggressively. In Next.js, use its image pipeline.
  • Animations: Simple transitions are fine. Complex gestures/physics may perform worse on web; test Reanimated/Gesture Handler on target browsers.
  • Bundle health: Track bundle size; prune unused Expo modules and avoid large polyfills.

Common pitfalls

  • Module support: Not all native Expo modules have web implementations. Guard by Platform.OS checks or provide fallbacks.
  • Browser APIs: Node-specific APIs (fs, path) aren’t available. Prefer Web APIs or conditional dependencies.
  • Accessibility: React Native semantics differ from semantic HTML. Validate roles/labels on web.
  • Routing: Expo Router gives SPA routing on web. If you need SSR routes, use Next.js.
  • CSS expectations: RN styles are not CSS; don’t rely on selectors or layout features that don’t exist in RN (e.g., grid) inside shared code.
  • Build tooling: Mixed transpilation across packages can cause resolution issues. Ensure consistent TS targets and that shared packages are transpiled.

How to decide for a “big” project

  • If SEO/SSR is a requirement, plan for Next.js on web from day one; still share 60–90% of UI/logic.
  • If your product is primarily authenticated app surfaces (dashboards, forms, feeds), Expo Web alone is often sufficient and faster to maintain.
  • Validate critical libraries: charts, maps, editors. Prefer RNW-compatible libraries or isolate them behind web-only components.
  • Prove it with a spike: build one complex screen and measure time-to-interactive, bundle size, and dev velocity before committing.

Tiny FAQ

  • Does Expo Web support SSR? Not out of the box. Use Next.js (or another SSR framework) for web SSR/SSG.
  • Can I reach 100% code sharing? Rarely. Expect some .web/.native splits for routing, images, and platform quirks.
  • Is performance comparable to React DOM apps? Often close for typical app UIs; test heavy animations, huge lists, and complex editors.
  • Can I use Tailwind or CSS-in-JS? Yes, but in shared RN components prefer RN styles; apply web-only styling in web-specific files.

Bottom line

Expo for Web is “good enough” for many large SPAs and internal apps, especially when code sharing with mobile is a priority. For SEO/SSR-centric web products, pair Expo (native) with a Next.js web app, sharing a unified React Native component library across platforms.

Series: React Native Intermediate tutorials

React Native