Tools: GoRouter Advanced Tutorial 2026: Bottom Nav, Nested Routes, Auth Redirects & Typed Navigation ๐
I remember being asked early in my career as a software engineer whether I could persist the bottom navigation so it would stay on the screen regardless of which page the user navigated to. My colleague and I explained that it's not possible, given how we implemented the bottom navigation system; it simply wasn't feasible. A few years later, I started using AutoRoute, and later moved to GoRouter on projects to streamline advanced navigation, nested routes for deep links, persistent bottom navigation, and much more. That's what inspired this tutorial on GoRouter. GoRouter Features: Deep linking, ShellRoute for tabs, typed routes via builder. Run dart run build_runner build for typed helpers. Create a new project or use an existing one. Router file (lib/router/app_router.dart): Bottom nav shell: StatefulShellRoute.indexedStack for branches (Home/Profile/Settings) to preserve state. Navigation: context.go('/details/123') or DetailsRoute(id: '123').go(context) for typed. Full Source Code ๐ - Show some โค๏ธ by starring โญ the repo and follow me ๐! https://github.com/techwithsam/gorouter_tutorial I hope you've learn something incredible. Press that follow button if you're not following me yet. Also, make sure to subscribe to the newsletter so you're notified when I publish a new article. ๐ Let's Connect ๐ โ Twitter | LinkedIn. Join my Community ๐จโ๐ป๐จโ๐ป on Discord. Subscribe to my YouTube channel | and also to the Medium newsletter in the input box above๐ or below๐. Happy Building! ๐ฅฐ๐จโ๐ป Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to ? It will become hidden in your post, but will still be visible via the comment's permalink. as well , this person and/or CODE_BLOCK:
dependencies: go_router: ^17.1.0 flutter_riverpod: ^2.6.1 dev_dependencies: build_runner: ^2.6.0 go_router_builder: ^4.0.1 CODE_BLOCK:
dependencies: go_router: ^17.1.0 flutter_riverpod: ^2.6.1 dev_dependencies: build_runner: ^2.6.0 go_router_builder: ^4.0.1 CODE_BLOCK:
dependencies: go_router: ^17.1.0 flutter_riverpod: ^2.6.1 dev_dependencies: build_runner: ^2.6.0 go_router_builder: ^4.0.1 COMMAND_BLOCK:
import 'package:go_router/go_router.dart';
import 'package:flutter/material.dart'; // Typed route data example
part 'app_router.g.dart'; @TypedGoRoute<HomeRoute>(path: '/')
class HomeRoute extends GoRouteData { @override Widget build(BuildContext context, GoRouterState state) => const HomeScreen();
} @TypedGoRoute<DetailsRoute>(path: '/details/:id')
class DetailsRoute extends GoRouteData { final String id; DetailsRoute(this.id); @override Widget build(BuildContext context, GoRouterState state) => DetailsScreen(id: id);
} // Shell for bottom nav
final _rootNavigatorKey = GlobalKey<NavigatorState>();
final _shellNavigatorKey = GlobalKey<NavigatorState>(); final router = GoRouter( navigatorKey: _rootNavigatorKey, initialLocation: '/', redirect: (context, state) { // Riverpod auth check example final isLoggedIn = false; // ref.watch(authProvider) if (!isLoggedIn && state.uri.toString() != '/login') { return '/login'; } return null; }, routes: [ ShellRoute( builder: (context, state, child) => ScaffoldWithNavBar(child: child), routes: [ GoRoute( path: '/', builder: (context, state) => const HomeScreen(), routes: [ GoRoute( path: 'details/:id', builder: (context, state) => DetailsScreen(id: state.pathParameters['id']!), ), ], ), GoRoute(path: '/profile', builder: (_, __) => const ProfileScreen()), GoRoute(path: '/settings', builder: (_, __) => const SettingsScreen()), ], ), GoRoute(path: '/login', builder: (_, __) => const LoginScreen()), ], errorBuilder: (context, state) => ErrorScreen(state.error),
); COMMAND_BLOCK:
import 'package:go_router/go_router.dart';
import 'package:flutter/material.dart'; // Typed route data example
part 'app_router.g.dart'; @TypedGoRoute<HomeRoute>(path: '/')
class HomeRoute extends GoRouteData { @override Widget build(BuildContext context, GoRouterState state) => const HomeScreen();
} @TypedGoRoute<DetailsRoute>(path: '/details/:id')
class DetailsRoute extends GoRouteData { final String id; DetailsRoute(this.id); @override Widget build(BuildContext context, GoRouterState state) => DetailsScreen(id: id);
} // Shell for bottom nav
final _rootNavigatorKey = GlobalKey<NavigatorState>();
final _shellNavigatorKey = GlobalKey<NavigatorState>(); final router = GoRouter( navigatorKey: _rootNavigatorKey, initialLocation: '/', redirect: (context, state) { // Riverpod auth check example final isLoggedIn = false; // ref.watch(authProvider) if (!isLoggedIn && state.uri.toString() != '/login') { return '/login'; } return null; }, routes: [ ShellRoute( builder: (context, state, child) => ScaffoldWithNavBar(child: child), routes: [ GoRoute( path: '/', builder: (context, state) => const HomeScreen(), routes: [ GoRoute( path: 'details/:id', builder: (context, state) => DetailsScreen(id: state.pathParameters['id']!), ), ], ), GoRoute(path: '/profile', builder: (_, __) => const ProfileScreen()), GoRoute(path: '/settings', builder: (_, __) => const SettingsScreen()), ], ), GoRoute(path: '/login', builder: (_, __) => const LoginScreen()), ], errorBuilder: (context, state) => ErrorScreen(state.error),
); COMMAND_BLOCK:
import 'package:go_router/go_router.dart';
import 'package:flutter/material.dart'; // Typed route data example
part 'app_router.g.dart'; @TypedGoRoute<HomeRoute>(path: '/')
class HomeRoute extends GoRouteData { @override Widget build(BuildContext context, GoRouterState state) => const HomeScreen();
} @TypedGoRoute<DetailsRoute>(path: '/details/:id')
class DetailsRoute extends GoRouteData { final String id; DetailsRoute(this.id); @override Widget build(BuildContext context, GoRouterState state) => DetailsScreen(id: id);
} // Shell for bottom nav
final _rootNavigatorKey = GlobalKey<NavigatorState>();
final _shellNavigatorKey = GlobalKey<NavigatorState>(); final router = GoRouter( navigatorKey: _rootNavigatorKey, initialLocation: '/', redirect: (context, state) { // Riverpod auth check example final isLoggedIn = false; // ref.watch(authProvider) if (!isLoggedIn && state.uri.toString() != '/login') { return '/login'; } return null; }, routes: [ ShellRoute( builder: (context, state, child) => ScaffoldWithNavBar(child: child), routes: [ GoRoute( path: '/', builder: (context, state) => const HomeScreen(), routes: [ GoRoute( path: 'details/:id', builder: (context, state) => DetailsScreen(id: state.pathParameters['id']!), ), ], ), GoRoute(path: '/profile', builder: (_, __) => const ProfileScreen()), GoRoute(path: '/settings', builder: (_, __) => const SettingsScreen()), ], ), GoRoute(path: '/login', builder: (_, __) => const LoginScreen()), ], errorBuilder: (context, state) => ErrorScreen(state.error),
); CODE_BLOCK:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'router/app_router.dart'; void main() { runApp(const MyApp());
} class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { return MaterialApp.router( title: 'Advanced GoRouter Tutorial', theme: ThemeData( useMaterial3: true, colorSchemeSeed: Colors.blue, ), routerConfig: router, ); }
} CODE_BLOCK:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'router/app_router.dart'; void main() { runApp(const MyApp());
} class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { return MaterialApp.router( title: 'Advanced GoRouter Tutorial', theme: ThemeData( useMaterial3: true, colorSchemeSeed: Colors.blue, ), routerConfig: router, ); }
} CODE_BLOCK:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'router/app_router.dart'; void main() { runApp(const MyApp());
} class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { return MaterialApp.router( title: 'Advanced GoRouter Tutorial', theme: ThemeData( useMaterial3: true, colorSchemeSeed: Colors.blue, ), routerConfig: router, ); }
}