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 Turbo | Solito Starter | Expo Router + Next.js (native) | |
|---|---|---|---|
| Maturity | High | Medium | Experimental |
| tRPC sharing | ✅ Core feature | ✅ Can add | Manual |
| Navigation sharing | Manual | ✅ Via Solito | ✅ Universal routes |
| UI sharing | Limited | Via NativeWind | Via NativeWind |
| Auth | NextAuth + Expo Auth | Manual | Manual |
| Community | Large | Medium | Growing |
| Production use | Many apps | Several apps | Early 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.