Skip to main content

Best Expo + Next.js Shared Boilerplates 2026

·StarterPick Team
exponext-jsreact-nativecross-platformmonorepot3-turbo2026

TL;DR

T3 Turbo is the most battle-tested Expo + Next.js monorepo starter. It shares tRPC types and Prisma schemas between mobile and web with a Turborepo setup. Solito adds shared React Navigation/Next.js routing. For most teams building web + mobile from one codebase: start with T3 Turbo and add Solito if you want shared navigation components. Pure Expo Router + Next.js co-location is emerging but still experimental for production.

Key Takeaways

  • T3 Turbo: tRPC + Prisma + NextAuth shared between Expo + Next.js, Turborepo, production-ready
  • Solito: shared navigation (Expo Router + Next.js Link), component sharing, not a full boilerplate
  • Expo Router + Next.js: experimental universal routing (same file routes web+native), 2026 maturing
  • Shared packages: all approaches use packages/ for shared types, UI components, API clients
  • What's actually shared: TypeScript types, tRPC routers, Zod schemas, some UI components
  • What's NOT shared: platform-specific components, navigation, native modules

T3 Turbo: The Production Standard

# Clone the official T3 Turbo starter:
git clone https://github.com/t3-oss/create-t3-turbo.git my-app
cd my-app && pnpm install

Project Structure

my-app/
  apps/
    nextjs/          ← Web app (Next.js 15)
      src/
        app/
        trpc/
    expo/            ← Mobile app (Expo SDK 51)
      src/
        app/         ← Expo Router file-based routing
  packages/
    api/             ← Shared tRPC routers (THE KEY PACKAGE)
      src/
        root.ts      ← Root tRPC router
        routers/
          post.ts    ← Shared procedure
    auth/            ← NextAuth config + Expo auth helpers
    db/              ← Prisma schema + client
    ui/              ← Shared React Native + web components (limited)
    validators/      ← Shared Zod schemas

Shared tRPC Router (The Magic)

// packages/api/src/routers/post.ts — used by BOTH apps:
import { z } from 'zod';
import { createTRPCRouter, protectedProcedure, publicProcedure } from '../trpc';

export const postRouter = createTRPCRouter({
  all: publicProcedure.query(({ ctx }) => {
    return ctx.db.post.findMany({ orderBy: { id: 'desc' } });
  }),
  byId: publicProcedure
    .input(z.object({ id: z.string() }))
    .query(({ ctx, input }) => {
      return ctx.db.post.findFirst({ where: { id: input.id } });
    }),
  create: protectedProcedure
    .input(z.object({ title: z.string().min(1) }))
    .mutation(({ ctx, input }) => {
      return ctx.db.post.create({
        data: { title: input.title, authorId: ctx.session.user.id },
      });
    }),
});
// apps/nextjs/src/trpc/server.ts — web usage (server-side):
import { createCaller } from '@acme/api';
import { createTRPCContext } from '@acme/api';

export const api = createCaller(await createTRPCContext({ headers: new Headers() }));

// In a Server Component:
const posts = await api.post.all();
// apps/expo/src/utils/api.tsx — mobile usage (React Native):
import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from '@acme/api';

export const api = createTRPCReact<AppRouter>();

// In an Expo component:
export function PostList() {
  const { data: posts } = api.post.all.useQuery();
  return (
    <FlatList
      data={posts}
      renderItem={({ item }) => <Text>{item.title}</Text>}
      keyExtractor={(item) => item.id}
    />
  );
}

The same AppRouter type powers both web and mobile. Type errors in the router break both apps simultaneously.

Running the Monorepo

# Run all apps concurrently:
pnpm dev

# This starts:
# → Next.js dev server on :3000
# → Expo Metro bundler on :8081 (for iOS simulator / Android emulator)

# Run individually:
pnpm --filter @acme/nextjs dev
pnpm --filter @acme/expo start

Solito: Shared Navigation Components

Solito is a library (not a boilerplate) that lets you write navigation code once for both Expo Router and Next.js.

# Add to T3 Turbo or any monorepo:
npm install solito
// Shared link component (works on web + mobile):
import { Link } from 'solito/link';

export function PostCard({ id, title }: { id: string; title: string }) {
  return (
    <Link href={`/post/${id}`}>
      {/* On Next.js: renders <a href="/post/123">   */}
      {/* On Expo:   uses Expo Router's <Link>        */}
      <Text>{title}</Text>
    </Link>
  );
}
// Shared hook for typed navigation:
import { useRouter } from 'solito/navigation';

export function CreatePostButton() {
  const router = useRouter();
  
  return (
    <Pressable onPress={() => router.push('/create-post')}>
      {/* Works on both Expo Router and Next.js App Router */}
      <Text>Create Post</Text>
    </Pressable>
  );
}

Solito Starter (with Next.js + Expo)

npx create-solito-app@latest my-app
# Choose: with-next-and-expo-router template

What You Can and Can't Share

✅ CAN share between Next.js + Expo:
  - TypeScript types and interfaces
  - tRPC routers and procedures
  - Zod validation schemas
  - Business logic (pure functions)
  - API client configuration
  - Constants and configuration
  - Some UI components (with platform checks)

⚠️  PARTIALLY shareable (needs platform branches):
  - Form components (React Hook Form works on both)
  - Animation (Reanimated vs Framer Motion)
  - Styling (NativeWind for React Native Tailwind)

❌ CANNOT share:
  - Native modules (camera, GPS, etc.)
  - CSS/Tailwind styles (needs NativeWind on mobile)
  - HTML-specific elements (div, span, etc.)
  - Next.js Server Components (client-only on mobile)
  - Platform-specific navigation UI

NativeWind for Shared Styling

// NativeWind lets you use Tailwind classes on React Native:
import { View, Text } from 'react-native';

// Works on both web (Tailwind) and mobile (NativeWind):
export function Card({ title }: { title: string }) {
  return (
    <View className="rounded-lg bg-white p-4 shadow-sm">
      <Text className="text-lg font-semibold text-gray-900">{title}</Text>
    </View>
  );
}

Comparison Table

T3 TurboSolito StarterExpo Router + Next.js (native)
MaturityHighMediumExperimental
tRPC sharing✅ Core feature✅ Can addManual
Navigation sharingManual✅ Via Solito✅ Universal routes
UI sharingLimitedVia NativeWindVia NativeWind
AuthNextAuth + Expo AuthManualManual
CommunityLargeMediumGrowing
Production useMany appsSeveral appsEarly adopters

Decision Guide

Start with T3 Turbo if:
  → Building web + mobile product from day one
  → tRPC type-safety across platforms is top priority
  → Team knows T3 Stack already
  → Production-ready today

Add Solito if:
  → Want to share navigation components (Links, routes)
  → Building content/navigation-heavy app
  → Have existing monorepo, adding Solito is additive

Wait for Expo Router universal if:
  → Want same file = same route on web + native
  → Okay with experimental trade-offs
  → 2026/2027 timeframe

Keep separate codebases if:
  → Web and mobile are very different products
  → Different teams own each platform
  → Shared code overhead not worth it

Find Expo + Next.js monorepo boilerplates at StarterPick.

Comments