GoRouter#
All named parameters#
Here is the complete and numbered list of named parameters of GoRouter in Flutter (go_router package) as of current stable versions. These are only the parameters that belong directly to the GoRouter constructor:
GoRouter(
routes: (required) β’ List of all app routes (GoRoute, ShellRoute, etc.)
extraCodec: β’ Controls how extra data is encoded/decoded for state restoration
initialLocation: β’ The starting path of your app (e.g. /, /login)
observers: β’ List of NavigatorObserver for navigation events
debugLogDiagnostics: β’ Prints route matching and navigation logs for debugging
redirect: β’ Global redirect logic for auth, onboarding, etc.
redirectLimit: β’ Prevents infinite redirect loops (default: 5)
navigatorKey: β’ Controls main Navigatorβs state
restorationScopeId: β’ Used for Android/iOS state restoration
routerNeglect: β’ Prevents route changes from updating the URL
onException: β’ Catches and handles routing exceptions
)
β 1.routes:#
Here is a very simple and clean Flutter example that shows how to use the routes: parameter in GoRouter together with MaterialApp.router().
- This example has two pages:
Home page (/)
About page (/about)
And a button to move between them.
β pubspec.yaml (required package)
Make sure you add go_router first:
dependencies:
flutter:
sdk: flutter
go_router: ^13.2.0
Then run:
flutter pub get
β main.dart (Complete working example)
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(MyApp());
}
// β
GoRouter with routes: parameter
final GoRouter _router = GoRouter(
routes: [
// Home Page
GoRoute(
path: '/',
builder: (context, state) => const HomePage(),
),
// About Page
GoRoute(
path: '/about',
builder: (context, state) => const AboutPage(),
),
],
);
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'GoRouter Routes Example',
// β
Connect GoRouter here
routerConfig: _router,
);
}
}
/* -------------------- HOME PAGE -------------------- */
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home Page')),
body: Center(
child: ElevatedButton(
onPressed: () {
// β
Go to About Page
context.go('/about');
},
child: const Text('Go to About Page'),
),
),
);
}
}
/* -------------------- ABOUT PAGE -------------------- */
class AboutPage extends StatelessWidget {
const AboutPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('About Page')),
body: Center(
child: ElevatedButton(
onPressed: () {
// β
Go back to Home
context.go('/');
},
child: const Text('Back to Home'),
),
),
);
}
}
πΉ What this example shows
Item |
Meaning |
|---|---|
routes: |
Where we define app pages in GoRouter |
MaterialApp.router() |
Required to use GoRouter |
context.go(β/aboutβ) |
Navigates to About page |
context.go(β/β) |
Returns to Home |
β 2.extraCodec:#
Here is a very simple, clean example that shows how to use the extraCodec: parameter in GoRouter with MaterialApp.router().
The extraCodec is used when you want to send non-primitive objects (custom classes) through extra: in GoRouter and make them work with state restoration and web deep-linking.
In this example we:
β Create a User class
β Create a Codec<User, Object?>
β Pass a User object using extra:
β Decode it using extraCodec
β 1. Full Working Example (main.dart)
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(const MyApp());
}
/* -------------------- CUSTOM CLASS -------------------- */
class User {
final String name;
final int age;
User(this.name, this.age);
Map<String, dynamic> toJson() => {
'name': name,
'age': age,
};
factory User.fromJson(Map<String, dynamic> json) {
return User(json['name'], json['age']);
}
}
/* -------------------- EXTRA CODEC -------------------- */
class UserCodec extends Codec<User, Object?> {
const UserCodec();
@override
Converter<User, Object?> get encoder => const _UserEncoder();
@override
Converter<Object?, User> get decoder => const _UserDecoder();
}
class _UserEncoder extends Converter<User, Object?> {
const _UserEncoder();
@override
Object? convert(User input) {
return input.toJson(); // Convert User to Map
}
}
class _UserDecoder extends Converter<Object?, User> {
const _UserDecoder();
@override
User convert(Object? input) {
final map = input as Map<String, dynamic>;
return User.fromJson(map); // Convert Map to User
}
}
/* -------------------- ROUTER -------------------- */
final GoRouter _router = GoRouter(
extraCodec: const UserCodec(), // β
IMPORTANT PART
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomePage(),
),
GoRoute(
path: '/profile',
builder: (context, state) {
final user = state.extra as User;
return ProfilePage(user: user);
},
),
],
);
/* -------------------- APP -------------------- */
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'GoRouter extraCodec Example',
routerConfig: _router,
);
}
}
/* -------------------- HOME -------------------- */
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home')),
body: Center(
child: ElevatedButton(
onPressed: () {
final user = User('Sherullah', 25);
// β
Pass custom object using extra
context.go('/profile', extra: user);
},
child: const Text('Go to Profile'),
),
),
);
}
}
/* -------------------- PROFILE -------------------- */
class ProfilePage extends StatelessWidget {
final User user;
const ProfilePage({super.key, required this.user});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Profile')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Name: ${user.name}', style: const TextStyle(fontSize: 22)),
Text('Age: ${user.age}', style: const TextStyle(fontSize: 22)),
const SizedBox(height: 30),
ElevatedButton(
onPressed: () {
context.go('/');
},
child: const Text('Back to Home'),
),
],
),
),
);
}
}
β What extraCodec is doing here
Part |
Meaning |
|---|---|
User |
Custom Dart class |
UserCodec |
Converts User Map |
extraCodec: UserCodec() |
Allows GoRouter to serialize extra: |
context.go(β/profileβ, extra: user); |
Sends custom object |
final user state.extra as User; |
Receives object |
Without extraCodec, a custom class will break state restoration or web URL reload.
Simple Rule to Remember
Use:
β extra: βHelloβ β No codec needed
β extra: 123 β No codec needed
β extra: MyClass() β β Needs extraCodec
β 3.initialLocation:#
Here is a very simple and clear Flutter example showing how to use the initialLocation: parameter in GoRouter with MaterialApp.router().
This tells your app which page to open first when the app starts.
In this example, the app will start on the About page (/about) instead of the Home page.
β Full working main.dart example
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(const MyApp());
}
/* -------------------- ROUTER -------------------- */
final GoRouter _router = GoRouter(
// β
The FIRST screen when the app opens
initialLocation: '/about',
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomePage(),
),
GoRoute(
path: '/about',
builder: (context, state) => const AboutPage(),
),
],
);
/* -------------------- APP -------------------- */
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'Initial Location Example',
routerConfig: _router,
);
}
}
/* -------------------- HOME -------------------- */
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home Page')),
body: Center(
child: ElevatedButton(
onPressed: () {
context.go('/about'); // Go to About
},
child: const Text('Go to About Page'),
),
),
);
}
}
/* -------------------- ABOUT -------------------- */
class AboutPage extends StatelessWidget {
const AboutPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('About Page')),
body: Center(
child: ElevatedButton(
onPressed: () {
context.go('/'); // Go to Home
},
child: const Text('Back to Home'),
),
),
);
}
}
π Key line (most important)
initialLocation: '/about',
That one line makes:
β App start on About page
β Without pressing any button
β No need for home: parameter
β Summary
Parameter |
Purpose |
|---|---|
initialLocation |
Decides first page on app launch |
Used in |
GoRouter() |
Works with |
MaterialApp.router() |
β 4.observers:#
Here is a simple, clean Flutter example showing how to use the observers: parameter in GoRouter with MaterialApp.router().
The observers parameter lets you listen to navigation events (like pushing a new route, popping a route). We will create a simple NavigatorObserver and print messages whenever navigation happens.
β Full working main.dart example (with observers)
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(const MyApp());
}
/* -------------------- ROUTER -------------------- */
final GoRouter _router = GoRouter(
routes: [
GoRoute(
path: '/',
name: 'home',
builder: (context, state) => const HomePage(),
),
GoRoute(
path: '/about',
name: 'about',
builder: (context, state) => const AboutPage(),
),
],
);
/* -------------------- APP -------------------- */
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'GoRouter pop example',
routerConfig: _router,
);
}
}
/* -------------------- HOME PAGE -------------------- */
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home Page')),
body: Center(
child: ElevatedButton(
onPressed: () {
// β
Use push so that we CAN pop later
context.push('/about');
},
child: const Text('Go to About'),
),
),
);
}
}
/* -------------------- ABOUT PAGE -------------------- */
class AboutPage extends StatelessWidget {
const AboutPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('About Page')),
body: Center(
child: ElevatedButton(
onPressed: () {
// β
This is safe because we used push()
context.pop();
},
child: const Text('Back (pop) to Home'),
),
),
);
}
}
Whatβs happening here?
Part |
Description |
|---|---|
MyRouteObserver |
Custom observer class |
didPush() |
Runs when a route is opened |
didPop() |
Runs when a route is closed |
observers: [MyRouteObserver()] |
Registers the observer |
context.go() |
Push-like navigation |
context.pop() |
Pop navigation |
When you run this app and navigate, you will see log messages in the console:
β
Pushed route: about
π Popped route: about
β Why is observers useful?
You can use it for:
Analytics (Firebase, GA)
Debugging
Tracking screen changes
Logging page views
β 5.debugLogDiagnostics:#
Here is a very simple Flutter example showing how to use the debugLogDiagnostics: parameter in GoRouter with MaterialApp.router().
This setting tells GoRouter to print detailed navigation logs in the console. It is used only for debugging (donβt use it in production).
β Full working example β main.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(const MyApp());
}
/* -------------------- ROUTER -------------------- */
final GoRouter _router = GoRouter(
// β
Important parameter
debugLogDiagnostics: true,
routes: [
GoRoute(
path: '/',
name: 'home',
builder: (context, state) => const HomePage(),
),
GoRoute(
path: '/about',
name: 'about',
builder: (context, state) => const AboutPage(),
),
],
);
/* -------------------- APP -------------------- */
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'GoRouter Debug Example',
routerConfig: _router,
);
}
}
/* -------------------- HOME PAGE -------------------- */
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home Page')),
body: Center(
child: ElevatedButton(
onPressed: () {
context.go('/about');
},
child: const Text('Go to About'),
),
),
);
}
}
/* -------------------- ABOUT PAGE -------------------- */
class AboutPage extends StatelessWidget {
const AboutPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('About Page')),
body: Center(
child: ElevatedButton(
onPressed: () {
context.go('/');
},
child: const Text('Back to Home'),
),
),
);
}
}
β Most important line
debugLogDiagnostics: true,
This tells GoRouter:
βPrint every navigation step in the console so I can see what is going on.β
β Example logs youβll see in Console
When app starts:
[GoRouter] Initial route location: /
[GoRouter] Router configured with 2 routes
When button is pressed:
[GoRouter] going to /about
[GoRouter] setting new location: /about
When going back:
[GoRouter] going to /
[GoRouter] setting new location: /
This is perfect for:
Finding routing bugs
Seeing deep link problems
Debugging back stack issues
β When to use / not use
Use it when |
Donβt use when |
|---|---|
Debugging |
Production release |
Learning GoRouter |
Release APK/IPA |
Finding errors |
Live app |
To turn off later:
debugLogDiagnostics: false,
(or completely remove the line)
β 6.redirect:#
Here is a very simple Flutter example showing how to use the redirect: parameter in GoRouter with MaterialApp.router().
In this example, we simulate a login system:
If the user is NOT logged in, any page redirects to /login
If the user IS logged in, /login redirects to /
β Full working main.dart (with redirect)
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(const MyApp());
}
/* -------------------- FAKE LOGIN STATE -------------------- */
// Change this to true/false to test redirect
bool isLoggedIn = false;
/* -------------------- ROUTER -------------------- */
final GoRouter _router = GoRouter(
// β
REDIRECT PARAMETER
redirect: (BuildContext context, GoRouterState state) {
final bool loggingIn = state.uri.toString() == '/login';
// If not logged in, send to login page
if (!isLoggedIn && !loggingIn) {
return '/login';
}
// If logged in and trying to access login page again
if (isLoggedIn && loggingIn) {
return '/';
}
// No redirect
return null;
},
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomePage(),
),
GoRoute(
path: '/login',
builder: (context, state) => const LoginPage(),
),
GoRoute(
path: '/about',
builder: (context, state) => const AboutPage(),
),
],
);
/* -------------------- APP -------------------- */
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'GoRouter redirect example',
routerConfig: _router,
);
}
}
/* -------------------- LOGIN PAGE -------------------- */
class LoginPage extends StatelessWidget {
const LoginPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Login Page')),
body: Center(
child: ElevatedButton(
onPressed: () {
// Simulate login
isLoggedIn = true;
// Refresh router
_router.refresh();
// Go to home after login
context.go('/');
},
child: const Text('Login'),
),
),
);
}
}
/* -------------------- HOME PAGE -------------------- */
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home Page')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'You are logged in',
style: TextStyle(fontSize: 22),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
context.go('/about');
},
child: const Text('Go to About'),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
// Simulate logout
isLoggedIn = false;
_router.refresh();
context.go('/login');
},
child: const Text('Logout'),
),
],
),
),
);
}
}
/* -------------------- ABOUT PAGE -------------------- */
class AboutPage extends StatelessWidget {
const AboutPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('About Page')),
body: Center(
child: ElevatedButton(
onPressed: () {
context.go('/');
},
child: const Text('Back to Home'),
),
),
);
}
}
The key part (redirect parameter)
redirect: (context, state) {
final loggingIn = state.uri.toString() == '/login';
if (!isLoggedIn && !loggingIn) {
return '/login';
}
if (isLoggedIn && loggingIn) {
return '/';
}
return null;
},
What it does
Situation |
Result |
|---|---|
Not logged in + any page |
β /login |
Logged in + try /login |
β / |
Logged in + normal page |
No redirect |
β Why redirect is important
You use it for:
Login / logout
Admin-only pages
Role-based security
Onboarding flows
First-time user flows
β 7.redirectLimit:#
Here is a clean, minimal Flutter example showing how to use the redirectLimit: parameter in GoRouter together with MaterialApp.router().
In this demo, we intentionally create a redirect loop so you can see how redirectLimit stops it.
β Full working main.dart β with redirectLimit
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(const MyApp());
}
/* -------------------- ROUTER -------------------- */
final GoRouter _router = GoRouter(
// β
Maximum number of redirects allowed
redirectLimit: 3,
// β
This redirect causes a loop (on purpose)
redirect: (context, state) {
if (state.uri.toString() == '/a') {
return '/b';
}
if (state.uri.toString() == '/b') {
return '/a';
}
return null;
},
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomePage(),
),
GoRoute(
path: '/a',
builder: (context, state) => const PageA(),
),
GoRoute(
path: '/b',
builder: (context, state) => const PageB(),
),
],
);
/* -------------------- APP -------------------- */
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'GoRouter redirectLimit demo',
routerConfig: _router,
);
}
}
/* -------------------- HOME PAGE -------------------- */
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home Page')),
body: Center(
child: ElevatedButton(
onPressed: () {
// β
Start redirect loop here
context.go('/a');
},
child: const Text('Go to Page A'),
),
),
);
}
}
/* -------------------- PAGE A -------------------- */
class PageA extends StatelessWidget {
const PageA({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Text(
'Page A',
style: TextStyle(fontSize: 24),
),
),
);
}
}
/* -------------------- PAGE B -------------------- */
class PageB extends StatelessWidget {
const PageB({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Text(
'Page B',
style: TextStyle(fontSize: 24),
),
),
);
}
}
β What redirectLimit is doing here
Means:
If GoRouter redirects more than 3 times in a row, it will stop and throw an error instead of looping forever.
Our redirect loop:
/a β /b β /a β /b β /a β ...
Because the limit is 3, GoRouter will stop after the 3rd redirect and show an error in the console like:
Too many redirects: /a -> /b -> /a -> /b
This protects your app from crashing or freezing.
β When should you really use redirectLimit?
Use it when |
Why |
|---|---|
You use redirect a lot |
Prevent errors |
You have auth logic |
Prevent infinite redirect |
You have role-based logic |
Prevent loop |
Production apps |
Safety feature |
Normally you donβt change it, but now you know exactly how it works.
β 9.restorationScopeId:#
Here is a very simple and clean Flutter example showing how to use the restorationScopeId: parameter in GoRouter with MaterialApp.router().
restorationScopeId is used for state restoration. It allows Flutter to restore the navigation state (for example, after the app is killed and reopened by the OS).
You wonβt βseeβ anything on screen, but this enables iOS/Android to remember your last route automatically in the background.
β Full working example using restorationScopeId
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(const MyApp());
}
/* -------------------- ROUTER -------------------- */
final GoRouter _router = GoRouter(
// β
Enable state restoration for GoRouter
restorationScopeId: 'app_router',
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomePage(),
),
GoRoute(
path: '/details',
builder: (context, state) => const DetailsPage(),
),
],
);
/* -------------------- APP -------------------- */
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'State Restoration Example',
restorationScopeId: 'material_app', // β
optional but recommended
routerConfig: _router,
);
}
}
/* -------------------- HOME PAGE -------------------- */
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home Page')),
body: Center(
child: ElevatedButton(
onPressed: () {
context.push('/details');
},
child: const Text('Go to Details'),
),
),
);
}
}
/* -------------------- DETAILS PAGE -------------------- */
class DetailsPage extends StatelessWidget {
const DetailsPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Details Page')),
body: Center(
child: ElevatedButton(
onPressed: () {
context.pop();
},
child: const Text('Back'),
),
),
);
}
}
The two important lines
In GoRouter
restorationScopeId: 'app_router',
In MaterialApp.router (recommended but optional)
restorationScopeId: 'material_app',
These IDs just have to be unique strings.
β What this actually does
When state restoration is enabled on the device:
Open the app
Go to /details
Minimize or kill the app
OS restores the app
β It can bring you back to /details automatically
This is powerful for:
β Big apps
β Multi-page flows
β Forms
β Long usage sessions
Easy rule to remember
Where |
Why |
|---|---|
GoRouter.restorationScopeId |
Restores navigation state |
MaterialApp.restorationScopeId |
Enables app state restoration |
String value |
Can be any unique text |
β 10.routerNeglect:#
Here is a simple, clean Flutter example that shows how to use the routerNeglect: parameter in GoRouter with MaterialApp.router().
routerNeglect is mainly useful for Flutter Web. When it is set to true, GoRouter will NOT update the browser URL when you navigate.
On mobile it still works fine β you wonβt see a difference β but on the Web, the URL bar will not change.
β Simple working example β routerNeglect
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(const MyApp());
}
/* -------------------- ROUTER -------------------- */
final GoRouter _router = GoRouter(
// β
Important: ignores changing browser URL
routerNeglect: true,
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomePage(),
),
GoRoute(
path: '/profile',
builder: (context, state) => const ProfilePage(),
),
],
);
/* -------------------- APP -------------------- */
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'routerNeglect Example',
routerConfig: _router,
);
}
}
/* -------------------- HOME PAGE -------------------- */
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home Page')),
body: Center(
child: ElevatedButton(
onPressed: () {
// Navigate to Profile
context.go('/profile');
},
child: const Text('Go to Profile'),
),
),
);
}
}
/* -------------------- PROFILE PAGE -------------------- */
class ProfilePage extends StatelessWidget {
const ProfilePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Profile Page')),
body: Center(
child: ElevatedButton(
onPressed: () {
// Navigate back to Home
context.go('/');
},
child: const Text('Back to Home'),
),
),
);
}
}
The important line
routerNeglect: true,
What it actually does
Situation |
Result |
|---|---|
routerNeglect: true |
β URL does NOT change in browser |
routerNeglect: false (default) |
β URL does change (/, /profile) |
On Android / iOS: no visible difference
On Web: this controls the address bar
β When should you use routerNeglect?
Use it when:
You donβt want the URL to change on Web
You use your own URL system
You want privacy in routes
You want single-page experience without URL updates
Do not use it if you want:
Deep links
Shareable URLs
Browser back/forward working with URLs
β 11.onException:#
Here is a simple, working Flutter program that shows how to use the onException: parameter in GoRouter with MaterialApp.router().
onException is used to catch routing errors (for example: when a user navigates to a page that does not exist, or when a builder throws an error) and handle them gracefully.
In this example:
We handle the error
Print it in the console
Redirect the user to a custom Error Page
β Full working example β onException
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(const MyApp());
}
/* -------------------- ROUTER -------------------- */
final GoRouter _router = GoRouter(
// In your version the 3rd param is GoRouter (NOT error)
onException: (BuildContext context, GoRouterState state, GoRouter router) {
debugPrint('β Routing problem. Tried path: ${state.uri}');
// Avoid infinite loop: only redirect if not already on /error
if (state.uri.toString() != '/error') {
router.go('/error'); // β
use router, NOT context.go
}
},
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomePage(),
),
GoRoute(
path: '/profile',
builder: (context, state) => const ProfilePage(),
),
GoRoute(
path: '/error',
builder: (context, state) => const ErrorPage(),
),
],
);
/* -------------------- APP -------------------- */
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'GoRouter onException demo',
routerConfig: _router, // π΄ no const here
);
}
}
/* -------------------- HOME PAGE -------------------- */
class HomePage extends StatelessWidget {
const HomePage({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: () => context.go('/profile'),
child: const Text('Go to Profile (valid)'),
),
const SizedBox(height: 20),
ElevatedButton(
// β Invalid route: will trigger onException
onPressed: () => context.go('/unknown-page'),
child: const Text('Go to INVALID route'),
),
],
),
),
);
}
}
/* -------------------- PROFILE PAGE -------------------- */
class ProfilePage extends StatelessWidget {
const ProfilePage({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Text(
'Profile Page',
style: TextStyle(fontSize: 24),
),
),
);
}
}
/* -------------------- ERROR PAGE -------------------- */
class ErrorPage extends StatelessWidget {
const ErrorPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Error Page')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Something went wrong β',
style: TextStyle(fontSize: 22),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () => context.go('/'),
child: const Text('Back to Home'),
),
],
),
),
);
}
}
What is onException in GoRouter?
onException is a global error handler for routing problems in your app.
- It runs when:
A user navigates to a route that doesnβt exist
(e.g. /unknown-page)
A route builder crashes
A deep link fails
There is a navigation parsing error
Instead of your app crashing, you get a chance to log the error and redirect to a safe page.
β Real signature in your GoRouter version (14.8.1)
From your errors, your version uses this signature:
onException: (BuildContext context, GoRouterState state, GoRouter router) {
// handle error here
}
Not GoRouterException, and not Object error.
Meaning of the 3 parameters:
Parameter |
Meaning |
|---|---|
BuildContext context |
Current (but unsafe) context |
GoRouterState state |
The route that failed |
GoRouter router |
The main router instance β |
Thatβs why we must use:
router.go('/error');
and NOT:
context.go('/error'); // β causes "No GoRouter found in context"
Simple Example
final GoRouter _router = GoRouter(
onException: (context, state, router) {
print('β Error when going to: ${state.uri}');
if (state.uri.toString() != '/error') {
router.go('/error'); // safe redirect
}
},
routes: [
GoRoute(path: '/', builder: (c, s) => const HomePage()),
GoRoute(path: '/error', builder: (c, s) => const ErrorPage()),
],
);
If you press a button like this:
context.go('/wrong-path');
GoRouter will:
Fail to find /wrong-path
Call onException
You redirect to /error via router.go(β/errorβ)
No crash β
Nice error page β
β Why onException is useful in real projects
You can use it for:
β Show βPage not foundβ page
β Log issues to server / Firebase
β Prevent crashes
β Show error message to user
β Recover from broken deep links
β οΈ Common mistakes (you already discovered these)
Mistake |
Problem |
|---|---|
Using GoRouterException |
β Doesnβt exist |
Using context.go() inside onException |
β No GoRouter in context |
Using errorBuilder + onException together |
β Assertion error |
Not checking current route |
β Infinite redirect loop |
π The golden rule
Inside onException β always use router.go(β¦)
Never use context.go(β¦) there.