Start Debugging

Flutter における go_router vs auto_route vs Navigator 2.0

go_router と auto_route はどちらも Navigator 2.0 の上に載っています。そのため本当の選択は、宣言的な URL ルーティング vs コード生成による型付きルート vs Router API を自前で書くことのどれを選ぶかです。それぞれの設定例を添えた意思決定マトリクスと、生の Navigator が今でも勝る場面を解説します。

手短な結論:2026 年のほとんどの Flutter アプリでは go_router を使いましょう。これは Flutter チームによってメンテナンスされており、公式ドキュメントが案内しているのもこの選択肢で、最小限の儀式で URL を画面へと変換してくれます。コンパイル時にチェックされる強く型付けされたナビゲーション呼び出しが欲しく、それを得るためにコードジェネレーターを走らせる覚悟があるなら auto_route を選びましょう。生の Navigator 2.0(RouterDelegateRouteInformationParser を自分で実装すること)まで降りていくのは、どちらのパッケージでも表現できない本当に特殊なナビゲーションモデルを持っている場合だけにしてください。というのも、大量のボイラープレートを引き受けることになるからです。そして、アプリが小さくディープリンクや Web URL の要件がないなら、素朴で命令的な Navigator.push でも今なお完璧に良い答えです。

この比較でほとんど誰もが間違えるのは、3 つを並列の選択肢として扱ってしまうことです。そうではありません。「Navigator 2.0」とは Flutter フレームワークの Router API のことで、go_routerauto_route もどちらもその上に構築されています。ですから決めるべきは「パッケージ A vs パッケージ B vs フレームワーク」ではなく、「ルーティングをどの層で書きたいか」なのです。本記事では go_router 17.3.0、auto_route 11.1.0、Flutter 3.44 stable、そして Dart 3.x を使用します。

レイヤーケーキ:「Navigator 2.0」とは実際に何なのか

Flutter には 2 つのナビゲーション API が並んで存在しています。

Navigator 1.0 は、あなたが既に知っている命令的なものです:Navigator.of(context).push(MaterialPageRoute(...))Navigator.pop(context)。スタックのように画面をプッシュしたりポップしたりします。これはシンプルで、ちゃんと動作し、小さなアプリならこれだけで十分です。

Navigator 2.0、より正確には Router API と呼ばれるものは、宣言的です。命令的にプッシュする代わりに、スタック全体を記述する Page オブジェクトのリストをフレームワークに渡し、アプリの状態が変わったときには新しいリストを渡します。それを機能させるのは 2 つの要素です:受け取った URL(RouteInformation)をあなた独自のアプリ固有のデータ型へと変換する RouteInformationParser と、そのデータとアプリの状態を読み取って正しい pages を持つ Navigator を構築する RouterDelegate です。公式の navigation and routing docs は、まさにこの分担を説明しています。

ただし厄介なのは、正しい RouterDelegateRouteInformationParser を手で書くのは大変な作業だということです。パスのパース、ページリストの維持、システムの戻るボタンの処理、ブラウザ URL の同期、そしてディープリンクの復元まで、すべてがあなたの責任になります。Flutter のドキュメントは、この点についてはっきりと述べています:

ルーティングパッケージを使わず、アプリ内のナビゲーションとルーティングを完全に制御したい場合は、RouteInformationParserRouterDelegate をオーバーライドしてください。

その「完全な制御が欲しいなら」という部分が手がかりです。それ以外の全員にとっては、ルーティングパッケージがその仕組みをラップしてくれます。go_routerauto_route も、どちらもそのラッパーです。これらは Navigator 2.0 を利用しているのであって、それと競合しているわけではありません。ですから誰かが「go_router か Navigator 2.0 か?」と尋ねたとき、正直な答えは、go_router ボイラープレートをあなたの代わりに書いてくれた Navigator 2.0 そのものだ、ということになります。

go_router が最適化しているもの:真実の源としての URL

go_router は検証済みの flutter.dev パブリッシャーによって公開されており、Flutter チームによってメンテナンスされています。そのモデルは URL 優先です:パスパターンのルートテーブルを宣言し、ナビゲーションはパスへ移動することの問題になります。ディープリンク、Web URL、そしてアプリ内ナビゲーションのすべてが同じテーブルを通じて解決されます。だからこそ、ディープリンクとボタンタップが同じ画面にたどり着くのです。

最小限のセットアップ:

// Flutter 3.44, go_router 17.3.0
final router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const HomeScreen(),
      routes: [
        GoRoute(
          path: 'orders/:id',
          builder: (context, state) {
            final id = state.pathParameters['id']!;
            return OrderScreen(orderId: id);
          },
        ),
      ],
    ),
  ],
);

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) =>
      MaterialApp.router(routerConfig: router);
}

