【初心者】気分記録アプリ、公開への道②|Expo で React Native アプリを作り、iPhone 実機に表示するまで

今回はAIに助けてもらって、Expoのファイルの作成から表示までの流れを書いていこうと思います。

2026年8月、アプリ開発が初めての状態から Expo でプロジェクトを立ち上げ、自分の iPhone に画面を出すまでの記録です。

公式手順どおりにやったのに詰まった箇所が3つあったので、そこを重点的に書きます。

環境

OS :macOS

Node.js:v24.7.0

npm:11.5.1

エディタ:VS Code

確認用の実機:iPhone(iOS 15.1 以上)

そもそも Expo とは

React Native でアプリを作るとき、本来は Xcode(Mac 用の巨大な開発ソフト、数十GB) をインストールして、ビルドして、実機に転送して…という手順が必要です。

Expo はこれを不要にしてくれる仕組みです。

Xcodeとの比較などは下記記事をご参照下さい。

【初心者】気分記録アプリ、公開への道①|進め方とExpo Go
要件定義書をChatGPTと作ったが、Claude Codeに確認すると考えられていない点が10点以上あった。iOSのアプリなのか、Androidを考えたアプリなのか、学習コスト、個人情報の扱いなど。データベースは一度Firebaseを使っ…

そして Expo Go は、App Store で配布されている無料アプリです。

これが「コードを実行する容れ物」になります。

自分のアプリを App Store に出さなくても、Expo Go の中で動かして確認できる、というわけです。

プロジェクトを作る

ターミナルで、プロジェクトを置きたいフォルダに移動して:

npx create-expo-app@latest .

最後の `.` は「今いるフォルダに作る」という意味です。新しくフォルダを作りたい場合は `.` の代わりにフォルダ名を書きます。

これだけで、必要なファイル一式と 600 個以上のライブラリが自動で入ります。1〜2分かかります。

npx とは: 「インストールせずに1回だけ実行する」コマンドです。
create-expo-app はプロジェクト作成時にしか使わないので、パソコンに常駐させる必要がありません。

⚠️ 最大の落とし穴:SDK のバージョン

ここが一番ハマりました。この記事で一番伝えたいところです。

create-expo-app は、常にその時点の最新 SDK でプロジェクトを作ります。

私の場合は SDK 57 でした。

ところが、App Store で配布されている Expo Go は SDK 54 までしか対応していませんでした。

結果、QRコードを読み込むとこのエラーが出ます:

Project is incompatible with this version of Expo Go
The project you requested requires a newer version of Expo Go.
Download the latest version of Expo Go from the App Store.

「最新版をダウンロードしてください」と言われますが、App Store にはそれ以上新しいものが存在しません。 指示どおりにやっても永遠に解決しない状態です。

確認方法

Apple の公開 API で、App Store 上の Expo Go のバージョンを調べられます:

curl -s "https://itunes.apple.com/lookup?bundleId=host.exp.Exponent&country=jp" \
  | python3 -c "import sys,json; a=json.load(sys.stdin)['results'][0]; print(a['version'], a['minimumOsVersion'])"

私が実行したときの結果:

54.0.2 15.1

つまり Expo Go は SDK 54 対応、iOS 15.1 以上が必要、ということです。

対処法:プロジェクトを Expo Go に合わせる

# 1. expo 本体を目的の SDK に下げる
npm install expo@~54.0.0
# 2. 他のライブラリを SDK 54 に合わせて揃え直す
npx expo install --fix

npx expo install –fix がうまく通らない場合

node_modules と package-lock.json を消してからやり直すと通ります(どちらも自動で再生成されるので消して大丈夫です):

rm -rf node_modules package-lock.json
npm install

教訓

ライブラリを追加するときは npm install ではなく npx expo install を使う。

npx expo install は「今の SDK に合ったバージョン」を自動で選んでくれます。

npm install は常に最新を入れてしまうので、バージョンのズレが起きます。

最低限必要なファイルは3つだけ

