Start Debugging

go_router vs. auto_route vs. Navigator 2.0 in Flutter

go_router und auto_route setzen beide auf Navigator 2.0 auf, die eigentliche Wahl lautet also: deklaratives URL-Routing vs. code-generierte typisierte Routen vs. die Router-API selbst schreiben. Eine Entscheidungsmatrix mit Konfiguration für jede Option, und wann der reine Navigator immer noch gewinnt.

Kurze Antwort: Für die meisten Flutter-Apps im Jahr 2026 verwenden Sie go_router. Es wird vom Flutter-Team gepflegt, es ist die Option, auf die Sie die offizielle Dokumentation verweist, und es macht aus URLs Screens mit dem geringsten Aufwand. Greifen Sie zu auto_route, wenn Sie zur Kompilierzeit geprüfte, stark typisierte Navigationsaufrufe möchten und bereit sind, dafür einen Code-Generator laufen zu lassen. Steigen Sie nur dann auf den reinen Navigator 2.0 hinab (indem Sie RouterDelegate und RouteInformationParser selbst implementieren), wenn Sie ein wirklich ungewöhnliches Navigationsmodell haben, das keines der beiden Pakete ausdrückt, denn Sie handeln sich damit eine Menge Boilerplate ein. Und wenn Ihre App klein ist und keine Deep-Link- oder Web-URL-Anforderungen hat, ist das schlichte imperative Navigator.push immer noch eine völlig gute Antwort.

Was in diesem Vergleich fast alle falsch machen: Sie behandeln die drei als parallele Optionen. Das sind sie nicht. “Navigator 2.0” ist die Router-API des Flutter-Frameworks, und sowohl go_router als auch auto_route bauen darauf auf. Die Entscheidung lautet also nicht “Paket A vs. Paket B vs. das Framework”, sondern “auf welcher Ebene möchte ich mein Routing schreiben”. Dieser Beitrag verwendet go_router 17.3.0, auto_route 11.1.0, Flutter 3.44 stable und Dart 3.x.

Die Schichttorte: was “Navigator 2.0” tatsächlich ist

Flutter hat zwei Navigations-APIs, die nebeneinander existieren.

Navigator 1.0 ist die imperative, die Sie bereits kennen: Navigator.of(context).push(MaterialPageRoute(...)) und Navigator.pop(context). Sie schieben und entfernen Screens wie einen Stapel. Es ist einfach und es funktioniert, und für eine kleine App ist es alles, was Sie brauchen.

Navigator 2.0, genauer die Router-API genannt, ist deklarativ. Statt imperativ zu pushen, geben Sie dem Framework eine Liste von Page-Objekten, die den gesamten Stapel beschreiben, und wenn sich der Zustand Ihrer App ändert, übergeben Sie eine neue Liste. Zwei Teile lassen das funktionieren: ein RouteInformationParser, der eine eingehende URL (RouteInformation) in Ihren eigenen app-spezifischen Datentyp verwandelt, und ein RouterDelegate, der diese Daten plus den Zustand Ihrer App liest und den Navigator mit den richtigen pages aufbaut. Die offizielle Dokumentation zu Navigation und Routing beschreibt genau diese Aufteilung.

Der Haken ist, dass es viel Arbeit ist, einen korrekten RouterDelegate und RouteInformationParser von Hand zu schreiben. Sie sind verantwortlich für das Parsen von Pfaden, das Pflegen der Seitenliste, das Behandeln der System-Zurück-Taste, das Synchronisieren von Browser-URLs und das Wiederherstellen von Deep Links. Die Flutter-Dokumentation ist da direkt:

If you prefer not to use a routing package and would like full control over navigation and routing in your app, override RouteInformationParser and RouterDelegate.

Dieses “if you prefer full control” ist das entscheidende Signal. Für alle anderen umhüllt ein Routing-Paket diese Maschinerie. go_router und auto_route sind beide diese Hülle. Sie konsumieren Navigator 2.0, sie konkurrieren nicht mit ihm. Wenn also jemand fragt “go_router oder Navigator 2.0?”, lautet die ehrliche Antwort, dass go_router Navigator 2.0 ist, mit der Boilerplate, die bereits für Sie geschrieben wurde.

Worauf go_router optimiert: URLs als Quelle der Wahrheit

