Navigating Flutter Routes with GoRouter: Configuration Patterns and Best Practices
Flutter developers often encounter challenges with navigation, especially when designing a consistent and predictable user experience in complex applications. GoRouter simplifies Flutter’s navigation, especially when it comes to deep linking and handling web-based routes, but understanding how it manages the navigation stack is crucial to avoid common pitfalls.
This article dives into two configurations in GoRouter — flat routes and nested (sub-) routes — showing how each impacts navigation behavior and when to use each for different types of applications.
Understanding the Navigation Stack in GoRouter
In GoRouter, routes are managed through a navigation stack. Each screen pushed onto the stack appears on top of the previous one, allowing users to “pop” or go back to the previous screen if needed. However, the way routes are configured in GoRouter can change how this stack behaves.
Here’s an example with two configurations:
- Flat route configuration (Configuration 1)
- Nested route configuration (Configuration 2)
Configuration 1: Flat Routes
In this configuration, each route is defined at the root level:
final GoRouter _router = GoRouter(
routes: <RouteBase>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) {
return const HomeScreen();
},
),
GoRoute(
path: '/details',
builder: (BuildContext context, GoRouterState state) {
return const DetailsScreen();
},
),
],
);
Behavior of Flat Routes:
- Navigation Replacement: When navigating from
/
(HomeScreen) to/details
(DetailsScreen) usingcontext.go('/details')
, the current screen (HomeScreen
) is replaced withDetailsScreen
. - No Back Navigation: Since
DetailsScreen
replacesHomeScreen
in the stack, attempting to usecontext.pop()
onDetailsScreen
will result in an error because the stack has no previous screen to return to.
In other words, if you use context.go('/details')
, GoRouter does not add DetailsScreen
on top of HomeScreen
. Instead, it replaces HomeScreen
, effectively limiting back navigation.
Configuration 2: Nested/Sub-Routes
In this configuration, /details
is defined as a sub-route under /
, making it a child route of HomeScreen
.
final GoRouter _router = GoRouter(
routes: <RouteBase>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) {
return const HomeScreen();
},
routes: <RouteBase>[
GoRoute(
path: 'details',
builder: (BuildContext context, GoRouterState state) {
return const DetailsScreen();
},
),
],
),
],
);
Behavior of Nested Routes:
- Navigation Adds to Stack: With this nested route setup, navigating with
context.go('/details')
addsDetailsScreen
on top ofHomeScreen
in the stack. - Back Navigation Works: Since
DetailsScreen
is added as a child ofHomeScreen
, callingcontext.pop()
onDetailsScreen
will correctly return the user toHomeScreen
without an error.
Choosing Between Flat and Nested Routes
- Use Flat Routes when you want to replace the current screen entirely and don’t need back navigation (e.g., replacing a splash screen with a home screen).
- Use Nested Routes when you need to retain the previous screen in the stack to allow back navigation, such as when navigating between detail and parent screens.
context.go
vs. context.push
Two primary methods for navigation in GoRouter are context.go
and context.push
.
context.go
: Directly navigates to the target route, often replacing the current screen in the stack. It's optimal for deep linking but requires careful route configuration to avoid unexpected stack behavior. This method works best with flat routes.context.push
: Adds a new screen to the stack, allowing back navigation without needing sub-routes. Ideal for navigation within an app where each screen needs to maintain a navigable stack. However,context.push
may be less suited for deep linking thancontext.go
.
Best Practices for GoRouter Configurations
- Plan the Navigation Stack: Decide whether users should be able to return to a previous screen. If back navigation is essential, consider nested routes or use
push
instead ofgo
. - Leverage Nested Routes for Multi-Level Navigation: For apps with a main screen and sub-screens (like settings and profile), nested routes ensure a smoother experience and consistent back navigation.
- Use
context.go
Sparingly for Replacements: If you want a new screen to replace the current one (such as when transitioning from login to home),context.go
is a good option. Otherwise, favorpush
for adding screens to the stack. - Optimize for Deep Linking: If your app requires deep linking,
context.go
is often better suited. However, remember that back navigation may need special handling if routes aren’t nested.