ナビゲーションは文字列です:

// go_router 17.3.0 - navigate by URL
context.go('/orders/42');   // replace the stack, land on order 42
context.push('/orders/42'); // push on top of the current stack

ここでの強みは、同時に鋭い刃でもあります:ナビゲーションのターゲットは文字列なのです。context.go('/orders/42') はコンパイラーによってチェックされません。パスを打ち間違えたりルートをリネームしたりすると、それに気づくのは実行時です。go_router にはこれに対する答えがあります。go_router_builder による型付きルート がそれで、型付きの GoRouteData クラスを生成してくれるので、OrderRoute(id: 42).go(context) のように呼び出せます。しかしそれは、デフォルトの流儀が文字列であるパッケージに後付けされたオプトインのコード生成であり、実際には多くの go_router のコードベースでは一度も有効化されないままです。

go_router はまた、人々が実際にこれを採用する理由となる 2 つの機能も備えています。ShellRouteStatefulShellRoute.indexedStack は永続的なシェル(タブ切り替えをまたいで生き残るボトムナビゲーションバー)を提供し、トップレベルの redirect コールバックがツリー全体の認証ゲーティングを扱います。シェルとディープリンク設定の完全なウォークスルーが欲しければ、go_router でネストされたルートとディープリンクをセットアップする を参照してください。

複数年にわたる意思決定にとって重要なステータスの注意点が 1 つあります:Flutter チームは go_router を機能完成済み(feature-complete)としてリストしています。彼らの ロードマップの記述 にはこうあります:

このパッケージは機能完成済みと見なされています。Flutter チームの主な焦点は、バグ修正への対応と安定性の確保に置かれます。

これは放棄ではなく安定性として読んでください。つまり、今日あなたが学ぶ API は今後もあなたの足元で変化する可能性が低く、バグ修正は今でも届くということです。また、大きな新機能がコアの go_router の機能としてではなく、コミュニティパッケージとして届く可能性の方が高い、ということでもあります。

auto_route が最適化しているもの:コード生成による型付きルート

auto_route 11.1.0 は Milad Akarie によるコミュニティパッケージです。これは go_router のデフォルトにおけるまさにその弱点、つまり文字列で型付けされたナビゲーションを攻撃します。各ページに @RoutePage() を注釈し、build_runner を走らせると、すべての画面に対して強く型付けされたルートが生成されます。ナビゲーション呼び出しはコンパイラーによってチェックされ、その引数も型付けされます。

// Flutter 3.44, auto_route 11.1.0
@RoutePage()
class OrderScreen extends StatelessWidget {
  const OrderScreen({super.key, required this.orderId});
  final int orderId; // a real int, not a string yanked from a path

  @override
  Widget build(BuildContext context) => const Placeholder();
}

@AutoRouterConfig()
class AppRouter extends RootStackRouter {
  @override
  List<AutoRoute> get routes => [
        AutoRoute(page: HomeRoute.page, initial: true),
        AutoRoute(page: OrderRoute.page, path: '/orders/:id'),
      ];
}

dart run build_runner build の後、ナビゲーションは端から端まで型付けされます:

// auto_route 11.1.0 - typed navigation, checked by the compiler
context.router.push(OrderRoute(orderId: 42)); // orderId is an int

orderId をリネームしたり、その型を変えたり、ルートを削除したりすると、そこへナビゲートするコードがコンパイルできなくなります。それがまさに売り文句のすべてであり、数十の画面と自明でない引数を持つ大規模アプリにとっては、これは本物の、日々の生産性の差になります。オブジェクトをクエリ文字列にシリアライズして手でパースし直す、といったことは決してしなくなります。

auto_route はまた、go_router の各機能に相当するものをファーストクラスで提供しています。ネストされたナビゲーションは子ルートのアウトレットとして AutoRouter ウィジェットを使い、タブ付きナビゲーションは AutoTabsRouter を使います。これは StatefulShellRoute と同じように画面外のタブの状態を保持します。認証ゲーティングにはガードを使います:AutoRouteGuard を継承し、onNavigation を実装して、そのガードをルートごとに、あるいはグローバルにアタッチします。

// auto_route 11.1.0 - a route guard
class AuthGuard extends AutoRouteGuard {
  @override
  void onNavigation(NavigationResolver resolver, StackRouter router) {
    if (isSignedIn) {
      resolver.next(true);
    } else {
      router.push(const LoginRoute());
    }
  }
}

