更新日: 2026年6月3日
React Navigation 8は、静的API(Static API)を中心に再設計された、React Native向けの公式ナビゲーションライブラリの最新メジャーバージョンです。画面定義からTypeScriptの型とディープリンクを自動推論し、useNavigation('Profile')のように画面名から型を直接導出できるようになりました。さらにReact Native 0.82で旧アーキテクチャが削除されたことに伴い、v8は新アーキテクチャ(Fabric / TurboModules)専用となっています。この記事では、v7からの移行手順、静的APIの設計、型安全なルーティング、ディープリンクの自動化までを実際のコードと共に解説します。正直なところ、自分のプロジェクトでv7から上げた時に一番嬉しかったのは、画面追加のたびに書いていたあの長ったらしい型注釈がほぼ消えたことでした。
- React Navigation 8 は新アーキテクチャ専用で、React Native 0.82 以降と Expo SDK 54+ が必要です。
- 静的API(Static API)は
createStaticNavigation を中心に、画面定義からTypeScript型・ディープリンクを自動生成します。
useNavigation('ScreenName') で画面名を渡すだけで戻り値の型が自動推論され、ジェネリクスが不要になりました。
- 動的APIは廃止されておらず、
createStaticNavigation と従来の NavigationContainer は並行して利用できます。
- ディープリンクは
linking プロパティを画面定義の隣に書けるため、ルートツリーと一元管理できます。
- 移行は1ナビゲーターずつ段階的に実施でき、既存の
RootStackParamList を残したまま静的化を進められます。
React Navigation 8 の新機能とは
React Navigation 8 は 2026 年初頭に安定版がリリースされた、ライブラリ史上で最大級の型システム改修を含むバージョンです。最大の変更点は、useNavigation をはじめとする主要フックの戻り値の型が、ジェネリクスを書かなくても画面名から自動で決まるようになった点です。これにより、長年悩まされてきた StackNavigationProp<RootStackParamList, 'Profile'> のような冗長な型注釈がほぼ消滅しました。正直、これだけでも移行する価値があります。
もう一つの大きな変更は、旧アーキテクチャ(Paper renderer / レガシー Bridge)のサポート打ち切りです。React Native 0.80 で旧アーキテクチャは凍結され、0.82 で完全に削除されました。v8 はこの方針に追従しており、Hermes と Fabric を前提に内部実装を簡素化しています。React Navigation 公式の v7→v8 アップグレードガイドでも、新アーキテクチャへの移行が前提条件として明記されています。
また、静的API は v7 で導入されてからの実運用フィードバックを反映して、if 条件による画面の表示制御、screenLayout による画面ラッパー、ネストされたナビゲーターの型推論が大幅に強化されました。Bottom Tabs では headerSearchBarOptions と tabBarVariant が追加され、iOS 17 以降のフローティングタブバーや Android 15 のマテリアル 3 表示にも対応しています。地味な変更ですが、デザイナーからの「あのタブをもうちょっと浮かせたい」要望をネイティブAPIで応えられるのは大きいです。
静的APIと動的APIの違いは何か
静的API(Static API)と動的API(Dynamic API)は、ナビゲーションツリーを宣言する方法が根本的に異なります。静的APIは設定オブジェクトでルートを一括宣言するため、TypeScript コンパイラと React Navigation 内部が同じ「設定情報」を共有でき、型・リンキング・状態永続化までを自動推論できます。一方、動的APIでは <Stack.Screen> を React の子要素として描画時に登録するため、コンパイル時にはツリーを完全に知ることができません。
| 項目 | 静的API | 動的API |
| 画面の定義 | 設定オブジェクトの screens キー | <Stack.Screen> の子要素 |
| 型推論 | 自動(StaticParamList) | 手書きの RootStackParamList |
| ディープリンク | 画面定義に linking を併記 | 外部の linking 設定オブジェクトで一元管理 |
| 条件付き画面 | if プロパティ(フック関数) | JSX の条件分岐 |
| ベストフィット | 新規プロジェクト、固定的なルート構造 | サインインフローなど大きく分岐するアプリ |
| 初期化エントリ | createStaticNavigation() | <NavigationContainer> |
結論を先に言うと、ほとんどのアプリは静的APIで完結します。動的APIが必要なのは、認証状態に応じてルート構造そのものを大きく差し替えるケースや、サーバー駆動 UI でルートを実行時に生成するケースくらいです。v8 では両者を共存させられるので、まずは静的APIから始めて、必要な箇所だけ動的に書くハイブリッド戦略が現実的かと思います。
React Navigation 8 のインストールと初期設定
新規プロジェクトの場合、まず React Native 0.82+ または Expo SDK 54+ をセットアップしておきます。Expo SDK のアップグレード手順はExpo SDK 55アップグレード完全ガイドで詳しく解説しています。準備ができたら、コアパッケージと最低限の依存を導入しましょう。
npm install @react-navigation/native@^8 \
@react-navigation/native-stack@^8 \
@react-navigation/bottom-tabs@^8 \
react-native-screens react-native-safe-area-context
iOS では npx pod-install を忘れずに実行してください(自分はこれを忘れて30分溶かしたことがあります)。Android は autolinking が New Architecture フラグを自動検出して Fabric 用の C++ コードをビルドします。次に、最小構成のアプリエントリを書きます。
import { createStaticNavigation } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeScreen from './screens/HomeScreen';
import ProfileScreen from './screens/ProfileScreen';
const RootStack = createNativeStackNavigator({
initialRouteName: 'Home',
screens: {
Home: HomeScreen,
Profile: {
screen: ProfileScreen,
options: { title: 'プロフィール' },
},
},
});
const Navigation = createStaticNavigation(RootStack);
export default function App() {
return <Navigation />;
}
ここで重要なのは、createStaticNavigation が返すのは「すでにルートに NavigationContainer が組み込まれた」コンポーネントである点です。v7 までのように NavigationContainer を別途配置する必要はありません。状態永続化、テーマ、ディープリンクも全てこの新コンポーネントに props として渡します。
型推論を有効にする方法
React Navigation 8 の真価は、TypeScript 型推論にあります。StaticParamList を使ってグローバルな RootParamList を一度宣言するだけで、アプリ全体の useNavigation、useRoute、Link、navigate 呼び出しが完全に型付けされます。
import type { StaticParamList } from '@react-navigation/native';
const RootStack = createNativeStackNavigator({
screens: {
Home: HomeScreen,
Profile: {
screen: ProfileScreen,
// params の型もここで宣言する
},
Article: {
screen: ArticleScreen,
},
},
});
declare global {
namespace ReactNavigation {
interface RootParamList extends StaticParamList<typeof RootStack> {}
}
}
この設定を入れた瞬間、画面コンポーネント側では次のように画面名を useNavigation に渡すだけで型が決まります。v7 までのジェネリクスは完全に不要です。
import { useNavigation } from '@react-navigation/native';
function HomeScreen() {
// ↓ 戻り値の型は 'Home' の文脈で自動推論される
const navigation = useNavigation('Home');
return (
<Button
title="プロフィールへ"
onPress={() => navigation.navigate('Profile', { userId: '123' })}
/>
);
}
パラメータ付き画面は screen 関数のシグネチャから推論されます。明示的に宣言したい場合は、StaticScreenProps ヘルパーを使うと props の型がそのまま静的設定にも反映されます。これにより、画面コンポーネントを単体テストする際にも同じ型を再利用できるのが地味に便利です。公式の TypeScript ガイドには、ネストされたナビゲーターでの型推論パターンが網羅されています。
ディープリンクを自動構成する
v8 のディープリンクは、画面定義の中に linking プロパティを書くだけで完結します。これまでは別ファイルに巨大な linking.config を抱える必要がありましたが、v8 では各画面の URL パスをローカルに宣言できるため、ルートツリーと一致しない設定ミスが起きません。
const RootStack = createNativeStackNavigator({
screens: {
Home: {
screen: HomeScreen,
linking: { path: '' }, // ルートパス
},
Article: {
screen: ArticleScreen,
linking: {
path: 'article/:slug',
parse: { slug: (s: string) => decodeURIComponent(s) },
},
},
Settings: {
screen: SettingsScreen,
linking: { path: 'settings' },
// 認証が必要な画面は if で制御
if: useIsAuthenticated,
},
},
});
const Navigation = createStaticNavigation(RootStack);
export default function App() {
return (
<Navigation
linking={{
enabled: 'auto',
prefixes: ['rnrelay://', 'https://reactnativerelay.com'],
}}
/>
);
}
enabled: 'auto' を指定すると、画面定義に linking.path がある画面だけが自動的にリンク可能になります。ユニバーサルリンクや App Links の検証は通常通り apple-app-site-association と assetlinks.json を配信する必要がありますが、設定が一元化されているのでデバッグは大幅に楽になりました。複雑なリストアクションを伴う画面遷移については、React Nativeリスト完全ガイドでディープリンクからのスクロール位置復元の実装パターンを取り上げています。
React Navigation 7 から 8 への移行手順
既存の v7 アプリを v8 にアップグレードする際の最短経路は次の通りです。所要時間は中規模アプリ(30〜50画面)で半日〜2日が目安です。私が前回担当したアプリ(約40画面)は、ちょうど1.5日で型エラーゼロまで持っていけました。
- React Native と Expo を 0.82+ / SDK 54+ に上げる: v8 は旧アーキテクチャでは動作しないため、これが事実上の前提条件です。新アーキテクチャ移行が未完了なら先に済ませます。
- パッケージを一括更新する:
npm install @react-navigation/native@8 @react-navigation/native-stack@8 @react-navigation/bottom-tabs@8 @react-navigation/drawer@8 を実行し、react-native-screens@^4.5、react-native-safe-area-context@^5 も同時に更新します。
- useNavigation のジェネリクスを削除する:
useNavigation<NativeStackNavigationProp<RootStackParamList, 'Profile'>>() から useNavigation('Profile') に置換します。一度に全画面を直す必要はなく、v8 は古い書き方も互換維持しています。
- NavigationContainer をオプションで
createStaticNavigation に置き換える: 動的APIをそのまま使い続けるならこの手順はスキップ可能です。
- ディープリンク設定を画面定義の隣に移す: 古い
linking オブジェクトは互換のため残せますが、新規追加ルートは画面定義に書くスタイルに統一しましょう。
- テストを再生成する:
jest-expo または @testing-library/react-native のスナップショットは型情報が変わるので必ず更新します。
移行中に最も詰まりやすいのが、navigation.navigate の第2引数の型エラーです。v8 では未定義の画面名や存在しないパラメータがコンパイル時にエラー化されるため、ビルド時に大量のエラーが噴出することがあります。これは型システム強化による「正しいエラー」なので、修正することで実行時バグを防げます。むしろ、こうした隠れバグを掘り出せるという意味で、移行は良い棚卸しの機会だと思います。
新アーキテクチャ要件と対応戦略
React Navigation 8 は新アーキテクチャ(Fabric + TurboModules + JSI)を前提とした設計に振り切っています。これは単なる依存条件ではなく、内部実装が UIManager の同期測定 API、react-native-screens の Fabric 専用ネイティブビュー、react-native-gesture-handler の TurboModule バインディングを直接呼び出しているためです。
旧アーキテクチャのまま v8 を入れると、ジェスチャの認識遅延、モーダル遷移時の白フラッシュ、戻るボタンのアニメーション欠落といった視覚的なバグが多発します。回避策は存在しないので、必ず React Native 公式の新アーキテクチャドキュメントを参照して newArchEnabled=true を有効化してください。Expo の場合は app.json の "newArchEnabled": true 一行で済みます(拍子抜けするくらい簡単です)。
サードパーティ製ナビゲーション拡張(react-native-navigation や @react-native-screens/native-stack など)も同様の対応が必要です。一方、Reanimated 3.5 以降、Gesture Handler 2.16 以降、Vision Camera 4 以降は autolink で新アーキテクチャに即時対応しているので、これらを使っているアプリは大きな書き換えなしに移行できます。アニメーションを多用する画面遷移については、React Native Reanimated 4完全ガイドのシェアード要素遷移セクションも併せて参照すると効果的です。
静的APIと動的APIを組み合わせる
大きな既存コードベースでは、全画面を一度に静的化するのは現実的じゃありません。v8 は静的ルートの中に動的ナビゲーターを埋め込むことを公式にサポートしており、段階的移行が現実的な選択肢になります。例えば、ルートは静的に宣言しつつ、認証フローだけ動的な書き方を残すパターンは次のようになります。
import { createComponentForStaticNavigation } from '@react-navigation/native';
// 既存の動的ナビゲーターをそのまま再利用
function AuthStack() {
return (
<Stack.Navigator>
<Stack.Screen name="SignIn" component={SignIn} />
<Stack.Screen name="SignUp" component={SignUp} />
</Stack.Navigator>
);
}
const RootStack = createNativeStackNavigator({
screens: {
Main: MainTabs,
Auth: AuthStack, // 動的サブツリーをそのまま挿入
},
});
このパターンの注意点は、動的サブツリー内の画面は StaticParamList の自動推論に乗らないことです。該当箇所だけ手書きで RootParamList をマージする必要があります。完全自動の恩恵を受けたいなら、認証フローも静的化して if: useIsSignedOut で表示制御するのが王道ですね。公式の「静的と動的を組み合わせる」ガイドには、ボトムタブとモーダルの組み合わせなど、具体的なパターンが多数掲載されています。
よくあるエラーとトラブルシューティング
v8 移行直後によく遭遇するエラーと対処法をまとめます。私自身が踏んだものも多いので、心当たりがあったら参考にしてみてください。
- 「
Cannot find a router」エラー: createStaticNavigation を呼ばずに静的な RootStack を直接レンダリングしようとすると発生します。const Navigation = createStaticNavigation(RootStack) でラップしてから JSX に渡してください。
- 型推論が効かない:
declare global { namespace ReactNavigation { ... } } ブロックが tsconfig.json の include 範囲外にあるとグローバル拡張が読み込まれません。必ずソースルートに置きます。
- ディープリンクで「
Got an invalid URL」: linking.prefixes に指定するスキーム末尾の :// を忘れているケースが大半です。'rnrelay://' のように必ず付与します。
- iOS でビルドが通らない:
react-native-screens v4.5 未満が混在しています。npm dedupe を実行し、ios/Pods を削除して pod install --repo-update をやり直してください。
- Bottom Tabs のラベルが表示されない: v8 で
tabBarLabel のデフォルトが undefined に変わりました。明示的に文字列を指定する必要があります。
よくある質問
React Navigation 8 は古いアーキテクチャでも動作しますか?
動作しません。v8 は React Native 0.82 以降の新アーキテクチャ(Fabric + TurboModules)専用です。旧アーキテクチャのまま使いたい場合は v7 を継続利用し、まず Hermes と新アーキへの移行を完了させてから v8 にアップグレードしてください。
静的APIに移行すると動的APIは使えなくなりますか?
いいえ、動的API(NavigationContainer と <Stack.Screen>)は v8 でも引き続き第一級APIとしてサポートされます。静的APIは追加された選択肢であり、両者は同一アプリ内で混在可能です。サインインフローのみ動的にする、といった部分採用が一般的です。
Expo Router と React Navigation 8 はどちらを使うべきですか?
Expo を使った新規プロジェクトでファイルベースルーティングを好むなら Expo Router、すでに React Navigation を採用している既存アプリや、ルート構造を厳密にコードで制御したい場合は React Navigation 8 が適しています。なお Expo Router は内部で React Navigation を使用しているため、根本的な動作モデルは共通です。
useNavigation のジェネリクス書式は廃止されましたか?
廃止はされておらず後方互換です。ただし v8 では画面名を引数に渡す新書式(useNavigation('Profile'))の方が型推論精度が高く、リネーム時のリファクタリングも安全です。新規コードは新書式で書くことを推奨します。
ディープリンクのデバッグはどうやって行いますか?
iOS シミュレーターでは xcrun simctl openurl booted "rnrelay://article/hello"、Android エミュレーターでは adb shell am start -a android.intent.action.VIEW -d "rnrelay://article/hello" でテストできます。React Native DevTools の Console にリンク解決ログが流れるため、画面名とパラメータが期待通りかを即座に確認できます。