今回は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 は、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
}
}
}
| キー | 意味 |
|---|---|
| name | iPhone のホーム画面に出るアプリ名。日本語OK |
| slug | URL用の識別子。英数字とハイフンのみ |
| scheme | アプリ独自の URL。通知をタップしてアプリを開くときなどに使う |
| orientation | portrait で縦画面固定 |
| userInterfaceStyle | automatic で 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 Native | HTML でいうと |
|---|---|
| <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.json | TypeScript の設定。@/components/... と短く書けるのはここの paths のおかげ | ほぼ触らない |
.gitignore | git に入れないファイルの指定 | ほぼ触らない |
消していいもの
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分、知らないと数時間」の類です。同じところで止まっている方の役に立てば幸いです。
この記事のカテゴリー
開発ブログ の記事をもっと見る →