その代償はコードジェネレーターです。auto_route_generatorbuild_runner を dev 依存として追加し、@RoutePage を追加したり変更したりするたびに再生成します。開発中は build_runner watch を走らせます。これは本物の税金です:コールドビルドが遅くなり、生成された .gr.dart ファイルがツリーの中に置かれ、そして物事の同期が崩れたときには生成された出力を削除して再ビルドする必要が時折生じます。型付きナビゲーションがコード生成のステップに見合うかどうかが、この比較全体の中心的なトレードオフです。

意思決定を、マトリクスとして

観点go_router 17.3.0auto_route 11.1.0生の Navigator 2.0
メンテナーFlutter チーム(flutter.dev)コミュニティ(Milad Akarie)Flutter フレームワーク
ナビゲーション呼び出し文字列(context.go('/x'))、builder 経由で型付けはオプトインデフォルトで型付き、コンパイラーがチェック自分で定義する
コード生成なし(型付きルート用にオプション)あり(build_runnerなし
ディープリンク / Web URL組み込み組み込み自分で実装する
永続的なシェル / タブStatefulShellRouteAutoTabsRouter自分で実装する
認証ゲーティングredirect コールバックAutoRouteGuard自分で実装する
ボイラープレート少ない少ないから中程度(さらに生成ファイル)多い
最適な場面ほとんどのアプリ、Web、ディープリンク大規模アプリ、多くの型付き引数、codegen が好き本当にカスタムなナビゲーションモデル

あなたの選択を左右すべき行は「ナビゲーション呼び出し」と「コード生成」です。コンパイル時にチェックされるナビゲーションがビルドステップを正当化するほど重要なら auto_route です。ジェネレーターを走らせたくなく、文字列パスに抵抗がない(または後で go_router_builder にオプトインする)なら go_router です。それ以外のすべては、どちらのパッケージも遜色なくこなします。

生の Navigator 2.0 が正しい選択になるとき(まれに)

制御している感覚を味わうためだけに手書きの RouterDelegate に手を伸ばしてはいけません。それが本当に報われる状況は限られています:

そうしたケースでは、生の API が List<Page> とパース・復元のサイクルに対する完全な制御を与えてくれます。それ以外のすべてにおいては、あなたが書くことになるボイラープレートは、パッケージがすでに書き、テストし、出荷したボイラープレートです。Flutter ドキュメントのガイダンスは明確です:高度なナビゲーションのニーズを持つアプリは「go_router のようなルーティングパッケージを使うべき」であり、生の API は完全な制御を明示的に望む人々のための脱出口として位置づけられています。

忘れないで:小さなアプリなら Navigator 1.0 でも今なお十分

5 画面のアプリが、必要でもなかったルーティングパッケージ、コードジェネレーター、ルートテーブルの抽象化を採用してしまう、という失敗モードがあります。アプリに Web ターゲットがなく、ディープリンクもなく、URL から画面を再構築する要件もないなら、命令的なナビゲーションはレガシーな間違いではなく、サイズの合った適切なツールです:

// Flutter 3.44 - imperative navigation, still correct for simple apps
Navigator.of(context).push(
  MaterialPageRoute<void>(
    builder: (context) => const SecondScreen(),
  ),
);

公式ドキュメントは「複雑なディープリンクを持たない小さなアプリケーション」に対して、これをはっきりと推奨しています。Web ビルド、共有可能なリンク、あるいはタブ切り替えをまたいで生き残らなければならないボトムナビを追加した瞬間に、go_router または auto_route へ移りましょう。それより前ではありません。

それぞれの選択が手渡してくる落とし穴

コミットした後に人々を噛む点がいくつかあります:

ルーティングだけでなく、より広いスタックをまだ決めている最中なら、この同じ「公式で退屈 vs 機能豊富なコミュニティ」という緊張は状態管理にも現れます。それは Provider vs Riverpod vs Bloc で解説しています。そしてプラットフォームの選択そのものについては Flutter vs React Native vs MAUI で解説しています。

実際のプロジェクトとの接触を生き延びる要約はこうです:go_router は公式で、安定していて、URL ネイティブだからこそデフォルトです。auto_route は、コンパイル時にチェックされるナビゲーションがコードジェネレーターに見合う大規模アプリでその価値を発揮します。生の Navigator 2.0 は、ナビゲーションモデルがルートテーブルに収まらないまれなアプリのためのものです。そして素朴な Navigator.push は、これらのどれも必要としない小さなアプリにとって今でも正しい答えです。最も強力な層ではなく、あなたの実際の問題を解く最も低い層を選びましょう。

関連記事

出典

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< 戻る