たくさんファイルが作られますが、アプリが動くために本当に必要なのは3つです。

まず全体の流れを頭に入れると、なぜこの3つなのかが分かります。

ターミナルで npx expo start

Metro(開発サーバー)が app.json を読む

「マニフェスト」を作って待機

iPhone の Expo Go が QR 経由で受け取る

package.json の “main” を見てアプリの入口を決める

Expo Router が src/app/ のファイルを全部走査

ファイル名 = 画面のURL として自動で画面を作る

iPhone に表示される

ファイル役割ないとどうなる
package.json使うライブラリ一覧+アプリの入口指定起動しない
app.jsonアプリ名・SDK・アイコン・URLスキームExpo Go が何のアプリか分からない
src/app/ の中身画面そのもの真っ白

app.json の書き方

アプリの「名札」にあたるファイルです。

{
  "expo": {
    "name": "サンプル",
    "slug": "sample",
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./assets/images/icon.png",
    "scheme": "sample",
    "userInterfaceStyle": "automatic",
    "ios": {
      "supportsTablet": false
    },
    "plugins": ["expo-router"],
    "experiments": {
      "typedRoutes": true
    }
  }
}
キー意味
nameiPhone のホーム画面に出るアプリ名。日本語OK
slugURL用の識別子。英数字とハイフンのみ
schemeアプリ独自の URL。通知をタップしてアプリを開くときなどに使う
orientationportrait で縦画面固定
userInterfaceStyleautomatic で iPhone のダークモード設定に追従
plugins使う機能の登録

⚠️ ハマりポイント:scheme は数字で始められない

create-expo-app はフォルダ名から scheme を自動生成します。

URL スキームは数字で始められません(`http://` の `h` のように、必ず英字始まり)。このままだと、あとで「通知をタップしてアプリを開く」機能が動きません。

sample のように英字始まりに直しておきましょう。作った直後は気づかず、あとで原因不明のバグになる部類のものです。

src/app/— ファイルを置くだけで画面になる

Expo Router は 「ファイル名 = 画面のURL」 という仕組みです。ルーティングの登録作業は一切要りません。

src/app/
├── _layout.tsx              ← 全画面の一番外側の枠
└── (tabs)/
    ├── _layout.tsx          ← タブバーの定義
    ├── index.tsx            ← /        (ホーム)
    ├── calendar.tsx         ← /calendar
    ├── graph.tsx            ← /graph
    ├── report.tsx           ← /report
    └── settings.tsx         ← /settings

命名ルール

書き方意味
calendar.tsx/calendar という画面になる
index.tsx/(トップ画面)になる。home.tsx ではダメ
_layout.tsx画面ではなく「枠」になる(アンダースコアが目印)
(tabs)括弧付きフォルダは URL に出ない。グループ分け専用

(tabs) が便利なのは、「タブバーが付く画面」と「付かない画面」を分けられるからです。設定の詳細画面などはタブバー無しで全画面表示したいので、(tabs) の外に置きます。

実際のコード

src/app/_layout.tsx — 一番外側の枠

import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
import { Stack } from 'expo-router/stack';
import { StatusBar } from 'expo-status-bar';
import { useColorScheme } from 'react-native';

export default function RootLayout() {
  const colorScheme = useColorScheme();

  return (
    <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
      <Stack screenOptions={{ headerShown: false }} />
      <StatusBar style="auto" />
    </ThemeProvider>
  );
}

useColorScheme() で iPhone がダークモードかどうかを取得し、配色を自動で切り替えています。

src/app/(tabs)/_layout.tsx — タブバー

中身は私が作っているアプリに合わせているので、自分のものに変更してください。

import { Icon, Label, NativeTabs } from 'expo-router/unstable-native-tabs';

