Webの技術でiOS・Androidを両方作る
Expoは、React Nativeでのモバイル開発をコマンド1つで始められるように整えたツールキットです。画面遷移にはExpo Router、通知やカメラなどの端末機能には公式のライブラリ群が用意されており、ネイティブのコードをほとんど書かずにアプリの骨格を組み立てられます。ホットリロードで変更をすぐ端末に反映できるため、AIエージェントに実装させてはその場で確認する、というループを短く回せます。
app.config.tsとeas.jsonが土台になる
アプリのID、名前、EASのプロジェクトIDはapp.config.tsにまとめます。
import { ExpoConfig } from "expo/config";
const config: ExpoConfig = {
name: "my-app",
slug: "my-app",
version: "1.0.0",
ios: {
bundleIdentifier: "com.example.myapp",
},
android: {
package: "com.example.myapp",
},
extra: {
eas: {
projectId: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
},
},
};
export default config;ビルドの種類ごとの設定はeas.jsonにプロファイルとして分けます。
{
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"distribution": "internal"
},
"production": {
"autoIncrement": true
}
}
}手元での動作確認はdevelopment、社内配布はpreview、ストア提出はproductionというように使い分けます。
EAS Build/Updateには無料枠がある
アプリストアに提出できる形式(.ipa/.apk)へのビルドは、EAS Buildというクラウドサービスで行います。無料プランでもAndroid・iOSそれぞれ月15回までのビルドが含まれ、個人開発の検証段階であれば十分な回数です。
eas build --profile production --platform allもう一つの仕組みがEAS Updateです。JavaScript側の変更(画面の修正やロジックの変更)は、ストア審査を経ずに配信できます。
eas update --branch production --message "fix login bug"無料プランでは月間アクティブユーザー1,000人までが対象です。ただし、ネイティブコードや権限まわりの変更はEAS Updateの対象外で、通常のストア審査が必要になります。
ストア公開には年会費・登録料がかかる
アプリをストアで公開するには、開発者登録が必要です。Apple Developer Programは年99ドル、Google Playは買い切りで25ドルです。初年度は合わせて124ドル程度、2年目以降はAppleの年99ドルのみが継続してかかります。これはエンジンやフレームワークの費用とは別に発生する固定費として見込んでおきます。
バックエンドはGoのAPIに接続する
Expoで作るアプリ本体は画面と端末側の処理を担当し、データベースへは直接つながりません。この手引のGoのAPI(モバイルアプリのAPIにGoを選ぶ理由)で紹介している構成のように、認証・権限チェック・DB操作を行うAPIサーバーを別途用意し、ExpoアプリはHTTPSでそのAPIとだけ通信します。
const res = await fetch("https://api.example.com/v1/items", {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (!res.ok) {
throw new Error("failed to fetch items");
}
const items = await res.json();認証トークンは、ログイン時にGoのAPI側から発行してもらい、端末内のセキュアストレージ(expo-secure-store)に保存します。アプリのコードに接続情報やDBの認証情報を直接埋め込まないという、APIサーバーを間に置く理由そのものです。
プッシュ通知もAPI経由で送る
プッシュ通知は、端末側でExpoのプッシュトークンを取得し、それをGoのAPIへ登録しておく流れが基本です。
import * as Notifications from "expo-notifications";
const { status } = await Notifications.requestPermissionsAsync();
if (status !== "granted") {
return;
}
const token = (await Notifications.getExpoPushTokenAsync()).data;
await fetch("https://api.example.com/v1/devices", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ pushToken: token }),
});通知を送りたいタイミングになったら、GoのAPI側からExpoのプッシュ通知サービスへリクエストします。通知の送信元をアプリではなくAPIサーバーに寄せることで、誰にいつ何を送ったかの記録や、送信のレート制御もサーバー側で一元管理できます。
KPIもGoのAPIへ送って自分のDBに貯める
初回起動、登録完了、課金など、継続率や転換率を見るためのイベントも、専用のSaaSを導入せず、同じGoのAPIに送って自分のPostgreSQL(またはD1)に貯める方法があります。
async function trackEvent(name: string, props?: Record<string, unknown>) {
fetch("https://api.example.com/v1/events", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, props, ts: Date.now() }),
}).catch(() => {});
}
trackEvent("signup_completed");失敗しても画面の操作をブロックしないよう、送信は投げっぱなしにします。溜まったイベントはDB側でSQLを書いて、DAU・継続率・登録完了率などのKPIを自分で集計します。