Introduction to GoRouter
Introduction to GoRouter
As Flutter apps grow in complexity, the built-in Navigator API can become difficult to manage. GoRouter is an official, declarative routing package from the Flutter team that introduces URL-based navigation, deep linking, and a clean route tree structure. Instead of pushing and popping routes imperatively, you declare your entire route hierarchy upfront and navigate using URL-like paths.
go_router package on pub.dev.Adding go_router to Your Project
Start by adding the dependency to your pubspec.yaml file:
pubspec.yaml
dependencies:
flutter:
sdk: flutter
go_router: ^14.0.0
After saving the file, run flutter pub get in your terminal to fetch the package. Then import it wherever you configure routing:
Import GoRouter
import 'package:go_router/go_router.dart';
Declaring a Route Tree with GoRoute
GoRouter requires you to declare a route tree — a list of GoRoute entries that map URL paths to widgets. Each GoRoute specifies a path and a builder (or pageBuilder) that returns the widget to display. You then pass the GoRouter instance to your MaterialApp.router constructor.
Defining a GoRouter with Multiple Routes
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
final GoRouter _router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) {
return const HomeScreen();
},
),
GoRoute(
path: '/profile',
builder: (BuildContext context, GoRouterState state) {
return const ProfileScreen();
},
),
GoRoute(
path: '/product/:id',
builder: (BuildContext context, GoRouterState state) {
final String productId = state.pathParameters['id']!;
return ProductScreen(productId: productId);
},
),
],
);
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'GoRouter Demo',
routerConfig: _router,
);
}
}
GoRouter instance as a top-level or provider-scoped variable, not inside a widget's build method. Recreating the router on every rebuild causes navigation state to reset unexpectedly.Navigating with context.go and context.push
GoRouter adds extension methods directly on BuildContext so you can navigate anywhere in your widget tree without needing a reference to the router object. The two most important methods are:
context.go(path)— Replaces the entire navigation stack with the new route. The user cannot press the back button to return. Use this for top-level navigation like switching tabs or going to a home screen after login.context.push(path)— Pushes the new route on top of the current stack, preserving the back button behaviour. Use this for drilling into details: going from a list to a detail screen.
Using context.go and context.push
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
// Replaces the stack — no back button to Home
context.go('/profile');
},
child: const Text('Go to Profile'),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
// Pushes on top of stack — back button returns here
context.push('/product/42');
},
child: const Text('View Product 42'),
),
],
),
),
);
}
}
Path Parameters and Query Parameters
GoRouter supports both path parameters (colon-prefixed segments like :id) and query parameters (key-value pairs after ?). Access them through the GoRouterState object passed to every builder:
state.pathParameters['id']— extracts a path segment valuestate.uri.queryParameters['filter']— extracts a query string value
To pass a query parameter when navigating, simply append it to the path string:
Path and Query Parameters Example
// Navigating with a query parameter
context.go('/profile?tab=settings');
// In the route builder:
GoRoute(
path: '/profile',
builder: (BuildContext context, GoRouterState state) {
final String tab = state.uri.queryParameters['tab'] ?? 'overview';
return ProfileScreen(initialTab: tab);
},
),
Replacing Navigator.push with GoRouter
If you are migrating an existing app, replace every Navigator.push(context, MaterialPageRoute(builder: ...)) call with a context.push('/path') call, and every Navigator.pushReplacement(...) call with context.go('/path'). This makes navigation intent explicit and enables deep linking automatically.
Navigator.push and context.go/context.push in the same app unless you fully understand how GoRouter interacts with the underlying Navigator. Mixing them can produce unexpected back-stack behaviour, especially on the web.Summary
GoRouter brings URL-based, declarative routing to Flutter. You add the go_router package, define a route tree with GoRoute entries in a GoRouter instance, attach it to MaterialApp.router, and navigate using context.go() (replace stack) or context.push() (add to stack). Path and query parameters are declared in the route path string and accessed via GoRouterState. This foundation makes deep linking, nested navigation, and URL synchronisation straightforward in both mobile and web Flutter apps.
context.go() when you want to replace the navigation history (e.g., after login) and context.push() when you want the user to be able to press back (e.g., viewing a detail screen). The route tree declared in GoRouter is the single source of truth for all navigation in your app.