Intermediate · Lists, navigation and forms · topic 25 of 50 · 30 min
Routing with go_router
Explanation
Why a router?
Navigator.push works well inside small apps. Larger apps need more: a URL for every screen (so the web version's address bar and browser back button work), deep links (opening a specific product from a notification or a shared link), and central rules such as "if not logged in, go to /login". A router package handles these. go_router is a popular choice maintained by the Flutter team (publisher flutter.dev).
flutter pub add go_routerDefining routes
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
final router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
routes: [
GoRoute(
path: 'product/:id', // full path: /product/42
builder: (context, state) {
final id = int.parse(state.pathParameters['id']!);
return ProductScreen(productId: id);
},
),
],
),
GoRoute(path: '/cart', builder: (context, state) => const CartScreen()),
GoRoute(path: '/login', builder: (context, state) => const LoginScreen()),
],
);
void main() => runApp(MaterialApp.router(routerConfig: router));Routes can be nested: the product route above is a child of /, so its full path is /product/42, and opening it directly (from a deep link) builds Home underneath it, so the back button leads somewhere sensible. Keep the route list in one file so the whole app's map is easy to read.
Pass ids in the path (/product/42) rather than whole objects. A path survives a browser refresh, can be shared as a link, and lets the screen load fresh data. go_router also has a state.extra field for passing objects, but it is lost when the web page reloads.
Moving around
| Call | Effect |
|---|---|
| context.go('/product/42') | Go to that location, rebuilding the stack to match the route tree (like typing a URL) |
| context.push('/cart') | Push a screen on top of the current one; Back returns here |
| context.pop() | Go back |
| context.go('/product/42?tab=reviews') | Query parameters, read with state.uri.queryParameters |
Redirects for login
final router = GoRouter(
redirect: (context, state) {
final loggedIn = authService.isLoggedIn; // your own auth state
final goingToLogin = state.matchedLocation == '/login';
if (!loggedIn && !goingToLogin) return '/login';
if (loggedIn && goingToLogin) return '/';
return null; // no redirect
},
routes: [ /* ... */ ],
);ShellRoute for a shared bottom bar
Many apps keep a bottom navigation bar visible while switching tabs. ShellRoute (and StatefulShellRoute, which keeps each tab's own stack) wraps several routes in a shared layout, so the bar stays on screen while the content changes. Check the go_router documentation for the current API, as it evolves.
Common mistakes
- Using MaterialApp(home: ...) instead of MaterialApp.router(routerConfig: router): the router is ignored.
- Mixing context.go and context.push without understanding them: go rebuilds the stack, push adds on top.
- Using int.parse on a path parameter without handling bad input from a deep link; show a "not found" screen instead of crashing.
- Putting large objects in state.extra and expecting them to survive a web refresh: they do not. Pass ids in the path.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Routing with go_router · 1/6
For bigger apps, deep links and the web, use a router. go_router is published by the Flutter team.
Prefer a video by a teacher? Find free videos on “flutter go_router tutorial path parameters redirect shellroute” on YouTube ↗
In real life: Deep link from a WhatsApp message
An online store shares product links like https://shop.example.in/product/1234 on WhatsApp. With go_router and app links configured, tapping the link opens the app directly on ProductScreen(productId: 1234), or the web version on the same page. A redirect sends users who are not logged in to /login first and then back to the product.
GoRoute(
path: '/product/:id',
builder: (context, state) {
final id = int.tryParse(state.pathParameters['id'] ?? '');
return id == null ? const NotFoundScreen() : ProductScreen(productId: id);
},
)Practice (0/3 done)
With GoRoute(path: '/order/:orderId'), how do you read the id for /order/77?
Which call keeps the current screen and lets Back return to it?
Convert a small app to go_router: routes for /, /product/:id and /login; MaterialApp.router; a redirect that sends logged-out users to /login (use a simple global bool or ValueNotifier for now); and a NotFound screen for invalid ids. Run it on the web and check that the browser URL and back button work.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Forms and validationLists, navigation and forms · same module
- Grids with GridViewLists, navigation and forms · same module
- Routing: sending each request to the right codeBackend Development · APIs and request handling
- Pages and client-side routingFull-Stack Development · Building the frontend
- Protecting pages and API routesFull-Stack Development · Authentication and files end to end
- How a router forwards: the routing tableComputer Networking (CCNA level) · Routing: static routes and OSPF
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
Why use a router package like go_router?
Answer: For URL-based routes, deep links and central redirects
Especially for web and larger apps.
Which MaterialApp constructor works with go_router?
Answer: MaterialApp.router()
Pass routerConfig: router.
What should a redirect return when no redirect is needed?
Answer: null
null means stay on the requested location.
Who publishes the go_router package?
Answer: The Flutter team (flutter.dev)
It is a first-party package.
Which route handles GET /products?sort=price?Related
Answer: GET /products
The query string is not part of routing. The handler of GET /products reads it.
For the route /notes/:id and the URL /notes/42, what does useParams() return?Related
Answer: { id: '42' }
useParams returns an object of named params, and the values are always strings. Use Number(id) when you need a number.
Which is the recommended way to pass a product id to a details screen with Navigator.push?Related
Answer: The details screen's constructor
Constructor arguments are explicit and type-safe.
From Navigator basics
Why is a frontend redirect to /login not real protection?Related
Answer: Anyone can call the API directly without using your pages
Tools like curl or a changed app can call the API directly, so only the backend check actually protects data.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 1 more topic in “Lists, navigation and forms” before its exam: Forms and validation.
- Module 6 exam: Lists, navigation and forms · 13 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, State management. 50 topics left in the course before the final exam.