Hi, this is my third blog post. This post will explain how to use GoRouter with ShellRoute in a Flutter application.
I assume you're already familiar with GoRouter, but here I will guide you through the entire setup and usage of GoRouter.
GoRouter is a popular navigation library in the Flutter community. It provides a way to handle navigation and deep links seamlessly. Without GoRouter, you would have a hard time implementing navigation using the traditional named routes of Scaffold or MaterialApp.
Here, I will focus on how to use ShellRoute. If you're just starting with GoRouter, I suggest heading to my previous blog post that explains it separately.
https://blog.sophoun.com/flutter-routing-using-gorouter
Let's get into the details.
ShellRoute provides a way to implement sub-navigation in your application, such as a bottom navigation bar or a sidebar for web interfaces.
Imagine you have a login screen for your users. After they log in to the app, they will see a sidebar with various functionalities, allowing them to navigate while maintaining the correct selection state.
The scenario above isn't easy to implement without ShellRoute. Let's look at the code.
I assume you already have a GoRouter defined in your application. Below is how you define ShellRoute in Flutter.
Here is my router.dart file.
dartimport 'package:go_router/go_router.dart'; import 'package:go_router_example/category_page.dart'; import 'package:go_router_example/dashboard_page.dart'; import 'package:go_router_example/home_page.dart'; import 'package:go_router_example/login_page.dart'; final goRouter = GoRouter( initialLocation: "/login", routes: [ GoRoute( path: '/login', builder: (context, state) => const LoginPage(), ), ShellRoute( builder: (context, state, child) => HomePage(child: child), routes: [ GoRoute( path: "/dashboard", builder: (context, state) => const DashboardPage(), ), GoRoute( path: "/category", builder: (context, state) => const CategoryPage(), ), ], ), ], );
As you can see here, I define GoRouter, and the initial location is /login, which points to the GoRoute path /login when the application starts.
I have also defined ShellRoute, which has two children GoRoutes named /dashboard and /category. The builder function of ShellRoute has a child parameter that is passed via callback; this child is the widget of the route that was navigated to. This allows you to design the sub-navigation as you wish within the HomePage.
Here is the code in my home_page.dart
dartimport 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; class HomePage extends StatefulWidget { const HomePage({ super.key, required this.child, }); final Widget child; @override StatecreateState() => _HomePageState(); } class _HomePageState extends State { int selectedIndex = 0; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("Home"), ), body: Row( children: [ NavigationRail( destinations: const [ NavigationRailDestination( icon: Icon(Icons.dashboard), label: Text("Dashboard"), ), NavigationRailDestination( icon: Icon(Icons.category), label: Text("Category"), ), ], selectedIndex: selectedIndex, labelType: NavigationRailLabelType.all, onDestinationSelected: (value) { selectedIndex = value; switch (value) { case 0: context.go("/dashboard"); break; case 1: context.go("/category"); break; default: throw Exception("Unknow destination"); } }, ), Expanded( child: widget.child, ) ], ), ); } }
Here, I have defined a NavigationRail that has two destinations: one for /dashboard and one for /category.
I also have a child widget that is accepted via the constructor and placed as a child. This child widget is passed via ShellRoute when it navigates.
By defining goRouter and HomePage, I can customize the navigation and easily navigate between those children.
Below is the code in my login_page.dart file
dartimport 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; class LoginPage extends StatefulWidget { const LoginPage({super.key}); @override StatecreateState() => _LoginPageState(); } class _LoginPageState extends State { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("Login"), ), body: Center( child: FilledButton( onPressed: () { context.go("/dashboard"); }, child: const Text("Login"), ), ), ); } }
Here is the result and available code.

Source code: go_router_example
That's all for today. If you have any questions or suggestions, please comment. Thank you for reading.