Flutter Navigation 2.0とGoRouter 2026年完全ガイド:ディープリンクから面接対策まで
GoRouter 17.5を使用したFlutter Navigation 2.0の実装方法を解説します。ディープリンク設定、ShellRoute、StatefulShellRoute、認証ガード、型安全ルーティングまで、本番アプリに必要な知識を網羅します。

Flutter のナビゲーションは、Navigation 2.0 の導入以来、大きく進化しました。Flutter チーム公式パッケージである GoRouter 17.5 は、Router API の複雑さを抽象化し、宣言的ルーティング、自動ディープリンク、ネストされたナビゲーションを標準でサポートします。
Flutter チームは GoRouter を2026年時点で機能完備と見なしています。パスパラメータ、クエリパラメータ、リダイレクト、永続的UI用の ShellRoute、タブ状態保持用の StatefulShellRoute をサポートします。生の Navigator 2.0 を手書きで実装することは、本番環境ではほとんどありません。
Navigator 2.0 アーキテクチャの理解
Navigator 2.0 は、宣言的で URL 駆動型のアプローチを Flutter ナビゲーションに導入しました。命令型の push や pop 呼び出しの代わりに、アプリの状態がナビゲーションスタックに表示される内容を決定します。このアーキテクチャは3つのコアクラスに依存しています。
- Router: ナビゲーションを調整するトップレベルウィジェット
- RouteInformationParser: URL をアプリ状態に変換
- RouterDelegate: その状態に基づいてウィジェットツリーを構築
OS からディープリンクが到着するか、Web ブラウザで URL が変更されると、フレームワークが状態を更新し、UI が反応します。問題点:カスタムの RouterDelegate と RouteInformationParser を手書きで作成するには、大量のボイラープレートが必要です。
// Raw Navigator 2.0 approach - verbose and rarely used directly
class AppRouterDelegate extends RouterDelegate<AppRoutePath>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<AppRoutePath> {
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
AppRoutePath? _currentPath;
AppRoutePath? get currentConfiguration => _currentPath;
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
pages: [
const MaterialPage(child: HomeScreen()),
if (_currentPath?.isProductPage == true)
MaterialPage(child: ProductScreen(id: _currentPath!.productId!)),
],
onDidRemovePage: (page) {
// Handle page removal
},
);
}
Future<void> setNewRoutePath(AppRoutePath path) async {
_currentPath = path;
notifyListeners();
}
}この冗長性が GoRouter が存在する理由です。Navigator 2.0 をラップし、ボイラープレートを処理します。
GoRouter 17.5 のセットアップ
GoRouter 17.5 では、ルートメタデータサポートとパスパラメータの正規表現制約が導入されました。最小 SDK 要件は Flutter 3.32 と Dart 3.8 です。
import 'package:go_router/go_router.dart';
final goRouter = GoRouter(
initialLocation: '/',
debugLogDiagnostics: true, // Useful during development
routes: [
GoRoute(
path: '/',
name: 'home',
builder: (context, state) => const HomeScreen(),
routes: [
GoRoute(
path: 'products/:id', // Path parameter
name: 'product',
builder: (context, state) {
final productId = state.pathParameters['id']!;
return ProductScreen(productId: productId);
},
),
GoRoute(
path: 'search',
name: 'search',
builder: (context, state) {
// Query parameters: /search?q=flutter&category=books
final query = state.uri.queryParameters['q'] ?? '';
final category = state.uri.queryParameters['category'];
return SearchScreen(query: query, category: category);
},
),
],
),
],
);ルーターは MaterialApp.router と統合されます。
import 'package:flutter/material.dart';
import 'router_config.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: goRouter,
title: 'GoRouter Demo',
);
}
}Android と iOS のディープリンク設定
ディープリンクにより、外部 URL がアプリ内の特定の画面を開くことができます。プラットフォーム設定が完了すれば、GoRouter が自動的にルーティングを処理します。
Android App Links
Android では AndroidManifest.xml に intent-filter を設定し、ドメインに Digital Asset Links ファイルをホストする必要があります。
<!-- android/app/src/main/AndroidManifest.xml -->
<activity android:name=".MainActivity">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="example.com"
android:pathPrefix="/products" />
</intent-filter>
</activity>assetlinks.json ファイルは https://example.com/.well-known/assetlinks.json で配信される必要があります。
iOS Universal Links
iOS では、Xcode で Associated Domains capability を有効にし、apple-app-site-association ファイルをホストする必要があります。
{
"applinks": {
"apps": [],
"details": [
{
"appID": "TEAMID.com.example.app",
"paths": ["/products/*", "/search"]
}
]
}
}GoRouter 17.5 では、空のパスを持つ Android のコールドスタートディープリンクがスキームと authority を失う重大なバグが修正されました。これにより、アプリが実行されていない場合でもディープリンクが確実に動作します。
Android のディープリンクは adb shell am start -a android.intent.action.VIEW -d "https://example.com/products/123" でテストできます。iOS の場合は xcrun simctl openurl booted "https://example.com/products/123" を使用します。
永続的ナビゲーション UI のための ShellRoute
ShellRoute は、BottomNavigationBar や Drawer のような永続的な UI 要素で子ルートをラップします。シェルは子ルート間のナビゲーション中も表示されたままになります。
final goRouter = GoRouter(
initialLocation: '/home',
routes: [
ShellRoute(
builder: (context, state, child) {
return ScaffoldWithNavBar(child: child);
},
routes: [
GoRoute(
path: '/home',
name: 'home',
builder: (context, state) => const HomeTab(),
),
GoRoute(
path: '/explore',
name: 'explore',
builder: (context, state) => const ExploreTab(),
),
GoRoute(
path: '/profile',
name: 'profile',
builder: (context, state) => const ProfileTab(),
),
],
),
],
);
// scaffold_with_nav_bar.dart
class ScaffoldWithNavBar extends StatelessWidget {
final Widget child;
const ScaffoldWithNavBar({super.key, required this.child});
Widget build(BuildContext context) {
return Scaffold(
body: child,
bottomNavigationBar: BottomNavigationBar(
currentIndex: _calculateSelectedIndex(context),
onTap: (index) => _onItemTapped(index, context),
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.explore), label: 'Explore'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
],
),
);
}
int _calculateSelectedIndex(BuildContext context) {
final location = GoRouterState.of(context).uri.toString();
if (location.startsWith('/explore')) return 1;
if (location.startsWith('/profile')) return 2;
return 0;
}
void _onItemTapped(int index, BuildContext context) {
switch (index) {
case 0: context.go('/home');
case 1: context.go('/explore');
case 2: context.go('/profile');
}
}
}GoRouter 17.5 では、iOS のバックジェスチャーがアクティブなサブルートではなく ShellRoute 全体をポップしてしまう問題が修正されました。
Flutterの面接対策はできていますか?
インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。
タブ状態を保持する StatefulShellRoute
ユーザーがタブを切り替えると、ShellRoute は子を再構築します。StatefulShellRoute は各ブランチのナビゲーションスタックを保持します。
final goRouter = GoRouter(
initialLocation: '/home',
routes: [
StatefulShellRoute.indexedStack(
builder: (context, state, navigationShell) {
return ScaffoldWithNavBar(navigationShell: navigationShell);
},
branches: [
StatefulShellBranch(
routes: [
GoRoute(
path: '/home',
builder: (context, state) => const HomeTab(),
routes: [
GoRoute(
path: 'details/:id',
builder: (context, state) => DetailsScreen(
id: state.pathParameters['id']!,
),
),
],
),
],
),
StatefulShellBranch(
routes: [
GoRoute(
path: '/explore',
builder: (context, state) => const ExploreTab(),
),
],
),
StatefulShellBranch(
routes: [
GoRoute(
path: '/profile',
builder: (context, state) => const ProfileTab(),
),
],
),
],
),
],
);この設定により、ユーザーが /home/details/42 に移動し、Explore タブに切り替え、その後 Home に戻ると、details 画面が表示されたままになります。
リダイレクトによるルートガード
リダイレクトは認証チェックと条件付きナビゲーションを処理します。redirect コールバックは各ナビゲーションの前に実行されます。
final goRouter = GoRouter(
initialLocation: '/',
redirect: (context, state) {
final isLoggedIn = AuthService.instance.isLoggedIn;
final isLoggingIn = state.matchedLocation == '/login';
// Redirect to login if not authenticated
if (!isLoggedIn && !isLoggingIn) {
return '/login?redirect=${state.uri}';
}
// Redirect away from login if already authenticated
if (isLoggedIn && isLoggingIn) {
final redirect = state.uri.queryParameters['redirect'];
return redirect ?? '/';
}
return null; // No redirect
},
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
),
GoRoute(
path: '/login',
builder: (context, state) => const LoginScreen(),
),
GoRoute(
path: '/admin',
redirect: (context, state) {
// Route-level redirect for admin-only access
if (!AuthService.instance.isAdmin) {
return '/';
}
return null;
},
builder: (context, state) => const AdminScreen(),
),
],
);GoRouter 17.5 では、リダイレクトコールバック内で context.namedLocation() などのコンテキスト拡張メソッドを使用できるようになりました。
Flutter ナビゲーション面接でよく聞かれる質問
面接官は、ナビゲーションがアーキテクチャ、状態管理、プラットフォーム統合に関わるため、この知識を頻繁にテストします。シニア候補者を見分ける質問を紹介します。
Q: Navigator 2.0 は Navigator 1.0 では解決できないどのような問題を解決しますか?
Navigator 1.0 は命令型ナビゲーションを使用します:Navigator.push() と Navigator.pop()。アプリはナビゲーション状態を URL として表現できないため、ディープリンクと Web サポートが困難です。Navigator 2.0 は宣言的です:アプリ状態がナビゲーションスタックを決定し、URL はファーストクラスの市民であり、ディープリンクは自動的に機能します。
Q: context.go() と context.push() はいつ使い分けますか?
context.go('/path') は、マッチしたルートまでの現在のナビゲーションスタックを置き換えます。context.push('/path') は、既存のスタックの上に新しいルートを追加します。トップレベルのナビゲーション(タブの切り替え、ホームへの移動)には go() を使用し、戻るナビゲーションを保持しながら詳細に移動する場合には push() を使用します。
Q: タブ切り替え時に状態を保持するにはどうしますか?
StatefulShellRoute.indexedStack() を使用します。各ブランチに個別のナビゲーションスタックを維持し、タブ切り替え時にそれらを保持します。これがないと、タブを切り替えるとサブツリー全体が再構築されます。
Q: ルート間で複雑なオブジェクトを渡すにはどうしますか?
3つのアプローチがあります。まず、パスパラメータで ID を渡し、目的の画面でオブジェクトをフェッチします。次に、GoRouter の extra パラメータを使用してオブジェクトを直接渡します。3つ目は、状態管理ソリューション(Riverpod、Bloc)にオブジェクトを格納し、目的地からアクセスします。最初のアプローチは、URL が共有可能なままなので、ディープリンクに最適です。
// Passing via extra (not deep-linkable)
context.push('/product', extra: productObject);
// Receiving via extra
GoRoute(
path: '/product',
builder: (context, state) {
final product = state.extra as Product?;
return ProductScreen(product: product);
},
),Q: GoRouter 17.5 の onEnter コールバックとは何ですか?
onEnter コールバックは、ルートに入るときに実行され、現在と次のルート状態の両方にアクセスできます。ルートが完全にロードされる前にロジックを実行でき、アナリティクスや条件付きプリロードに便利です。
コード生成による型安全ルーティング
GoRouter は、コード生成による型安全なルーティングをサポートしています。ルートデータクラスを定義し、build_runner にナビゲーションコードを生成させます。
import 'package:go_router/go_router.dart';
part 'routes.g.dart';
<HomeRoute>(path: '/')
class HomeRoute extends GoRouteData {
const HomeRoute();
Widget build(BuildContext context, GoRouterState state) => const HomeScreen();
}
<ProductRoute>(path: '/product/:id')
class ProductRoute extends GoRouteData {
final String id;
const ProductRoute({required this.id});
Widget build(BuildContext context, GoRouterState state) =>
ProductScreen(productId: id);
}dart run build_runner build を実行後、コンパイル時の安全性でナビゲートできます。
// Type-safe navigation
ProductRoute(id: '123').go(context);これにより、ルートパスのタイプミスが排除され、パラメータが常に提供されることが保証されます。
本番アプリのための Flutter ナビゲーション習得
- GoRouter 17.5 は、ディープリンク、ネストされたナビゲーション、または Web サポートを必要とする Flutter アプリに推奨されるナビゲーションソリューションです
- 永続的なボトムナビゲーションバーには
ShellRouteを使用し、各タブのナビゲーションスタックを保持するにはStatefulShellRouteを使用します - プラットフォーム固有のディープリンク(Android App Links、iOS Universal Links)は GoRouter とは別に設定します。GoRouter はルーティングロジックを処理します
- 認証ガードは、ルーターレベルまたは個別のルートレベルで
redirectコールバックを使用して実装します - 面接シナリオでは、宣言的モデルの説明に焦点を当てます:アプリ状態が UI を駆動し、URL は状態から導出され、GoRouter が変換を処理します
- コード生成による型安全ルートは、タイプミスによるパスのランタイムエラーを排除します
- バージョン固有の機能については、Flutter ナビゲーションドキュメント と GoRouter 変更ログ を確認してください
今すぐ練習を始めましょう!
面接シミュレーターと技術テストで知識をテストしましょう。
Flutter のバグを見つけられますか
実際のコード、隠れたバグ、1日1回。アカウントなしで試せます。

執筆
Anthony Fillion-MailletSharpSkill 創業者
10 年以上フルスタック開発に携わっています。SharpSkill を運営し、ここで公開される内容に責任を負っています。
2026年8月23日 更新
タグ
共有
関連記事

2026年のFlutterパフォーマンス最適化:Impeller、リビルド、ベストプラクティス
2026年にFlutterアプリを60または120fpsで安定させる方法。Impeller、規律あるウィジェットのリビルド、RepaintBoundary、DevToolsでのプロファイリングを解説します。

Flutter vs React Native 2026年版:アーキテクチャとパフォーマンスの徹底比較
Flutter 3.44とReact Native 0.86のレンダリングアーキテクチャ、パフォーマンスベンチマーク、開発者体験、採用戦略を詳細に比較します。2026年のクロスプラットフォームモバイル開発におけるフレームワーク選定の指針を提供します。

Flutterテスト完全ガイド2026:ウィジェットテスト、統合テスト、面接対策のベストプラクティス
Flutter のウィジェットテスト、統合テスト、ゴールデンテスト、モック戦略を網羅的に解説。2026年の技術面接で問われる実践的なテストパターンとコード例を紹介します。