Communitygithub.com

expo/skills

Despliega sitios web de Expo y rutas API de Expo Router en EAS Hosting: exporta el bundle web, ejecuta eas deploy para producción y vistas previas de PR, gestiona secretos de entorno y dominios personalizados, y trabaja dentro del runtime de Cloudflare Workers. También cubre la creación de rutas API (manejadores +api.ts, métodos HTTP, manejo de solicitudes, CORS). Úsalo al desplegar una app web de Expo o rutas API, configurar EAS Hosting o entornos de hosting y dominios. No es para compilaciones nativas o lanzamientos en tiendas; usa la skill eas-app-stores para esos casos.

¿Qué es skills?

Esta skill te permite desplegar aplicaciones web de Expo y rutas API de Expo Router en EAS Hosting, el servicio de hosting administrado de Expo basado en Cloudflare Workers. Cubre todo el flujo: exportar el bundle web con `npx expo export -p web`, desplegar con `eas deploy` (tanto para producción como para vistas previas de PR), gestionar variables de entorno con `eas env:create`, configurar dominios personalizados y automatizar despliegues mediante flujos de trabajo EAS Workflows. También incluye una guía completa para crear rutas API usando el sufijo `+api.ts`, con soporte para métodos HTTP (GET, POST, PUT, DELETE), rutas dinámicas, manejo de parámetros de consulta, cabeceras, body JSON, CORS, autenticación y proxy de APIs externas. Explica las limitaciones del runtime de Cloudflare Workers (sin sistema de archivos, sin módulos nativos de Node.js, timeout de 30 segundos) y cómo usar Web APIs y bases de datos en la nube (D1, Turso, PlanetScale, Supabase, Neon) en su lugar. Es ideal tanto para backend completo como para complementar una app web de Expo, y no aplica para compilaciones nativas o lanzamientos en tiendas.

Compatible con~Claude Code✓Codex CLI~Cursor
npx skills add https://github.com/expo/skills/tree/HEAD/plugins/expo/skills/eas-hosting

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

EAS Hosting

Servicio EAS: se aplican costos. EAS Hosting es un producto pago de Expo Application Services con límites de nivel gratuito; los despliegues de producción consumen la cuota de solicitudes y ancho de banda de tu plan. Consulta https://expo.dev/pricing. La creación de rutas API y la exportación del bundle web son gratuitas y de código abierto, y puedes auto-alojar la salida del servidor exportado en lugar de usar EAS Hosting.

EAS Hosting despliega tu app web de Expo y rutas API en el edge administrado de Expo (Cloudflare Workers). Exporta el bundle web con npx expo export -p web y envíalo con eas deploy: el mismo comando despliega cualquier ruta API de Expo Router incluida. Esta skill cubre el despliegue de un sitio web, la creación de rutas API y el runtime de hosting; consulta la sección de Despliegue más abajo para el flujo de trabajo.

Cuándo Usar Rutas API

Usa rutas API cuando necesites:

  • Secretos del lado del servidor — Claves de API, credenciales de base de datos o tokens que nunca deben llegar al cliente
  • Operaciones de base de datos — Consultas directas a la base de datos que no deben exponerse
  • Proxies de APIs de terceros — Ocultar claves de API al llamar a servicios externos (OpenAI, Stripe, etc.)
  • Validación del lado del servidor — Validar datos antes de escribir en la base de datos
  • Endpoints de webhook — Recibir callbacks de servicios como Stripe o GitHub
  • Límite de tasa — Controlar el acceso a nivel de servidor
  • Cómputo pesado — Descargar procesamiento que sería lento en dispositivos móviles

Cuándo NO Usar Rutas API

Evita las rutas API cuando:

  • Los datos ya son públicos — Usa fetch directo a APIs públicas en su lugar
  • No se requieren secretos — Datos estáticos u operaciones seguras para el cliente
  • Se necesitan actualizaciones en tiempo real — Usa WebSockets o servicios como Supabase Realtime
  • CRUD simple — Considera Firebase, Supabase o Convex para backends administrados
  • Subida de archivos — Usa subidas directas a almacenamiento (URLs prefirmadas de S3, Cloudflare R2)
  • Solo autenticación — Usa Clerk, Auth0 o Firebase Auth en su lugar

Estructura de Archivos

Las rutas API viven en el directorio app con el sufijo +api.ts:

app/
  api/
    hello+api.ts          → GET /api/hello
    users+api.ts          → /api/users
    users/[id]+api.ts     → /api/users/:id
  (tabs)/
    index.tsx

Ruta API Básica

// app/api/hello+api.ts
export function GET(request: Request) {
  return Response.json({ message: "Hello from Expo!" });
}

Métodos HTTP

Exporta funciones con nombre para cada método HTTP:

// app/api/items+api.ts
export function GET(request: Request) {
  return Response.json({ items: [] });
}

export async function POST(request: Request) {
  const body = await request.json();
  return Response.json({ created: body }, { status: 201 });
}

export async function PUT(request: Request) {
  const body = await request.json();
  return Response.json({ updated: body });
}

export async function DELETE(request: Request) {
  return new Response(null, { status: 204 });
}

Rutas Dinámicas

// app/api/users/[id]+api.ts
export function GET(request: Request, { id }: { id: string }) {
  return Response.json({ userId: id });
}

Manejo de Solicitudes

Parámetros de Consulta

export function GET(request: Request) {
  const url = new URL(request.url);
  const page = url.searchParams.get("page") ?? "1";
  const limit = url.searchParams.get("limit") ?? "10";

  return Response.json({ page, limit });
}

Cabeceras

export function GET(request: Request) {
  const auth = request.headers.get("Authorization");

  if (!auth) {
    return Response.json({ error: "No autorizado" }, { status: 401 });
  }

  return Response.json({ authenticated: true });
}

Cuerpo JSON

export async function POST(request: Request) {
  const { email, password } = await request.json();

  if (!email || !password) {
    return Response.json({ error: "Campos faltantes" }, { status: 400 });
  }

  return Response.json({ success: true });
}

Variables de Entorno

Usa process.env para secretos del lado del servidor:

// app/api/ai+api.ts
export async function POST(request: Request) {
  const { prompt } = await request.json();

  const response = await fetch("https://api.openai.com/v1/chat/completions", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
    },
    body: JSON.stringify({
      model: "gpt-4",
      messages: [{ role: "user", content: prompt }],
    }),
  });

  const data = await response.json();
  return Response.json(data);
}

Configura las variables de entorno:

  • Local: Crea un archivo .env (nunca lo subas al repositorio)
  • EAS Hosting: Usa eas env:create o el panel de Expo

Cabeceras CORS

Añade CORS para clientes web:

const corsHeaders = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
  "Access-Control-Allow-Headers": "Content-Type, Authorization",
};

export function OPTIONS() {
  return new Response(null, { headers: corsHeaders });
}

export function GET() {
  return Response.json({ data: "value" }, { headers: corsHeaders });
}

Manejo de Errores

export async function POST(request: Request) {
  try {
    const body = await request.json();
    // Procesar...
    return Response.json({ success: true });
  } catch (error) {
    console.error("Error de API:", error);
    return Response.json({ error: "Error interno del servidor" }, { status: 500 });
  }
}

Pruebas Locales

Inicia el servidor de desarrollo con las rutas API:

npx expo serve

Esto inicia un servidor local en http://localhost:8081 con soporte completo para rutas API.

Prueba con curl:

curl http://localhost:8081/api/hello
curl -X POST http://localhost:8081/api/users -H "Content-Type: application/json" -d '{"name":"Test"}'

Despliegue en EAS Hosting

Prerrequisitos

npm install -g eas-cli
eas login

Desplegar

El despliegue envía tu bundle web y cualquier ruta API de Expo Router juntos: eas deploy maneja ambos. La exportación se ejecuta tanto si tienes un sitio web completo, un backend solo con rutas API, o ambos.

# Exportar el bundle web (incluye cualquier ruta API)
npx expo export -p web

# Desplegar una vista previa (URL estilo PR)
npx eas-cli@latest deploy

# Desplegar a producción
npx eas-cli@latest deploy --prod

Todo termina en EAS Hosting (Cloudflare Workers).

Variables de Entorno para Producción

# Crear un secreto
eas env:create --name OPENAI_API_KEY --value sk-xxx --environment production

# O usar el panel de Expo

Dominio Personalizado

Configúralo en eas.json o en el panel de Expo.

Automatizar con EAS Workflows

Despliega el sitio web (y las rutas API) en cada push a main con un flujo de trabajo type: deploy:

.eas/workflows/deploy.yml

name: Deploy

on:
  push:
    branches:
      - main

# https://docs.expo.dev/eas/workflows/syntax/#deploy
jobs:
  deploy_web:
    type: deploy
    params:
      prod: true

Los despliegues de vista previa para pull requests usan el mismo tipo de trabajo con prod: false:

name: Web PR Preview

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  preview:
    type: deploy
    params:
      prod: false

Para crear o validar YAML de flujos de trabajo más allá de estos ejemplos, usa la skill eas-workflows.

Runtime de EAS Hosting (Cloudflare Workers)

Las rutas API se ejecutan en Cloudflare Workers. Limitaciones clave:

APIs Faltantes o Limitadas

  • Sin sistema de archivos de Node.js — El módulo fs no está disponible
  • Sin módulos nativos de Node — Usa Web APIs o polyfills
  • Tiempo de ejecución limitado — 30 segundos de tiempo de espera para tareas intensivas en CPU
  • Sin conexiones persistentes — WebSockets requieren Durable Objects
  • fetch está disponible — Usa fetch estándar para solicitudes HTTP

Usa Web APIs en su Lugar

// Usa Web Crypto en lugar de crypto de Node
const hash = await crypto.subtle.digest(
  "SHA-256",
  new TextEncoder().encode("data")
);

// Usa fetch en lugar de node-fetch
const response = await fetch("https://api.example.com");

// Usa Response/Request (ya disponibles)
return new Response(JSON.stringify(data), {
  headers: { "Content-Type": "application/json" },
});

Opciones de Base de Datos

Dado que el sistema de archivos no está disponible, usa bases de datos en la nube:

  • Cloudflare D1 — SQLite en el edge
  • Turso — SQLite distribuido
  • PlanetScale — MySQL sin servidor
  • Supabase — Postgres con API REST
  • Neon — Postgres sin servidor

Ejemplo con Turso:

// app/api/users+api.ts
import { createClient } from "@libsql/client/web";

const db = createClient({
  url: process.env.TURSO_URL!,
  authToken: process.env.TURSO_AUTH_TOKEN!,
});

export async function GET() {
  const result = await db.execute("SELECT * FROM users");
  return Response.json(result.rows);
}

Llamar a Rutas API desde el Cliente

// Desde componentes React Native
const response = await fetch("/api/hello");
const data = await response.json();

// Con cuerpo
const response = await fetch("/api/users", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "John" }),
});

Patrones Comunes

Middleware de Autenticación

// utils/auth.ts
export async function requireAuth(request: Request) {
  const token = request.headers.get("Authorization")?.replace("Bearer ", "");

  if (!token) {
    throw new Response(JSON.stringify({ error: "No autorizado" }), {
      status: 401,
      headers: { "Content-Type": "application/json" },
    });
  }

  // Verificar token...
  return { userId: "123" };
}

// app/api/protected+api.ts
import { requireAuth } from "../../utils/auth";

export async function GET(request: Request) {
  const { userId } = await requireAuth(request);
  return Response.json({ userId });
}

Proxy de API Externa

// app/api/weather+api.ts
export async function GET(request: Request) {
  const url = new URL(request.url);
  const city = url.searchParams.get("city");

  const response = await fetch(
    `https://api.weather.com/v1/current?city=${city}&key=${process.env.WEATHER_API_KEY}`
  );

  return Response.json(await response.json());
}

Reglas

  • NUNCA expongas claves de API o secretos en el código del cliente
  • SIEMPRE valida y sanitiza la entrada del usuario
  • Usa códigos de estado HTTP adecuados (200, 201, 400, 401, 404, 500)
  • Maneja errores con gracia usando try/catch
  • Mantén las rutas API enfocadas — una responsabilidad por endpoint
  • Usa TypeScript para seguridad de tipos
  • Registra errores del lado del servidor para depuración

Enviar Comentarios

Si encuentras errores, información engañosa o desactualizada en esta skill, repórtala para que Expo pueda mejorar:

npx --yes @expo/agent-cli@latest feedback --category skills --subject "eas-hosting" "<comentario procesable>"

Envía solo cuando tengas algo específico y procesable que informar. Incluye todo el contexto relevante posible. Si un agente falla repetidamente en una tarea de Expo o el usuario tiene que intervenir, envía un breve informe de la tarea, el resultado esperado y lo que salió mal usando --category evals y el paquete o comando de Expo relevante como --subject.

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

expo/add-app-clip

Add an iOS App Clip target to an Expo app. Use when the user mentions App Clip, AASA, apple-app-site-association, appclips, smart app banner, or wants to ship a lightweight iOS Clip invoked from a URL alongside their parent app.

expo/building-native-ui

Complete guide for building beautiful apps with Expo Router. Covers fundamentals, styling, components, navigation, animations, patterns, and native tabs.

expo/eas-update-insights

Check the health of published EAS Updates: crash rates, install/launch counts, unique users, payload size, and the split between embedded and OTA users per channel. Use when the user asks how an update is performing, whether a rollout is healthy, how many users are on the embedded build vs OTA, or wants to gate CI on update health.

expo/expo-api-routes

Guidelines for creating API routes in Expo Router with EAS Hosting

expo/expo-brownfield

Integrate Expo and React Native into an existing native iOS or Android app. Use when the user mentions brownfield, embedding React Native in a native app, AAR/XCFramework, or adding Expo to an existing Kotlin/Swift project. Covers both the isolated approach and the integrated approach.

expo/expo-cicd-workflows

Helps understand and write EAS workflow YAML files for Expo projects. Use this skill when the user asks about CI/CD or workflows in an Expo or EAS context, mentions .eas/workflows/, or wants help with EAS build pipelines or deployment automation.

expo/expo-deployment

Deploying Expo apps to iOS App Store, Android Play Store, web hosting, and API routes

expo/expo-dev-client

Build and distribute Expo development clients locally or via TestFlight

expo/expo-examples

Framework (OSS). Expo's official example projects - the expo/examples repo of ~70 `with-*` integrations (Stripe, Clerk, Supabase, OpenAI, maps, Reanimated, SQLite, Skia, NativeWind, and more). Use when integrating a third-party library or service into an existing Expo app and you want the canonical, version-matched pattern to adapt, or when scaffolding a new project from one with `npx create-expo --example`.

expo/expo-module

Guide for creating and writing Expo native modules and views using the Expo Modules API (Swift, Kotlin, TypeScript). Covers module definition DSL, native views, shared objects, config plugins, lifecycle hooks, autolinking, and type system. Use when building or modifying native modules for Expo.

expo/expo-observe

Use for anything related to EAS Observe — adding `expo-observe` to an Expo project (AppMetricsRoot/ObserveRoot HOC, markInteractive, the useObserve hook, and the Expo Router / React Navigation integrations for per-route metrics), querying via the EAS CLI (`eas observe:metrics-summary`, `observe:metrics`, `observe:routes`, `observe:events`, `observe:versions`), or interpreting the resulting metrics (cold/warm launch, TTR, TTI, navigation cold/warm TTR, update download, and the TTI frameRate params for triaging slow startups).

expo/expo-overview

Framework (OSS). Entry point and router for every Expo or EAS task. Load this skill first — before writing code and before choosing another expo-* / eas-* skill — when the request, PRD, or spec mentions Expo, EAS, Expo Go, or an expo-* package, or the project has an `expo` dependency in `package.json`. Within that gate it also covers app specs and designs to implement (tabs, stacks, maps, lists, navigation, building from a screenshot), and phrasings like 'implement a mobile app', 'make my app look native', 'add navigation', 'fetch some data', 'upgrade my SDK', 'add Expo to my existing native app', 'ship to the App Store', or 'I'm new to Expo, where do I start'. A fully specified request (SDK pinned, libraries named, layout given) still routes through here — the shared setup rules still apply. Do NOT load it when neither signal is present: a bare React Native project with no `expo` dependency is not Expo work. Detects the real goal, routes to the right expo-* / eas-* skill, and owns the shared setup rules.

expo/expo-router

Framework (OSS). Navigation and routing for Expo Router. Covers file-based routes, groups and dynamic routes, folder organization, Link with previews and context menus, native Stack, page titles, modals and form sheets, NativeTabs, headers and toolbars, and header search bars.

expo/expo-skill-eval

Evaluate Expo skills in this repo end-to-end - trigger accuracy, generated code quality, and runtime screenshots on iOS simulator and Android emulator via Expo Go (web optional). Use when the user wants to eval an Expo skill, test that a skill produces working code, benchmark a skill with device screenshots, or verify a skill's output renders correctly.

expo/expo-tailwind-setup

Set up Tailwind CSS v4 in Expo with react-native-css and NativeWind v5 for universal styling

expo/expo-ui

Framework (OSS). Build native UI with the @expo/ui package: real SwiftUI on iOS and Jetpack Compose on Android. Default to @expo/ui for sheets (BottomSheet), pickers, sliders, toggles, menus, and grouped-form sections — do NOT reach for Reanimated, @gorhom/bottom-sheet, or RN built-in Picker/Switch; use @expo/ui instead. Fall back to RN built-ins only when @expo/ui is missing the component. NOTE: @expo/ui List renders native grouped rows like an iOS Settings screen — it is NOT a virtualized list; use FlatList/FlashList for large datasets. Covers universal components (Host, Column, Row, Button, Text, List, BottomSheet, FieldGroup, Switch, Slider, Picker, Menu), drop-in replacements for RN community libraries, and platform-specific SwiftUI/Jetpack Compose trees. Not for Expo Router navigation, Reanimated, or data fetching.

expo/expo-ui-jetpack-compose

`@expo/ui/jetpack-compose` package lets you use Jetpack Compose Views and modifiers in your app.

expo/expo-ui-swiftui

`@expo/ui/swift-ui` package lets you use SwiftUI Views and modifiers in your app.

expo/expo-upgrade

Framework (OSS). Guidelines for upgrading Expo SDK versions and fixing dependency issues

expo/native-data-fetching

Use when implementing or debugging ANY network request, API call, or data fetching. Covers fetch API, React Query, SWR, error handling, caching, offline support, and Expo Router data loaders (`useLoaderData`).

Skills relacionados