export default function TabsLayout() {
  return (
    <NativeTabs>
      <NativeTabs.Trigger name="index">
        <Icon sf="house.fill" />
        <Label>ホーム</Label>
      </NativeTabs.Trigger>

      <NativeTabs.Trigger name="calendar">
        <Icon sf="calendar" />
        <Label>カレンダー</Label>
      </NativeTabs.Trigger>

      <NativeTabs.Trigger name="graph">
        <Icon sf="chart.line.uptrend.xyaxis" />
        <Label>グラフ</Label>
      </NativeTabs.Trigger>
    </NativeTabs>
  );
}

name の値は ファイル名と一致させます(index なら index.tsx)。

sf= は SF Symbols という Apple 純正のアイコン集の名前です。

画像ファイルを用意しなくても、house.fill や calendar と書くだけで iOS 標準のアイコンが出ます。

名前は Apple の「SF Symbols」アプリ(無料)で探せます。

注意:
expo-router/unstable-native-tabs は名前のとおり unstable(不安定)です。
将来 API が変わる可能性があります。実際、SDK 54 と SDK 57 で書き方が違いました。
– SDK 54: <Icon /> と <Label /> を個別に import
– SDK 57: <NativeTabs.Trigger.Icon /> のような入れ子

src/app/(tabs)/index.tsx — 画面の中身

import { StyleSheet, Text, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';

export default function HomeScreen() {
  return (
    <SafeAreaView style={styles.container}>
      <View style={styles.inner}>
        <Text style={styles.title}>ホーム</Text>
      </View>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1 },
  inner: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  title: {
    fontSize: 24,
    fontWeight: '600',
  },
});

Web の HTML と対応させると、こう覚えると分かりやすいです。

React NativeHTML でいうと
<View><div>
<Text><p> /  <span>
<SafeAreaView>ノッチやホームバーを避ける <div>

<Text> の外に文字を書くとエラーになります。 これは Web と違うところで、最初に必ず引っかかります。

iPhone に表示する

1. iPhone に App Store から Expo Go をインストール

2. Mac と iPhone を同じ Wi-Fi につなぐ

3. ターミナルで開発サーバーを起動

npx expo start

4. ターミナルに出た QRコードを iPhone の標準カメラアプリで読む

これだけです。

⚠️ ハマりポイント:iOS の「ローカルネットワーク」許可

QRを読むと、こんなエラーが出ることがあります。

There was a problem running the requested app.
Unknown error: The Internet connection appears to be offline.
exp://....

「オフライン」と言われますが、Wi-Fi はちゃんと繋がっています。

iOS 14 以降、アプリが同じ Wi-Fi 内の機器(=あなたの Mac)にアクセスするには、専用の許可が必要です。これが下りていないと、この紛らわしいエラーが出ます。

直し方

設定 → プライバシーとセキュリティ → ローカルネットワーク → 「Expo Go」を ON

そのあと Expo Go を完全に終了(アプリスイッチャーで上にスワイプ)してから開き直します。

切り分けのコツ

iPhone の Safari で http://さっき出てた数値 を開いてみてください(IPアドレスはターミナルに表示されているものに読み替えてください)。

・何か表示される :ネットワークは届いている → ローカルネットワーク許可の問題
・繋がらない : ネットワーク自体の問題(別のWi-Fi、ルーターの分離機能など)

Safari はこの許可の対象外なので、「Safari では開けるのに Expo Go では開けない」なら許可の問題で確定です。

ネットワーク自体が届かない場合

・iPhone と Mac が同じ Wi-Fiか(2.4GHz と 5GHz で SSID が分かれている場合は要注意)

・iPhone の iCloud プライベートリレーを OFF

・ルーターのプライバシーセパレーター / ネットワーク分離機能を OFF(日本のルーターに多い機能で、同じ Wi-Fi の機器同士の通信を禁止します)

どうしても解決しない場合は、インターネット経由で繋ぐ方法があります:

npx expo start --tunnel

遅くなりますが、ネットワーク設定に一切依存せずに繋がります。

⚠️ ハマりポイント:ログインは不要

Expo Go を初めて開くと、ログイン画面が大きく出てきます。

