Why choose the go_router Flutter package?

The go_router Flutter package offers a declarative API for handling navigation, deep links, and nested routes without writing boilerplate code. It fits naturally into clean architecture because you can isolate routing logic behind a service or helper class, keeping UI widgets free from direct package calls.

Tip: Review the official pub.dev page and the package changelog before locking a version for production.

Installation

Add the package to your project with the Flutter CLI:

Code
flutter pub add go_router

After the command completes, run flutter pub get to fetch the dependency.

Basic setup – a beginner‑friendly example

Below is a minimal, runnable example that demonstrates a home screen, a details screen, and a nested settings screen using go_router.

Dart / Flutter
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // Define the router configuration in one place.
  static final GoRouter _router = GoRouter(
    routes: [
      GoRoute(
        path: '/',
        name: 'home',
        builder: (context, state) => const HomeScreen(),
        routes: [
          GoRoute(
            path: 'details/:id',
            name: 'details',
            builder: (context, state) {
              final id = state.params['id'];
              return DetailsScreen(itemId: id ?? 'unknown');
            },
          ),
          GoRoute(
            path: 'settings',
            name: 'settings',
            builder: (context, state) => const SettingsScreen(),
          ),
        ],
      ),
    ],
    // Optional: handle unknown routes.
    errorBuilder: (context, state) => const Scaffold(
      body: Center(child: Text('Page not found')),
    ),
  );

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'go_router Demo',
      routerConfig: _router,
      theme: ThemeData(primarySwatch: Colors.blue),
    );
  }
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Home')),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            ElevatedButton(
              onPressed: () => context.go('/details/42'),
              child: const Text('Open Details (id=42)'),
            ),
            const SizedBox(height: 12),
            ElevatedButton(
              onPressed: () => context.go('/settings'),
              child: const Text('Open Settings'),
            ),
          ],
        ),
      ),
    );
  }
}

class DetailsScreen extends StatelessWidget {
  final String itemId;
  const DetailsScreen({Key? key, required this.itemId}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Details – $itemId')),
      body: Center(child: Text('Showing details for item $itemId')),
    );
  }
}

class SettingsScreen extends StatelessWidget {
  const SettingsScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Settings')),
      body: const Center(child: Text('Settings go here')),
    );
  }
}

This example demonstrates:

  • Declarative route definitions with GoRoute.
  • Path parameters (e.g., :id).
  • Nested routes for a clean URL hierarchy.
  • Programmatic navigation using context.go().

Integrating go_router behind an abstraction layer

In larger codebases, keep routing calls out of UI widgets. Create a small service that exposes navigation methods:

Code
class NavigationService {
  final GoRouter _router;
  NavigationService(this._router);

  void goToHome() => _router.go('/');
  void goToDetails(String id) => _router.go('/details/$id');
  void goToSettings() => _router.go('/settings');
}

Inject NavigationService via your preferred DI framework and call it from view models or controllers instead of using context.go() directly.

Setup notes and best practices

  • Version pinning: Use a caret or exact version (e.g., go_router: ^7.0.0) and monitor the changelog for breaking changes.
  • Deep linking: The package automatically parses URL fragments on mobile and web. Test with adb shell am start -a android.intent.action.VIEW -d "myapp://details/99" on Android.
  • Error handling: Provide an errorBuilder to show a friendly UI for unknown routes.
  • State restoration: If you need state restoration, enable it in MaterialApp.router and pass a routerDelegate that supports restoration.

Mistakes to avoid

  • Mixing Navigator.push with go_router calls – stick to one navigation strategy.
  • Hard‑coding route strings throughout the app – use named routes or a constants file.
  • Placing GoRouter inside a widget that rebuilds frequently – define it as a top‑level static or inside a provider that lives for the app’s lifetime.
  • Ignoring platform‑specific URL schemes – verify that your Android AndroidManifest.xml and iOS Info.plist contain the correct intent filters.
Remember: The go_router API evolves. Always check the changelog when upgrading major versions.

Searchable terms developers often use

  • go_router navigation
  • Flutter deep linking
  • Nested routes Flutter
  • Declarative routing Flutter
  • MaterialApp.router example

FAQ

  • Q: Does go_router support web URL synchronization?
    A: Yes. When you use MaterialApp.router, the package updates the browser address bar and parses incoming URLs automatically.
  • Q: Can I use go_router with existing Navigator 1.0 code?
    A: You can, but mixing the two approaches can lead to unexpected stack behavior. It’s recommended to migrate fully to go_router or keep the old navigation isolated in a separate module.
  • Q: How do I pass complex objects between routes?
    A: Prefer passing simple primitives via the URL and retrieve richer data from a shared state (e.g., a provider, Riverpod, or Bloc) inside the destination screen.
  • Q: Is go_router compatible with Flutter’s state restoration?
    A: Starting with version 7, the package offers optional support for state restoration. Enable it by setting routerDelegate: _router.routerDelegate and configuring restorationScopeId on your MaterialApp.
  • Q: Where can I find official documentation?
    A: The primary source is the package page on pub.dev: https://pub.dev/packages/go_router. The README contains the latest API usage and migration guides.

Frequently Asked Questions

Does go_router support web URL synchronization?

Yes. When you use MaterialApp.router, the package updates the browser address bar and parses incoming URLs automatically.

Can I use go_router with existing Navigator 1.0 code?

You can, but mixing the two approaches can lead to unexpected stack behavior. It’s recommended to migrate fully to go_router or keep the old navigation isolated in a separate module.

How do I pass complex objects between routes?

Prefer passing simple primitives via the URL and retrieve richer data from a shared state (e.g., a provider, Riverpod, or Bloc) inside the destination screen.

Is go_router compatible with Flutter’s state restoration?

Starting with version 7, the package offers optional support for state restoration. Enable it by setting routerDelegate: _router.routerDelegate and configuring restorationScopeId on your MaterialApp.

Where can I find official documentation?

The primary source is the package page on pub.dev: https://pub.dev/packages/go_router. The README contains the latest API usage and migration guides.