【初心者】気分記録アプリ、公開への道②|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をコピーしました