Steps to reproduce
- Use
go_router version ^16.0.0 or ^17.0.0.
- Define a top-level
redirect function that handles a chain:
- If path is
/ -> return /a
- If path is
/a -> return /b
- Run the app starting at
/.
Expected results
In version 15, if a top-level redirect returned a new path, the router would immediately evaluate the redirect function again against that new path. This allowed for chaining redirects (e.g., / -> /a -> /b).
https://pub.dev/documentation/go_router/latest/topics/Redirection-topic.html
Actual results
In version 16+, the router stops processing after the first redirect occurs. It navigates to the intermediate route instead of continuing the chain to the final destination.
Code sample
Code sample
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(const App());
}
class App extends StatefulWidget {
const App({super.key});
@override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
late final GoRouter _router;
@override
void initState() {
_router = GoRouter(
redirect: (context, state) {
print('redirect called with location: ${state.matchedLocation}');
if (state.matchedLocation == '/') {
print('redirecting to /a');
return '/a';
} else if (state.matchedLocation == '/a') {
print('redirecting to /b');
return '/b';
} else {
print('no redirect');
return null;
}
},
routes: [
GoRoute(path: '/', builder: (context, state) => const RScreen()),
GoRoute(path: '/a', builder: (context, state) => const AScreen()),
GoRoute(path: '/b', builder: (context, state) => const BScreen()),
],
);
super.initState();
}
@override
Widget build(BuildContext context) {
return MaterialApp.router(routerConfig: _router);
}
}
class RScreen extends StatelessWidget {
const RScreen({super.key});
@override
Widget build(BuildContext context) {
return const Center(child: Text('r'));
}
}
class AScreen extends StatelessWidget {
const AScreen({super.key});
@override
Widget build(BuildContext context) {
return const Center(child: Text('a'));
}
}
class BScreen extends StatelessWidget {
const BScreen({super.key});
@override
Widget build(BuildContext context) {
return const Center(child: Text('b'));
}
}
Screenshots or Video
Logs
Expected Behavior (observed in v15):
The app should redirect recursively until null is returned.
Logs from v15:
I/flutter ( 2159): redirect called with location: /
I/flutter ( 2159): redirecting to /a
I/flutter ( 2159): redirect called with location: /a
I/flutter ( 2159): redirecting to /b
I/flutter ( 2159): redirect called with location: /b
I/flutter ( 2159): no redirect
The screen displays "b".
Actual Behavior (observed in v16 - v17):
The redirection stops after the first step.
Logs from v16/17:
I/flutter ( 2159): redirect called with location: /
I/flutter ( 2159): redirecting to /a
The screen displays "a".
Flutter Doctor output
Doctor output
sh 2972 (git)-[main]-% flutter doctor -v
[✓] Flutter (Channel stable, 3.38.2, on macOS 26.1 25B78 darwin-arm64, locale en-US) [490ms]
• Flutter version 3.38.2 on channel stable at /Users/yehorh/opt/flutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision f5a8537f90 (5 days ago), 2025-11-18 09:27:21 -0500
• Engine revision b5990e5ccc
• Dart version 3.10.0
• DevTools version 2.51.1
• Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android, enable-ios, cli-animations, enable-native-assets, omit-legacy-version-file,
enable-lldb-debugging
[✓] Android toolchain - develop for Android devices (Android SDK version 36.0.0) [2.4s]
• Android SDK at /Users/yehorh/Library/Android/sdk
• Emulator version 36.2.12.0 (build_id 14214601) (CL:N/A)
• Platform android-36, build-tools 36.0.0
• ANDROID_HOME = /Users/yehorh/Library/Android/sdk
• Java binary at: /Users/yehorh/Library/Java/JavaVirtualMachines/azul-17.0.12/Contents/Home/bin/java
This JDK is specified in your Flutter configuration.
To change the current JDK, run: `flutter config --jdk-dir="path/to/jdk"`.
• Java version OpenJDK Runtime Environment Zulu17.52+17-CA (build 17.0.12+7-LTS)
• All Android licenses accepted.
[✓] Xcode - develop for iOS and macOS (Xcode 26.1.1) [1,217ms]
• Xcode at /Applications/Xcode.app/Contents/Developer
• Build 17B100
• CocoaPods version 1.16.2
[✓] Chrome - develop for the web [13ms]
• Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
[✓] Connected device (4 available) [6.4s]
• Android SDK built for arm64 (mobile) • emulator-5554 • android-arm64 • Android 14 (API 34) (emulator)
• yiPhone (mobile) • 00008101-000E28880AD2001E • ios • iOS 26.1 23B85
• macOS (desktop) • macos • darwin-arm64 • macOS 26.1 25B78 darwin-arm64
• Chrome (web) • chrome • web-javascript • Google Chrome 142.0.7444.176
! Error: Yehor's Apple Watch 📟 has not finished loading development services. Please select a different device, or wait for the device to load development services and try again. (code 8)
[✓] Network resources [1,081ms]
• All expected network resources are available.
• No issues found!
Steps to reproduce
go_routerversion^16.0.0or^17.0.0.redirectfunction that handles a chain:/-> return/a/a-> return/b/.Expected results
In version 15, if a top-level
redirectreturned a new path, the router would immediately evaluate theredirectfunction again against that new path. This allowed for chaining redirects (e.g.,/->/a->/b).https://pub.dev/documentation/go_router/latest/topics/Redirection-topic.html
Actual results
In version 16+, the router stops processing after the first redirect occurs. It navigates to the intermediate route instead of continuing the chain to the final destination.
Code sample
Code sample
Screenshots or Video
Logs
Expected Behavior (observed in v15):
The app should redirect recursively until
nullis returned.Logs from v15:
The screen displays "b".
Actual Behavior (observed in v16 - v17):
The redirection stops after the first step.
Logs from v16/17:
The screen displays "a".
Flutter Doctor output
Doctor output