---
name: expo-router-eas-hosting-universal
description: "Build universal RN + web apps with Expo Router file-based typed routes, +api.ts routes, and EAS Hosting deploys. Production Expo Router (SDK 54, New Architecture): file-based routing with Stack/Tabs/Drawer layouts, typed routes and Href types, universal deep linking with scheme + associated domains. Server-side +api.ts API routes, server functions, and React Server Components with the metro web bundler. Deploy the backend and static web export to EAS Hosting with eas deploy, custom domains, and environment-aware origins."
license: MIT
---

# Expo Router Universal

A working setup for building one codebase that ships to iOS, Android, and the web using Expo Router's file-based routing (Expo SDK 54, New Architecture). It wires up typed routes, `Stack`/`Tabs`/`Drawer` layouts, server `+api.ts` endpoints, and deployment to EAS Hosting — aimed at developers who want a single React Native + web app without maintaining separate routing or a separate backend.

## When to use it

- You're starting a universal app and want routes, deep links, and an API layer that work on native and web from day one.
- You have an existing Expo app on `expo-router` and want to add server endpoints (`+api.ts`) instead of standing up a separate Node service.
- You need verified universal links — `https://` associated domains on iOS/Android plus a custom `scheme://` — and want the `app.json` and route mapping done correctly.
- You want to deploy the web/server output to EAS Hosting and get a preview URL per branch.

## How it works

Routing is driven by the `app/` directory. A file like `app/posts/[id].tsx` becomes a route, and `_layout.tsx` files declare navigators via `<Stack>`, `<Tabs>`, or `<Drawer>`. Enable `experiments.typedRoutes` in `app.json` so `<Link href>` and `router.push()` are checked against generated `Href` types. Server routes live next to screens as `app/api/hello+api.ts`, exporting `GET`/`POST` functions that return a standard `Response`. These run on Metro's web/server bundler, which also backs React Server Components and server functions. Deep linking is configured through `scheme` and `associatedDomains` / intent filters in `app.json`. Deploy the exported site with `eas deploy`.

## Usage

```bash
curl -fsSL --create-dirs -o ~/.claude/skills/expo-router-eas-hosting-universal/SKILL.md https://claudeskil.com/skills/expo-router-eas-hosting-universal/SKILL.md

# scaffold + run
npx create-expo-app@latest my-app
npx expo start            # native + web from one server

# a server route: app/api/ping+api.ts
export function GET(req: Request) {
  return Response.json({ ok: true, ts: Date.now() });
}

# export web/server output and deploy
npx expo export -p web
eas deploy
```

## Tips & gotchas

- `+api.ts` routes only run on platforms with a server target (web/EAS Hosting). They are not bundled into native binaries — don't expect them to answer requests offline on a device.
- Typed routes regenerate on `expo start`; if `Href` errors look stale, restart the dev server to refresh `.expo/types`.
- Set `web.output` to `"server"` in `app.json` when using `+api.ts` or RSC — the default `"static"` won't serve dynamic endpoints.
- Use `expo-router`'s `Link` with `prefetch` carefully on native; it behaves differently than web `<a>` prefetch.

## FAQ

**Can I call a `+api.ts` route from native?** Yes — use `fetch` against your deployed origin (read it from `EXPO_PUBLIC_*` env). The route itself runs server-side, not in the app bundle.

**Do I need a custom dev client?** New Architecture and most config plugins require a dev build (`eas build --profile development`); Expo Go won't cover native modules added via plugins.

**How are env vars handled on server routes?** Non-`EXPO_PUBLIC_` secrets stay server-only and are available inside `+api.ts`; only `EXPO_PUBLIC_`-prefixed vars reach the client bundle.