私はこれに従って、Googleアカウントでログイン → ユーザー名登録 → アカウント種別の選択 → 組織名とスラッグの設定 → プロジェクト作成 …と進みました。

このステップは全部不要でした。

ローカル開発に必要なのは、これだけです:npx expo start → QRを読む

ログイン機能は「自分が公開したプロジェクトを一覧表示する」ためのもので、手元の開発とは無関係です。ログイン画面はスキップして構いません。

(ただし、将来 TestFlight や App Store に配信する段階では EAS のアカウントが必要になるので、作っておいて無駄にはなりません。)

なお、Expo Go のホーム画面には「Development servers」と「Projects」の2つの欄があります。

押すべきなのは 上の「Development servers」に出てくるサーバーです。下の「Projects」はクラウドに登録したプロジェクトの一覧で、何も公開していない状態で押すとエラーになります。

終了方法と電池

・ iPhone :アプリスイッチャーで Expo Go を上にスワイプして終了

・Mac : ターミナルで Ctrl + C

「最近開いた項目」は消さなくていい

Expo Go に残る「最近開いた項目」はただの履歴リストです。ブラウザの履歴と同じで、表示されていてもアプリは動いていませんし、電池も通信も使いません。

むしろ残しておくと、翌日は npx expo start → 履歴をタップ の2手で再開できて便利です。

その他のファイルは何なのか

create-expo-app はたくさんファイルを作りますが、分類するとこうなります。

自動生成(編集しても意味がない)


何者か
node_modules/ライブラリ本体。728個。npm install で復元されるので git に入れません
package-lock.jsonバージョンの固定記録。これは git に入れます(他の人が同じ環境を再現するため)
.expo/開発サーバー のキャッシュと、ルートの型定義(/calendar などを自動生成)
expo-env.d.ts画像 import などの型定義

.expo/expo-env.d.ts.gitignore に入っているので、消しても npx expo start で作り直されます。

設定

ファイル中身触る?
tsconfig.jsonTypeScript の設定。@/components/... と短く書けるのはここの paths のおかげほぼ触らない
.gitignoregit に入れないファイルの指定ほぼ触らない

消していいもの

create-expo-app はサンプル画面(Expo のロゴが回るデモ)を一緒に作ります。

自分のアプリを作るなら、サンプル画面とサンプル画像は全部消して構いません。

消したあとに、そのサンプルでしか使っていなかったライブラリが package.json に残ります。これも一緒に消しておくと node_modules が軽くなります。

まとめ:毎日のコマンドはこれだけ

npx expo start

→ iPhone の Expo Go で「最近開いた項目」をタップ

コードを保存すると、1秒ほどで iPhone の画面に反映されます。 ビルドも転送も不要です。ここまで来ると開発がとても速くなります。

詰まったときのチェックリスト

・Project is incompatible with this version of Expo Go
 →SDK のバージョン不一致。App Store の Expo Go に合わせて SDK を下げる

・The Internet connection appears to be offline
 →iOS のローカルネットワーク許可。
  設定 → プライバシーとセキュリティ → ローカルネットワーク

・QR を読んでも Expo Go が開かない
 →標準のカメラアプリで読む

・ 通知タップでアプリが開かない
 →app.json の scheme が数字で始まっていないか

・ライブラリを入れたら動かなくなった
 →npm install ではなく npx expo install を使う

・急に読み込まなくなった
 →iPhone の低電力モード / Wi-Fi の切り替わり

おわりに

公式ドキュメントどおりにやっても詰まったのは、次の3つでした。

1. create-expo-appが最新 SDK で作るのに、App Store の Expo Go は追いついていない

2. iOS のローカルネットワーク許可が必要なのに、エラーメッセージが「オフライン」としか言わない

3. ログイン画面が目立つ位置に出るが、実は不要

どれも「知っていれば1分、知らないと数時間」の類です。同じところで止まっている方の役に立てば幸いです。

タイトルとURLをコピーしました