• Projects
  • Service
  • About
  • branding.bz
  • Podcast
  • Tips
  • FAQ
  • Recruit
  • Download
  • Contact
  • branding.bz(ブランド構築SaaS)
  • DESIGN NOW(デザインメディア)
  • X
  • LinkedIn
  • Spotify
  • Facebook

213-0011 神奈川県川崎市高津区久本3-6-7-303

© 2026 ID INC. All rights reserved

claude-skills/スキル
SKILLOfficialdevelopment

eas-hosting

プラグイン
expo
ライセンス
MIT
ソース
GitHub で見る ↗
説明

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.

ユースケース
  • Expoウェブアプリをデプロイする
  • APIルートをデプロイする
  • EASHostingをセットアップする
  • ホスティング環境を設定する
  • カスタムドメインを設定する
本文(日本語訳)

EAS ホスティング

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キー、データベース認証情報、トークンなど、クライアント側に絶対に露出させてはいけない情報
  • データベース操作 — 外部に公開すべきでないデータベースクエリの実行
  • 外部サービスのプロキシ(中継) — OpenAIやStripeなど外部APIのキーを隠して呼び出す
  • サーバー側の検証 — データベース書き込み前の入力チェック
  • ウェブフック受け取り — StripeやGitHubなどのサービスからのコールバック(通知)受信
  • アクセス制限 — サーバーレベルでのリクエスト数制御
  • 重い計算処理 — モバイル端末では遅くなる処理をサーバーで実行

API ルートを使わない場面

次の場合はAPI ルートを避けた方がいいです:

  • データが既に公開情報 — 公開APIに直接接続する方が簡単
  • 秘密情報が不要 — 静的データやクライアント側で安全に処理できる操作
  • リアルタイム更新が必要 — WebSocketやSupabase Realtimeなどのサービスを使う
  • 単純なデータ操作(CRUD) — Firebase、Supabase、Convexなどの管理型バックエンドを検討
  • ファイルアップロード — S3の署名付きURL、Cloudflare R2など直接ストレージ保存を使う
  • 認証だけが目的 — Clerk、Auth0、Firebase Authなどの専用サービスを使う

ファイル構成

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

シンプルなAPI ルート

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

HTTP メソッド

各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 });
}

JSON 本体

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 ホスティング: eas env:create またはExpo ダッシュボードで設定

CORS ヘッダー

ウェブクライアント用に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"}'

EAS ホスティングへのデプロイ

準備

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 ダッシュボードで設定します。

EAS ワークフロー(自動デプロイ)で自動化

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 スキルを使ってください。

EAS ホスティング実行環境(Cloudflare Workers)

API ルートはCloudflare Workers上で動作します。主な制限事項:

使えないまたは制限があるAPI

  • Node.js ファイルシステムがない — fs モジュール未対応
  • ネイティブNode モジュール未対応 — ウェブAPI か代替ライブラリを使う
  • 実行時間制限 — CPU集約的なタスクは30秒が上限
  • 常時接続不可 — WebSocket は専用機能(Durable Objects)が必要
  • fetch は使える — HTTP リクエストは標準的な fetch で可能

かわりにウェブAPIを使う

// 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" },
});

データベース選択肢

ファイルシステムが使えないため、クラウドデータベースを使います:

  • Cloudflare D1 — エッジサーバー上のSQLite(軽量データベース)
  • Turso — 分散SQLite
  • PlanetScale — サーバーレスMySQL
  • Supabase — REST API付きPostgres(高機能データベース)
  • Neon — サーバーレスPostgres

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);
}

クライアントからAPI ルートを呼び出す

// 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 });
}

外部API のプロキシ

// 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());
}

ルール

  • 絶対に API キーや秘密情報をクライアント側コードに埋め込まない
  • 必ず ユーザー入力を検証・サニタイズ(安全化)する
  • 適切なHTTP ステータスコードを使う(200, 201, 400, 401, 404, 500)
  • try/catch でエラーを適切に処理する
  • API ルートは1つの役割に絞る
  • TypeScript を使って型安全性を確保する
  • エラーはサーバー側でログに記録してデバッグに備える

フィードバック送信

このスキルの説明に誤りや古い情報があれば、Expo が改善できるよう報告してください:

npx --yes submit
原文(English)を表示

EAS Hosting

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.

When to Use API Routes

Use API routes when you need:

  • Server-side secrets — API keys, database credentials, or tokens that must never reach the client
  • Database operations — Direct database queries that shouldn't be exposed
  • Third-party API proxies — Hide API keys when calling external services (OpenAI, Stripe, etc.)
  • Server-side validation — Validate data before database writes
  • Webhook endpoints — Receive callbacks from services like Stripe or GitHub
  • Rate limiting — Control access at the server level
  • Heavy computation — Offload processing that would be slow on mobile

When NOT to Use API Routes

Avoid API routes when:

  • Data is already public — Use direct fetch to public APIs instead
  • No secrets required — Static data or client-safe operations
  • Real-time updates needed — Use WebSockets or services like Supabase Realtime
  • Simple CRUD — Consider Firebase, Supabase, or Convex for managed backends
  • File uploads — Use direct-to-storage uploads (S3 presigned URLs, Cloudflare R2)
  • Authentication only — Use Clerk, Auth0, or Firebase Auth instead

File Structure

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

Basic API Route

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

HTTP Methods

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 });
}

Dynamic Routes

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

Request Handling

Query Parameters

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 });
}

Headers

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 });
}

JSON Body

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 });
}

Environment Variables

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:

  • Local: Create .env file (never commit)
  • EAS Hosting: Use eas env:create or Expo dashboard

CORS Headers

Add 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 });
}

Error Handling

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 });
  }
}

Testing Locally

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"}'

Deployment to EAS Hosting

Prerequisites

npm install -g eas-cli
eas login

Deploy

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).

Environment Variables for Production

# Create a secret
eas env:create --name OPENAI_API_KEY --value sk-xxx --environment production

# Or use the Expo dashboard

Custom Domain

Configure in eas.json or Expo dashboard.

Automate with EAS Workflows

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.

EAS Hosting Runtime (Cloudflare Workers)

API routes run on Cloudflare Workers. Key limitations:

Missing/Limited APIs

  • No Node.js filesystem — fs module unavailable
  • No native Node modules — Use Web APIs or polyfills
  • Limited execution time — 30 second timeout for CPU-intensive tasks
  • No persistent connections — WebSockets require Durable Objects
  • fetch is available — Use standard fetch for HTTP requests

Use Web APIs Instead

// 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" },
});

Database Options

Since filesystem is unavailable, use cloud databases:

  • Cloudflare D1 — SQLite at the edge
  • Turso — Distributed SQLite
  • PlanetScale — Serverless MySQL
  • Supabase — Postgres with REST API
  • Neon — Serverless Postgres

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);
}

Calling API Routes from Client

// 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" }),
});

Common Patterns

Authentication Middleware

// 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 });
}

Proxy External API

// 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());
}

Rules

  • NEVER expose API keys or secrets in client code
  • ALWAYS validate and sanitize user input
  • Use proper HTTP status codes (200, 201, 400, 401, 404, 500)
  • Handle errors gracefully with try/catch
  • Keep API routes focused — one responsibility per endpoint
  • Use TypeScript for type safety
  • Log errors server-side for debugging

Submitting Feedback

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 による自動翻訳です。