Bottom Tab は完成していると気持ちのいい部品なのですが、Rork で生成したコードに少し手を入れた瞬間、アイコンだけが消えたり、タップしても画面がうんともすんとも言わなくなったりすることがあります。私自身、Rork でアプリを量産しているうちに「同じ症状を 3 回以上踏んでいるな」と気づいて整理したのが、この記事の内容です。
困ったときに「どこを最初に見るべきか」を分かっている人だけが、サクッと数分で直せるところを、知らないと半日溶かす。そんな性格のバグなので、症状を 3 つのパターンに切り分けてから順に潰していくのが結局いちばん早いです。
症状を 3 つに切り分けてから手を動かす
まず、闇雲に _layout.tsx をいじる前に、いまのバグがどのパターンに当てはまるかを切り分けます。Rork で発生する Bottom Tab 系のトラブルは、私の経験ではほぼ次の 3 種類に集約されます。
- パターン A: アイコンだけ表示されない(タブの切り替え自体は動く)
- パターン B: タブをタップしても画面が切り替わらない(アイコンは出る)
- パターン C: 開発ビルドでは動くのに本番ビルドだけアイコンが消える
A と B では原因がまったく違うので、同じ手順で直そうとすると遠回りします。最初に Expo Go や Rork Companion で動作確認し、自分のケースがどれかを 1 分で判定してから次に進みます。
パターン A: アイコンが表示されないとき
このケースは、tabBarIcon に渡している関数の 戻り値の型 か icon ライブラリのインポート がほぼ確実に怪しいです。Rork が初期生成するコードはたいてい lucide-react-native を使いますが、ここを別のライブラリに差し替えた直後に空白になることが多いです。
// app/(tabs)/_layout.tsx
import { Tabs } from "expo-router";
import { Home, Settings, User } from "lucide-react-native";
export default function TabLayout() {
return (
<Tabs
screenOptions={{
// ❌ NG: 関数を返さない / size が undefined のまま渡る
// tabBarIcon: <Home />,
// ✅ OK: ({ color, size }) => ReactNode の形を守る
tabBarActiveTintColor: "#0070f3",
headerShown: false,
}}
>
<Tabs.Screen
name="index"
options={{
title: "Home",
tabBarIcon: ({ color, size }) => <Home color={color} size={size} />,
}}
/>
<Tabs.Screen
name="settings"
options={{
title: "Settings",
tabBarIcon: ({ color, size }) => <Settings color={color} size={size} />,
}}
/>
</Tabs>
);
}ポイントは 3 つです。(1) tabBarIcon は必ず関数で渡すこと、(2) color と size を props に流すこと、(3) lucide-react-native を使う場合は react-native-svg がインストール済みであること。react-native-svg が無いと、エラーは出ないのにアイコンだけ静かに消えます。expo install react-native-svg をたたいてから再ビルドすると、ピタッと出てきます。
パターン B: タップしても画面が切り替わらないとき
タブ自体は表示されているのにタップが効かない場合は、ファイル名とルート名のミスマッチを疑います。expo-router は app/(tabs)/ 配下のファイル名を自動でルートに割り当てるので、<Tabs.Screen name="profile" /> と書いておきながら app/(tabs)/Profile.tsx(先頭大文字)になっていると、対応が取れずに無反応になります。
修正の流れは次のとおりです。app/(tabs)/ の中身を確認して、ファイル名がすべて 小文字 + ハイフン になっていることを確認し、name= の値と一致させます。あわせて _layout.tsx の <Tabs.Screen name="..." /> 順がファイルの存在順と齟齬していないかも見ます。
もう一つよく踏むのが、Stack を入れ子にしたタブで initialRouteName を間違えるケースです。タブ内でさらに Stack を持たせる場合は、各サブ画面の最初のルートを明示しておかないと、タップしても「タブの中の何も無いルート」へ遷移してしまい、画面が動かないように見えます。原因の見つけ方やデバッグ手順は、expo-router でナビゲーションエラーが出るときの対処 にも詳しくまとめています。
パターン C: 本番ビルドだけアイコンが消える
開発中はちゃんと動いていたのに、TestFlight や本番 APK にしたとたん Bottom Tab のアイコンが空白になります。これは、Metro の tree-shaking とアイコンライブラリの相性で起きることが多いです。lucide-react-native のように動的インポートを内部で使うライブラリは、本番ビルドの最適化で参照を見失うことがあります。
対処は次の順で試すのが効率的です。
- まず
metro.config.jsでresolver.unstable_enableSymlinksを有効にしていないかを確認します。trueにしているとモジュール解決が崩れることがあります app.jsonのexperiments.tsconfigPathsを試験的に外して再ビルドする- それでも直らない場合は、
react-native-vector-iconsや@expo/vector-iconsの安定したセットに置き換える
@expo/vector-icons は Expo SDK に同梱されていて Bottom Tab 用途では事故が少なく、私はトラブル時のフォールバックとして第一候補にしています。アイコンセットを切り替えると、内部的には別パッケージになるので、本番ビルドの挙動も変わります。
つまずきやすいポイントと予防策
事前に避けられたら避けたいポイントを 3 つだけ書いておきます。
- 「アイコンが消える=設定ミス」と決めつけない。
react-native-svg未インストール、Hermes エンジン側の問題など、外側の依存関係が原因のことが半数くらいあります Tabs.Screenのnameとファイル名を 1 対 1 で合わせる。Rork の AI に「タブを追加して」と頼むと、たまにここがズレた状態で生成されるので、生成直後にファイルツリーを見て確認するクセをつけます- 本番ビルドは TestFlight で必ず確認する。Companion アプリで動いている=本番でも動く、ではありません。リリース直前に気づいて慌てないように、テスト工程に「Tab の見た目チェック」を 1 行入れておくだけで保険になります
似た系統の症状で UI が更新されないケースは state が更新されない・再レンダリングされない問題の修正 も参考になるので、原因の切り分けに迷ったら覗いてみてください。
全体を振り返って — 直したあとに 1 つだけやっておくこと
修正できたら、その日のうちに app/(tabs)/_layout.tsx をひとつのコンポーネントに切り出して、ファイル先頭に「Bottom Tab を追加するときの手順」を 3 行コメントで残しておくのをおすすめします。次に同じバグを踏むのは未来の自分か、共同開発者です。「アイコンを足したら react-native-svg を確認」「name= とファイル名を一致」「本番ビルドで TestFlight 確認」。この 3 行のチェックリストが、半日の時間を救ってくれることが何度もありました。