dev-resources.site
for different kinds of informations.
Best Practices for Managing Route Names and Paths with go_router in Flutter
Are you tired of typing out long, hard-coded paths when navigating between screens in your Flutter app? Or worried about circular imports while organizing your routes across multiple features? In this post, we’ll explore how to manage route names and paths using go_router
in a clean and maintainable way.
1. Why Avoid Hard-Coding Routes?
Using strings like context.go("/some/really/long/path/42")
all over your codebase can be error-prone and difficult to maintain. If you ever decide to change "/some/really/long/path"
to "/shorter/path"
, you’ll have to hunt down every place that calls it.
Instead, define named routes or centralized route constants. This helps you:
- Avoid typos.
- Keep the code DRY (Don’t Repeat Yourself).
- Make future changes to your routes simpler.
2. Named Routes in go_router
go_router
lets you define both a path
and a name
for your routes. You can then call context.goNamed()
instead of context.go()
.
Here’s a quick example:
import 'package:go_router/go_router.dart';
final router = GoRouter(
routes: [
GoRoute(
name: 'home',
path: '/home',
builder: (context, state) => const HomePage(),
),
GoRoute(
name: 'profile',
path: '/profile/:userId',
builder: (context, state) {
final userId = state.pathParameters['userId'];
return ProfilePage(userId: userId);
},
),
],
);
// Navigate by route name
context.goNamed('profile', params: {'userId': '42'});
With named routes:
- If you rename
/profile/:userId
to/users/:id
, you only update one place (the route definition) instead of everygo()
call in the project.
3. Centralizing Route Names and Paths
Create a file or class that contains all of your route constants. For example:
// app_routes.dart
abstract class AppRouteName {
static const home = 'home';
static const profile = 'profile';
}
abstract class AppRoutePath {
static const home = '/home';
static const profile = '/profile/:userId';
}
Then, when defining routes:
GoRoute(
name: AppRouteName.profile,
path: AppRoutePath.profile,
builder: (context, state) => ...
);
And when navigating:
context.goNamed(
AppRouteName.profile,
params: {'userId': '42'},
);
This approach makes it easy to manage changes in path structure and ensures a single source of truth for route definitions.
4. Organizing Routes in Larger Apps
If you’re following a feature-first approach:
- Core Layer: Defines shared resources, services, or base classes.
- Feature Layer: Each feature can define its own UI, logic, and route definitions.
-
App (Composition) Layer: Imports both the core and the features, then merges all routes in one central
GoRouter
.
This avoids circular imports:
- Core doesn’t import features.
- Features import core if needed.
- App imports both core and features to assemble the final routes.
A typical folder structure might look like this:
lib/
├── core/
| └── app_routes.dart
├── features/
| ├── feature_a/
| └── feature_b/
└── app/
├── app_router.dart
└── main.dart
The app_router.dart
then gathers routes from core
and each feature, creating one unified router.
5. Final Tips
- Use
go_router_builder
if you want generated, type-safe route navigation functions likecontext.goToProfile(userId: 42)
. - Keep route definitions minimal and avoid burying complex logic within them.
- For dynamic screens, leverage
pathParameters
orqueryParameters
to parse IDs and flags.
By following these best practices—named routes, centralized constants, and a layered architecture—you’ll save yourself tons of headaches and ensure a more maintainable Flutter codebase. Happy coding!
Featured ones: