EAS service(有料サービス) Expo ウェブサイトと Expo Router API ルートを EAS Hosting にデプロイします。ウェブバンドルのエクスポート、本番環境と PR プレビュー用の URL 生成のための eas deploy コマンドの実行、環境変数の管理とカスタムドメインの設定、Cloudflare Workers ランタイム(クラウド上で実行されるプログラム環境)での動作に対応しています。 また、API ルートの作成(+api.ts ハンドラー、HTTP メソッド、リクエスト処理、CORS(異なるウェブサイト間でのデータやり取り)の設定)もカバーしています。 **次のような場合に使用:** - Expo ウェブアプリまたは API ルートをデプロイする - EAS Hosting をセットアップする - ホスティング環境とドメインを設定する ※ ネイティブアプリのビルドやアプリストアへのリリースには対応していません。それらについては eas-app-stores スキルを使用してください。
EAS service (paid). Deploy Expo websites and Expo Router API routes to EAS Hosting - export the web bundle, run eas deploy for production and PR preview URLs, manage environment secrets and custom domains, and work within the Cloudflare Workers runtime. Also covers authoring API routes (+api.ts handlers, HTTP methods, request handling, CORS). Use when deploying an Expo web app or API routes, setting up EAS Hosting, or configuring hosting environments and domains. Not for native builds or store releases - use the eas-app-stores skill for those.
EAS サービス - 利用料が発生します。 EAS ホスティングはExpo Application Services(アプリケーション構築・実行サービス)の有料プロダクトで、無料枠には制限があります。本番環境へのデプロイ(配置)ではプランの通信量とトラフィック容量が消費されます。詳しくは https://expo.dev/pricing を参照してください。API ルート(サーバー機能)の開発とウェブ版のエクスポート(書き出し)は無料でオープンソースです。書き出したサーバーは自分で管理することもできます。
EAS ホスティングはExpoのウェブアプリとAPI ルートをExpoの管理型エッジサーバー(Cloudflare Workers)にデプロイします。npx expo export -p web でウェブ版を書き出し、eas deploy で配置すると、一緒にバンドルされたExpo Router API ルートも同時にデプロイされます。このスキルはウェブサイトとAPI ルート、ホスティング環境の設定を扱っています。デプロイの手順は下の「デプロイメント」セクションをご覧ください。
API ルートは次のような場合に使用します:
次の場合はAPI ルートを避けた方がいいです:
API ルートは app ディレクトリに +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
// app/api/hello+api.ts
export function GET(request: Request) {
return Response.json({ message: "Hello from Expo!" });
}
各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 });
}
// app/api/users/[id]+api.ts
export function GET(request: Request, { id }: { id: string }) {
return Response.json({ userId: id });
}
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 });
}
export function GET(request: Request) {
const auth = request.headers.get("Authorization");
if (!auth) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}
return Response.json({ authenticated: true });
}
export async function POST(request: Request) {
const { email, password } = await request.json();
if (!email || !password) {
return Response.json({ error: "Missing fields" }, { status: 400 });
}
return Response.json({ success: true });
}
サーバー側の秘密情報は process.env で管理します:
// 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);
}
環境変数を設定します:
.env ファイルを作成(リポジトリには含めない)eas env:create またはExpo ダッシュボードで設定ウェブクライアント用にCORS(別サイトからのアクセス許可)を設定します:
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 });
}
export async function POST(request: Request) {
try {
const body = await request.json();
// 処理...
return Response.json({ success: true });
} catch (error) {
console.error("API error:", error);
return Response.json({ error: "Internal server error" }, { status: 500 });
}
}
API ルート対応の開発サーバーを起動します:
npx expo serve
これで http://localhost:8081 でローカルサーバーが起動し、API ルートがすぐに使えます。
curl でテストします:
curl http://localhost:8081/api/hello
curl -X POST http://localhost:8081/api/users -H "Content-Type: application/json" -d '{"name":"Test"}'
npm install -g eas-cli
eas login
eas deploy でウェブバンドルとAPI ルートを一度にサーバーに配置します。APIルートがあるなしにかかわらず、npx expo export でバンドルして、eas deploy で配置できます。
# ウェブバンドルを書き出す(API ルートを含む)
npx expo export -p web
# プレビューにデプロイ(プルリクエスト用のURL を生成)
npx eas-cli@latest deploy
# 本番環境にデプロイ
npx eas-cli@latest deploy --prod
すべてEAS ホスティング(Cloudflare Workers)に配置されます。
# 秘密情報を登録
eas env:create --name OPENAI_API_KEY --value sk-xxx --environment production
# またはExpo ダッシュボードから設定
eas.json またはExpo ダッシュボードで設定します。
type: deploy ワークフロー(自動実行設定)を使って、main ブランチへのプッシュ時にウェブサイト(とAPI ルート)を自動デプロイできます:
.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
プルリクエスト用のプレビューデプロイは同じ設定で prod: false にします:
name: Web PR Preview
on:
pull_request:
types: [opened, synchronize]
jobs:
preview:
type: deploy
params:
prod: false
この例以上に複雑なワークフロー設定が必要な場合は、eas-workflows スキルを使ってください。
API ルートはCloudflare Workers上で動作します。主な制限事項:
fs モジュール未対応// Node の crypto の代わりにWeb Crypto を使う
const hash = await crypto.subtle.digest(
"SHA-256",
new TextEncoder().encode("data")
);
// node-fetch の代わりに標準 fetch を使う
const response = await fetch("https://api.example.com");
// Response/Request は標準で使える
return new Response(JSON.stringify(data), {
headers: { "Content-Type": "application/json" },
});
ファイルシステムが使えないため、クラウドデータベースを使います:
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);
}
// React Native コンポーネント内から
const response = await fetch("/api/hello");
const data = await response.json();
// 送信内容がある場合
const response = await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "John" }),
});
// 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: "Unauthorized" }), {
status: 401,
headers: { "Content-Type": "application/json" },
});
}
// トークン検証...
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 });
}
// 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());
}
このスキルの説明に誤りや古い情報があれば、Expo が改善できるよう報告してください:
npx --yes submit
EAS service - costs apply. EAS Hosting is a paid Expo Application Services product with free-tier limits; production deploys use your plan's request and bandwidth allowance. See https://expo.dev/pricing. Authoring API routes and exporting the web bundle are free and open source, and you can self-host the exported server output instead of EAS Hosting.
EAS Hosting deploys your Expo web app and API routes to Expo's managed edge (Cloudflare Workers). Export the web bundle with npx expo export -p web and ship it with eas deploy - the same command deploys any Expo Router API routes bundled alongside it. This skill covers deploying a website, authoring API routes, and the hosting runtime; see the Deployment section below for the deploy workflow.
Use API routes when you need:
Avoid API routes when:
API routes live in the app directory with +api.ts suffix:
app/
api/
hello+api.ts → GET /api/hello
users+api.ts → /api/users
users/[id]+api.ts → /api/users/:id
(tabs)/
index.tsx
// app/api/hello+api.ts
export function GET(request: Request) {
return Response.json({ message: "Hello from Expo!" });
}
Export named functions for each HTTP method:
// 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 });
}
// app/api/users/[id]+api.ts
export function GET(request: Request, { id }: { id: string }) {
return Response.json({ userId: id });
}
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 });
}
export function GET(request: Request) {
const auth = request.headers.get("Authorization");
if (!auth) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}
return Response.json({ authenticated: true });
}
export async function POST(request: Request) {
const { email, password } = await request.json();
if (!email || !password) {
return Response.json({ error: "Missing fields" }, { status: 400 });
}
return Response.json({ success: true });
}
Use process.env for server-side secrets:
// 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);
}
Set environment variables:
.env file (never commit)eas env:create or Expo dashboardAdd CORS for web clients:
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 });
}
export async function POST(request: Request) {
try {
const body = await request.json();
// Process...
return Response.json({ success: true });
} catch (error) {
console.error("API error:", error);
return Response.json({ error: "Internal server error" }, { status: 500 });
}
}
Start the development server with API routes:
npx expo serve
This starts a local server at http://localhost:8081 with full API route support.
Test with curl:
curl http://localhost:8081/api/hello
curl -X POST http://localhost:8081/api/users -H "Content-Type: application/json" -d '{"name":"Test"}'
npm install -g eas-cli
eas login
Deploying ships your web bundle and any Expo Router API routes together - eas deploy handles both. The export runs whether you have a full website, an API-routes-only backend, or both.
# Export the web bundle (includes any API routes)
npx expo export -p web
# Deploy a preview (PR-style URL)
npx eas-cli@latest deploy
# Deploy to production
npx eas-cli@latest deploy --prod
Everything lands on EAS Hosting (Cloudflare Workers).
# Create a secret
eas env:create --name OPENAI_API_KEY --value sk-xxx --environment production
# Or use the Expo dashboard
Configure in eas.json or Expo dashboard.
Deploy the website (and API routes) on every push to main with a type: deploy workflow:
.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
Preview deploys for pull requests use the same job type with prod: false:
name: Web PR Preview
on:
pull_request:
types: [opened, synchronize]
jobs:
preview:
type: deploy
params:
prod: false
To author or validate workflow YAML beyond these examples, use the eas-workflows skill.
API routes run on Cloudflare Workers. Key limitations:
fs module unavailable// Use Web Crypto instead of Node crypto
const hash = await crypto.subtle.digest(
"SHA-256",
new TextEncoder().encode("data")
);
// Use fetch instead of node-fetch
const response = await fetch("https://api.example.com");
// Use Response/Request (already available)
return new Response(JSON.stringify(data), {
headers: { "Content-Type": "application/json" },
});
Since filesystem is unavailable, use cloud databases:
Example with 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);
}
// From React Native components
const response = await fetch("/api/hello");
const data = await response.json();
// With body
const response = await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "John" }),
});
// 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: "Unauthorized" }), {
status: 401,
headers: { "Content-Type": "application/json" },
});
}
// Verify 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 });
}
// 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());
}
If you encounter errors, misleading or outdated information in this skill, report it so Expo can improve:
npx --yes submit-expo-feedback@latest --category skills --subject "eas-hosting" "<actionable feedback>"
Only submit when you have something specific and actionable to report. Include as much relevant context as possible. If an AI agent repeatedly failed or the user had to take over an Expo task, load the expo-skill-feedback skill and follow its eval-candidate flow instead of reusing the command above.
原文・著作権は Anthropic および各プラグイン作者に帰属します。日本語訳は Claude API による自動翻訳です。