Rork Max アプリでExpo Goプレビューと実機ビルドの動作が違う:よくある原因と解決法
Rork Maxでアプリを開発していると、Expo Goでのプレビューでは完璧に動いているのに、本番ビルドや実機でテストすると急に動かなくなる……という体験をすることがあります。
「プレビューで確認したのにリリース後にクラッシュした」「Expo GoではUIが正しく見えるのにビルドすると崩れる」——このような問題は、Expo GoとネイティブビルドのRuntimeの違いに起因していることがほとんどです。
Expo Goとネイティブビルドの根本的な違い
まず前提として、Expo GoとEAS Build(またはexpo buildによるネイティブビルド)の違いを理解する点が肝心です。
Expo Goは事前にコンパイルされたExpoのシェルアプリで、JavaScript(またはTypeScript)のみを実行します。ネイティブモジュールはExpo SDKが提供するものだけが使えます。
EAS Build / ネイティブビルドは実際のApp Store配布向けバイナリを生成します。app.json/app.config.jsの全設定が反映され、カスタムのネイティブコードを含む本番アプリです。
この違いが、多くの「プレビューは動くのにビルドが動かない」問題の根本原因です。
問題パターン①:カスタムフォントが実機で表示されない
症状: Expo Goではカスタムフォントが適用されているのに、ビルドしたアプリではデフォルトフォントになります。
// ❌ フォントの読み込みタイミングが不適切
import * as Font from 'expo-font';
export default function App() {
// フォント読み込みを待たずにレンダリングされる
Font.loadAsync({
'NotoSansJP': require('./assets/fonts/NotoSansJP-Regular.ttf'),
});
return <Text style={{ fontFamily: 'NotoSansJP' }}>テキスト</Text>;
}
// ✅ useFontsフックで読み込み完了を待つ
import { useFonts } from 'expo-font';
import * as SplashScreen from 'expo-splash-screen';
import { useEffect } from 'react';
SplashScreen.preventAutoHideAsync();
export default function App() {
const [fontsLoaded, fontError] = useFonts({
'NotoSansJP': require('./assets/fonts/NotoSansJP-Regular.ttf'),
'NotoSansJP-Bold': require('./assets/fonts/NotoSansJP-Bold.ttf'),
});
useEffect(() => {
if (fontsLoaded || fontError) {
SplashScreen.hideAsync();
}
}, [fontsLoaded, fontError]);
if (!fontsLoaded && !fontError) {
return null; // フォント読み込み中はnullを返す
}
return <Text style={{ fontFamily: 'NotoSansJP' }}>テキスト</Text>;
}また、app.jsonでフォントファイルをassets配列に含める必要があります。
{
"expo": {
"assets": [
"./assets/fonts/NotoSansJP-Regular.ttf",
"./assets/fonts/NotoSansJP-Bold.ttf"
]
}
}問題パターン②:ネイティブモジュールがビルド後に動かない
症状: Expo Goでは使えていた機能が、本番ビルドでのみクラッシュします。
Expo Goには組み込まれていない(またはバージョンが異なる)ネイティブモジュールを使うと、このような問題が起きます。
# まず問題のモジュールを特定する
# Expo Goと互換性がないモジュールを使っているか確認
npx expo-doctor
# よく問題になるモジュールの例
# - react-native-camera (Expo Go非対応 → expo-camera を使う)
# - react-native-maps (バージョン依存 → react-native-maps@1.7以降を推奨)
# - @stripe/stripe-react-native (Expo Goで制限あり → dev-clientを使う)解決策A: Expo互換の代替モジュールに切り替える
// ❌ Expo Goで完全動作しないことがある
import { Camera } from 'react-native-camera';
// ✅ Expo管理のモジュールを使う
import { CameraView, useCameraPermissions } from 'expo-camera';解決策B: Development Build(Custom Dev Client)を使う
Expo Goを使わず、カスタムのDevelopmentビルドを使うことで、本番ビルドと同じ環境でテストできます。
# EASでdevelopmentビルドを作成
eas build --profile development --platform ios
# または開発サーバーをdevClientモードで起動
npx expo start --dev-client問題パターン③:環境変数がビルド後に反映されない
症状: APIエンドポイントやAPIキーが本番ビルドで参照できません。process.env.EXPO_PUBLIC_API_URLがundefinedになります。
# .env.local (Expo Goでは読み込まれるが、ビルドには反映されない場合がある)
EXPO_PUBLIC_API_URL=https://api.example.com// ❌ 本番ビルドで環境変数が取れないケース
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
// → undefinedになることがある
// ✅ app.config.jsを使って確実に埋め込む// app.config.js
export default {
expo: {
name: "My App",
// 環境変数をビルド時に埋め込む
extra: {
apiUrl: process.env.EXPO_PUBLIC_API_URL ?? "https://api.example.com",
apiKey: process.env.EXPO_PUBLIC_API_KEY,
}
}
};// アプリ内での参照
import Constants from 'expo-constants';
const apiUrl = Constants.expoConfig?.extra?.apiUrl;EAS Buildでの環境変数管理(本番環境):
// eas.json
{
"build": {
"production": {
"env": {
"EXPO_PUBLIC_API_URL": "https://api.production.example.com"
}
},
"preview": {
"env": {
"EXPO_PUBLIC_API_URL": "https://api.staging.example.com"
}
}
}
}EAS Secretsで機密情報を安全に管理することもできます。
eas secret:create --scope project --name STRIPE_SECRET_KEY --value "sk_live_xxxx"問題パターン④:画像やアセットがビルド後に表示されない
症状: Expo Goでは画像が表示されるのに、ビルドしたアプリで表示されない、または壊れた画像アイコンになります。
// ❌ 動的なrequireはビルドに含まれないことがある
const imagePath = `./assets/images/${imageName}.png`;
const image = require(imagePath); // ← これはビルドで機能しない
// ✅ 静的なrequireを使う(ビルド時にバンドルされる)
const images = {
cat: require('./assets/images/cat.png'),
dog: require('./assets/images/dog.png'),
bird: require('./assets/images/bird.png'),
};
// 動的に選ぶ場合はオブジェクトのマッピングを使う
const image = images[imageName];app.jsonのassets配列にも必要なアセットを追加してください。
{
"expo": {
"assets": ["./assets/images/", "./assets/icons/"],
"icon": "./assets/icon.png",
"splash": {
"image": "./assets/splash.png"
}
}
}デバッグの効率化:Expo Goに依存しないテスト環境の構築
長期的な解決策として、Development Buildを常用することを強くおすすめします。
# EAS CLIのインストール
npm install -g eas-cli
# プロジェクトの初期化
eas build:configure
# iOS/Android向けdevelopmentビルドの作成
eas build --profile development --platform all
# ビルドをインストールして起動
npx expo start --dev-clientDevelopment Buildを使うことで、本番ビルドとほぼ同じ環境でHot Reloadを使いながら開発できます。Expo Goで動かなくなるケースを事前に発見できるため、リリース前のトラブルを大幅に減らせます。
全体を振り返って
Expo Goと実機ビルドの動作不一致は、主に以下の4パターンに分類できます。
- カスタムフォント →
useFontsフックとapp.jsonのassets配列を正しく設定する - ネイティブモジュール → Expo互換モジュールへの移行またはDevelopment Buildの使用
- 環境変数 →
app.config.jsのextra経由またはEAS環境変数を使う - 動的アセット → 静的なrequireとオブジェクトマッピングに書き換える
プレビューと本番の差異を根本から解消するには、Development Buildを本開発環境として採用するのが最も効果的です。これにより、リリース後に初めてバグに気づくという状況を防げます。
本記事の内容