Skip to content
Blog

go_router: The De Facto Standard for Flutter Routing in 2026

go_router dominates Flutter routing with 1.28M+ monthly downloads. Learn declarative routing, nested navigation, deep linking, and authentication guards.

Published on • September 15, 2026

AI Assistant

go_router has become the undisputed routing solution for Flutter, with over 1.28 million monthly downloads on pub.dev. Its declarative API, deep linking support, and nested navigation patterns make it the default choice for production Flutter apps.

Why go_router won

Flutter’s built-in Navigator 2 API is powerful but verbose. go_router wraps it in a declarative, URL-driven API that’s easier to reason about:

final router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => HomeScreen(),
    ),
    GoRoute(
      path: '/user/:id',
      builder: (context, state) => UserScreen(
        id: state.pathParameters['id']!,
      ),
    ),
  ],
);

No more Navigator.push with type arguments. Routes are URLs, and parameters are part of the path.

Basic route configuration

Define routes as a tree structure:

final router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => HomeScreen(),
      routes: [
        GoRoute(
          path: 'dashboard',
          builder: (context, state) => DashboardScreen(),
        ),
        GoRoute(
          path: 'settings',
          builder: (context, state) => SettingsScreen(),
        ),
      ],
    ),
    GoRoute(
      path: '/login',
      builder: (context, state) => LoginScreen(),
    ),
  ],
);

Nested routes create URL paths like /dashboard and /settings under the root route.

Shell routes for nested navigation

Shell routes preserve layout across nested navigations:

final router = GoRouter(
  routes: [
    ShellRoute(
      builder: (context, state, child) => ScaffoldWithNav(child: child),
      routes: [
        GoRoute(path: '/home', builder: (_, __) => HomeScreen()),
        GoRoute(path: '/profile', builder: (_, __) => ProfileScreen()),
      ],
    ),
  ],
);

The ScaffoldWithNav widget stays mounted while /home and /profile swap underneath.

Deep linking

go_router handles deep links automatically:

// Android: android/app/src/main/AndroidManifest.xml
<intent-filter>
  <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="myapp.com" />
</intent-filter>

// iOS: ios/Runner/Info.plist
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array><string>myapp</string></array>
  </dict>
</array>

When a user taps https://myapp.com/user/123, go_router navigates to the matching route.

Redirect guards for authentication

Protect routes with redirect logic:

final router = GoRouter(
  redirect: (context, state) {
    final isLoggedIn = AuthService.of(context).isLoggedIn;
    final isLoggingIn = state.matchedLocation == '/login';

    if (!isLoggedIn && !isLoggingIn) return '/login';
    if (isLoggedIn && isLoggingIn) return '/';
    return null; // No redirect
  },
  routes: [
    GoRoute(path: '/', builder: (_, __) => HomeScreen()),
    GoRoute(path: '/login', builder: (_, __) => LoginScreen()),
    GoRoute(path: '/dashboard', builder: (_, __) => DashboardScreen()),
  ],
);

The redirect function runs on every navigation, enforcing authentication rules declaratively.

Programmatic navigation

Navigate programmatically with context.go and context.push:

// Replace entire navigation stack
context.go('/dashboard');

// Push onto current stack
context.push('/user/123');

// Push with extra data
context.push('/order', extra: OrderRequest(items: cart.items));

// Pop back
context.pop();

// Replace current route
context.go('/success');

Type-safe route parameters

Avoid stringly-typed parameters with route extensions:

extension UserRoutes on GoRouterHelper {
  void pushUser(String id) => push('/user/$id');
  void goHome() => go('/');
}

// Usage
context.pushUser('123');

Error handling

Handle unknown routes gracefully:

final router = GoRouter(
  errorBuilder: (context, state) => Scaffold(
    body: Center(
      child: Text('Page not found: ${state.error}'),
    ),
  ),
);

URL strategies for web

Configure URL strategies for Flutter Web:

final router = GoRouter(
  urlPathStrategy: UrlPathStrategy.path, // /path instead of #/hash
  routes: [...],
);

Platform-aware routing

Adapt routing behavior per platform:

final router = GoRouter(
  routes: [...],
  platformGuards: [
    if (kIsWeb) WebGuard(), // Web-specific guard
    if (!kIsWeb) MobileGuard(), // Mobile-specific guard
  ],
);

Comparison with alternatives

Featurego_routerauto_routeGetX
Declarative✅✅❌
Deep linking✅✅Limited
Nested navigation✅✅❌
Type-safe paramsVia extensions✅❌
Community supportExcellentGoodDeclining
MaintenanceActiveActiveUncertain

go_router’s combination of declarative API, deep linking, and community support makes it the standard choice for Flutter routing in 2026.