Introduction
In Flutter’s evolving landscape, routing has shifted from imperative patterns toward a more declarative style. The go_router package brings a concise, type-safe approach to navigation, aligning with Flutter’s widget-driven design. This tutorial introduces the core concepts of declarative navigation using go_router, walks through setup, route definitions, nested routing, and parameter handling, and demonstrates how to maintain clean, maintainable navigation in mobile development.
Why Declarative Navigation?
Declarative navigation expresses routing state via widget properties rather than commands like push and pop. Benefits include:
• Predictable state: The UI reflects the current route configuration.
• Simplified testing: You can assert widget trees against expected routes.
• Deep linking: URL-based navigation maps directly to page widgets.
go_router builds on these principles, offering a URL-centric API, built-in redirection, and error handling.
Setting up go_router
Add go_router to pubspec.yaml and import it:
dependencies:
flutter:
sdk: flutter
go_router
In your main.dart, initialize GoRouter at the top of the widget tree:
final GoRouter _router = GoRouter(
initialLocation: '/home',
routes: [ ],
errorBuilder: (context, state) => ErrorPage(state.error),
);
void main() => runApp(MaterialApp.router(
routerConfig: _router,
));
This snippet sets the initial route, a placeholder for routes, and a global error handler.
Defining Routes
Routes in go_router are instances of GoRoute. Each route has a path and a builder that returns a widget. You can nest routes to represent hierarchies.
final List<GoRoute> routes = [
GoRoute(
path: '/home',
builder: (ctx, state) => HomePage(),
routes: [
GoRoute(
path: 'profile/:id',
builder: (ctx, state) {
final id = state.params['id']!;
return ProfilePage(userId: id);
},
),
],
),
];In this example:
• /home shows HomePage.
• /home/profile/123 matches the nested profile route, extracting id.
Use context.go('/home/profile/123') to navigate without stack push/pop semantics.
Handling Nested Routes and Parameters
Nested routes let you build layouts with shared shells and dynamic sections. You can wrap nested routes in a shell builder:
GoRouter(
routes: [
ShellRoute(
builder: (ctx, state, child) => Scaffold(body: child),
routes: [
GoRoute(path: '/feed', builder: (c, s) => FeedPage()),
GoRoute(
path: '/details/:item',
builder: (c, s) => DetailsPage(item: s.params['item']!),
),
],
),
],
);Here, ShellRoute wraps both /feed and /details/:item in a common Scaffold. URL parameters become strongly typed via state.params.
Advanced techniques:
• Query parameters: use state.queryParams['q'].
• Redirection: add a redirect callback in GoRouter to route based on auth state.
goRouter = GoRouter(
redirect: (ctx, state) {
final loggedIn = AuthService.isLoggedIn;
return !loggedIn && state.subloc != '/login' ? '/login' : null;
},
);This callback ensures unauthorized visitors land on /login.
Vibe Studio

Vibe Studio, powered by Steve’s advanced AI agents, is a revolutionary no-code, conversational platform that empowers users to quickly and efficiently create full-stack Flutter applications integrated seamlessly with Firebase backend services. Ideal for solo founders, startups, and agile engineering teams, Vibe Studio allows users to visually manage and deploy Flutter apps, greatly accelerating the development process. The intuitive conversational interface simplifies complex development tasks, making app creation accessible even for non-coders.
Conclusion
Declarative navigation with go_router transforms routing into a state-driven, URL-centric system that matches Flutter’s widget paradigm. By defining routes as data, handling deep links, nesting pages under common shells, and leveraging redirection, you keep navigation logic clean and maintainable. Embrace go_router to unlock predictable, testable, and scalable navigation in your Flutter mobile development projects.