go_router wird vom verifizierten Publisher flutter.dev veröffentlicht und vom Flutter-Team gepflegt. Sein Modell ist URL-first: Sie deklarieren eine Routentabelle aus Pfadmustern, und Navigation ist eine Frage des Wechsels zu einem Pfad. Deep Links, Web-URLs und In-App-Navigation lösen sich alle über dieselbe Tabelle auf, weshalb ein Deep Link und ein Button-Tap auf demselben Screen landen.

Ein minimales Setup:

// 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);
}

Navigation ist ein String:

// 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

Die Stärke hier ist zugleich die scharfe Kante: Navigationsziele sind Strings. context.go('/orders/42') wird nicht vom Compiler geprüft. Vertippen Sie sich im Pfad oder benennen Sie eine Route um, erfahren Sie es zur Laufzeit. go_router hat eine Antwort, typisierte Routen über go_router_builder, das typisierte GoRouteData-Klassen generiert, sodass Sie OrderRoute(id: 42).go(context) statt eines Strings aufrufen können. Aber das ist eine optionale Code-Generierung, die auf ein Paket aufgesetzt wird, dessen Standard-Idiom Strings sind, und in der Praxis schalten viele go_router-Codebasen sie nie ein.

go_router besitzt außerdem die zwei Funktionen, für die Leute es tatsächlich einsetzen. ShellRoute und StatefulShellRoute.indexedStack geben Ihnen eine persistente Hülle (eine untere Navigationsleiste, die Tab-Wechsel übersteht), und ein redirect-Callback auf oberster Ebene übernimmt das Auth-Gating für den gesamten Baum. Wenn Sie den vollständigen Durchgang zu Hüllen und Deep-Link-Konfiguration möchten, siehe Nested Routes und Deep Links mit go_router einrichten.

Eine Statusanmerkung, die für eine mehrjährige Entscheidung zählt: Das Flutter-Team führt go_router als feature-complete. Ihre Roadmap-Aussage lautet:

This package is considered feature-complete. The Flutter team’s primary focus will be on addressing bug fixes and ensuring stability.

Lesen Sie das als Stabilität, nicht als Aufgabe. Es bedeutet, dass die API, die Sie heute lernen, wahrscheinlich nicht unter Ihnen weg mutiert, und Bugfixes weiterhin landen. Es bedeutet auch, dass große neue Fähigkeiten eher als Community-Pakete ankommen als als Kernfunktionen von go_router.

Worauf auto_route optimiert: typisierte Routen aus Code-Generierung

auto_route 11.1.0 ist ein Community-Paket von Milad Akarie. Es greift genau die Schwäche im Standard von go_router an: die stringbasierte Navigation. Sie annotieren jede Seite mit @RoutePage(), lassen build_runner laufen, und es generiert eine stark typisierte Route für jeden Screen. Navigationsaufrufe werden dann vom Compiler geprüft, und ihre Argumente sind typisiert.

// 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'),
      ];
}

Nach dart run build_runner build ist die Navigation von Ende zu Ende typisiert:

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

Benennen Sie orderId um, ändern Sie seinen Typ oder löschen Sie die Route, und der Code, der dorthin navigiert, kompiliert nicht mehr. Das ist der ganze Kern der Sache, und für eine große App mit dutzenden Screens und nicht-trivialen Argumenten ist es ein echter, täglicher Produktivitätsunterschied. Sie serialisieren nie ein Objekt in einen Query-String und parsen es von Hand wieder heraus.

auto_route liefert außerdem erstklassige Entsprechungen der go_router-Funktionen. Nested Navigation verwendet ein AutoRouter-Widget als Auslass für Kindrouten, tab-basierte Navigation verwendet AutoTabsRouter, das den Zustand von Tabs außerhalb des Bildschirms so bewahrt, wie es StatefulShellRoute tut. Auth-Gating verwendet Guards: Sie leiten von AutoRouteGuard ab, implementieren onNavigation und hängen den Guard pro Route oder global an.

// 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());
    }
  }
}

Der Preis ist der Code-Generator. Sie fügen auto_route_generator und build_runner als Dev-Abhängigkeiten hinzu, und jedes Mal, wenn Sie ein @RoutePage hinzufügen oder ändern, generieren Sie neu. In der aktiven Entwicklung lassen Sie build_runner watch laufen. Das ist eine echte Steuer: langsamere Cold Builds, eine generierte .gr.dart-Datei in Ihrem Baum und die gelegentliche Notwendigkeit, generierte Ausgaben zu löschen und neu zu kompilieren, wenn die Dinge aus dem Takt geraten. Ob typisierte Navigation einen Code-Generierungsschritt wert ist, ist der zentrale Kompromiss in diesem ganzen Vergleich.

Die Entscheidung, als Matrix

Aspektgo_router 17.3.0auto_route 11.1.0Reiner Navigator 2.0
MaintainerFlutter-Team (flutter.dev)Community (Milad Akarie)Flutter-Framework
NavigationsaufrufeStrings (context.go('/x')), typisiert optional über BuilderTypisiert, standardmäßig compilergeprüftSie definieren es
Code-GenerierungNein (optional für typisierte Routen)Ja (build_runner)Nein
Deep Links / Web-URLsEingebautEingebautSie implementieren es
Persistente Hülle / TabsStatefulShellRouteAutoTabsRouterSie implementieren es
Auth-Gatingredirect-CallbackAutoRouteGuardSie implementieren es
BoilerplateGeringGering bis mittel (plus generierte Dateien)Hoch
Am besten wennDie meisten Apps; Web; Deep LinksGroße App, viele typisierte Argumente, Sie mögen CodegenWirklich individuelles Navigationsmodell

Die Zeilen, die Ihre Wahl bestimmen sollten, sind “Navigationsaufrufe” und “Code-Generierung”. Wenn zur Kompilierzeit geprüfte Navigation wichtig genug ist, um einen Build-Schritt zu rechtfertigen, dann auto_route. Wenn Sie lieber keinen Generator laufen lassen und mit String-Pfaden zufrieden sind (oder sich später für go_router_builder entscheiden), dann go_router. Alles andere beherrschen beide Pakete kompetent.

Wann der reine Navigator 2.0 die richtige Wahl ist (selten)

Greifen Sie nicht zu einem handgeschriebenen RouterDelegate, nur um sich in Kontrolle zu fühlen. Die Situationen, in denen es sich wirklich auszahlt, sind eng begrenzt:

Für diese Fälle gibt Ihnen die reine API totale Kontrolle über die List<Page> und den Parse-und-Wiederherstellungs-Zyklus. Für alles andere ist die Boilerplate, die Sie schreiben würden, Boilerplate, die die Pakete bereits geschrieben, getestet und ausgeliefert haben. Die Anleitung der Flutter-Dokumentation ist eindeutig: Apps mit anspruchsvollen Navigationsanforderungen “should use a routing package such as go_router”, und die reine API wird als die Notausstiegsluke für Leute dargestellt, die explizit volle Kontrolle wollen.

Nicht vergessen: für eine kleine App ist Navigator 1.0 immer noch in Ordnung

Es gibt einen Fehlermodus, bei dem eine App mit fünf Screens ein Routing-Paket, einen Code-Generator und eine Routentabellen-Abstraktion übernimmt, die sie nie brauchte. Wenn Ihre App kein Web-Ziel, keine Deep Links und keine Anforderung hat, einen Screen aus einer URL zu rekonstruieren, ist imperative Navigation kein Altlasten-Fehler, sondern das richtig dimensionierte Werkzeug:

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

Die offizielle Dokumentation segnet dies ausdrücklich ab für “small applications without complex deep linking”. In dem Moment, in dem Sie einen Web-Build, teilbare Links oder eine untere Navigationsleiste hinzufügen, die Tab-Wechsel überstehen muss, wechseln Sie zu go_router oder auto_route. Nicht früher.

Die Fallstricke, die jede Wahl Ihnen mitgibt

Ein paar Dinge beißen Leute, nachdem sie sich festgelegt haben:

Wenn Sie noch Ihren breiteren Stack entscheiden statt nur das Routing, zeigt sich dieselbe Spannung “offiziell-und-langweilig vs. funktionsreich-Community” auch beim State-Management, das ich in Provider vs. Riverpod vs. Bloc durchgehe, und bei der Plattformwahl selbst in Flutter vs. React Native vs. MAUI.

Die Zusammenfassung, die den Kontakt mit einem echten Projekt übersteht: go_router ist der Standard, weil es offiziell, stabil und URL-nativ ist. auto_route verdient sich seinen Platz bei großen Apps, in denen zur Kompilierzeit geprüfte Navigation einen Code-Generator wert ist. Der reine Navigator 2.0 ist für die seltene App, deren Navigationsmodell nicht in eine Routentabelle passt. Und das schlichte Navigator.push ist immer noch die korrekte Antwort für eine kleine App, die nichts davon braucht. Wählen Sie die niedrigste Ebene, die Ihr tatsächliches Problem löst, nicht die mächtigste.

Ähnliche Beiträge

Quellen

Comments

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

< Zurück