Logo

dev-resources.site

for different kinds of informations.

Best Practices for Managing Route Names and Paths with go_router in Flutter

Published at
1/11/2025
Categories
Author
Twilight
Categories
1 categories in total
open
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 every go() 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:

  1. Core Layer: Defines shared resources, services, or base classes.
  2. Feature Layer: Each feature can define its own UI, logic, and route definitions.
  3. 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 like context.goToProfile(userId: 42).
  • Keep route definitions minimal and avoid burying complex logic within them.
  • For dynamic screens, leverage pathParameters or queryParameters 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: