Repository navigation
[go_router] onEnter only triggers once (no recursive redirection) #188014
Copy link
Copy link
Closed
flutter/packages
#12337Closed
Copy link
Labels
P2Important issues not at the top of the work listImportant issues not at the top of the work listf: routesNavigator, Router, and related APIs.Navigator, Router, and related APIs.frameworkflutter/packages/flutter repository. See also f: labels.flutter/packages/flutter repository. See also f: labels.has reproducible stepsThe issue has been confirmed reproducible and is ready to work onThe issue has been confirmed reproducible and is ready to work onp: go_routerThe go_router packageThe go_router packagepackageflutter/packages repository. See also p: labels.flutter/packages repository. See also p: labels.team-frameworkOwned by Framework teamOwned by Framework teamtriaged-frameworkTriaged by Framework teamTriaged by Framework team
Description
Steps to reproduce
- Use the Code Sample
- Add go_router to
pubspec.yaml - Run the App
The onEnter is only called once for the '/' Router, but not after the redirect for the '/a' route.
If you hit F5 refresh the onEnter is applied "again" and redirects to '/b'.
Related Issue: #178984
Related MR: flutter/packages#11108
Expected results
Page 'B' is visible.
Actual results
Page 'A' is visible
Code sample
Code sample
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({super.key});
final router = GoRouter(
initialLocation: '/',
onEnter: (context, currentState, nextState, goRouter) {
if (nextState.path == '/a') {
return Block.then(() => goRouter.go('/b'));
}
return Allow();
},
routes: [
GoRoute(
path: '/',
redirect: (context, state) {
return '/a';
},
),
GoRoute(
path: '/a',
builder: (_, _) => _TestWidget(identifier: 'A'),
),
GoRoute(
path: '/b',
builder: (_, _) => _TestWidget(identifier: 'B'),
),
],
);
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'Flutter Demo',
theme: ThemeData(colorScheme: .fromSeed(seedColor: Colors.deepPurple)),
routerConfig: router,
);
}
}
class _TestWidget extends StatelessWidget {
const _TestWidget({super.key, required this.identifier});
final String identifier;
@override
Widget build(BuildContext context) {
return Scaffold(body: Column(children: [Text(identifier)]));
}
}
Screenshots or Video
No response
Logs
Logs
Flutter 3.38.9 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 67323de285 (5 months ago) • 2026-01-28 13:43:12 -0800
Engine • hash 5eb06b7ad5bb8cbc22c5230264c7a00ceac7674b (revision 587c18f873) (4
months ago) • 2026-01-27 23:23:03.000Z
Tools • Dart 3.10.8 • DevTools 2.51.1go_router: ^17.3.0Flutter Doctor output
Doctor output
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.38.9, on macOS 26.3.1 25D771280a darwin-arm64, locale
en-DE)
[✓] Android toolchain - develop for Android devices (Android SDK version 36.1.0)
[✓] Xcode - develop for iOS and macOS (Xcode 26.2)
[✓] Chrome - develop for the web
[✓] Connected device (2 available)
[✓] Network resources
• No issues found!Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
P2Important issues not at the top of the work listImportant issues not at the top of the work listf: routesNavigator, Router, and related APIs.Navigator, Router, and related APIs.frameworkflutter/packages/flutter repository. See also f: labels.flutter/packages/flutter repository. See also f: labels.has reproducible stepsThe issue has been confirmed reproducible and is ready to work onThe issue has been confirmed reproducible and is ready to work onp: go_routerThe go_router packageThe go_router packagepackageflutter/packages repository. See also p: labels.flutter/packages repository. See also p: labels.team-frameworkOwned by Framework teamOwned by Framework teamtriaged-frameworkTriaged by Framework teamTriaged by Framework team
Activity
mbcorona commentedon Jun 15, 2026
Hi @jlnrrg , thanks for reporting. Routing to the framework team for evaluation.
Dhritimann commentedon Jul 17, 2026
Hi, I'd like to take this one if it's not already assigned.
From reading through router.dart and configuration.dart, my understanding of the root cause is : the documented order of operations is onEnter → top-level redirect → route-level GoRoute.redirect, and onEnter is only evaluated once, against the original location, before the redirect loop resolves. When a route-level redirect (e.g. / → /a) changes the matched location, onEnter isn't re-invoked against the new location, so a guard meant to intercept /a never fires. This looks like the same class of issue that #178984 / PR #11108 addressed for top-level redirect re-evaluation, but it doesn't seem to extend to onEnter.
My plan is to:
reproduce locally with the sample from this issue
modify the redirect-resolution loop in configuration.dart so onEnter is re-run against the newly resolved GoRouterState whenever a redirect changes the location, reusing the existing redirectLimit guard to prevent infinite loops
add a regression test in test/redirects_test.dart (or a dedicated onEnter test) covering this exact repro
Let me know if this direction sounds right or if there's context I'm missing before I start on the PR.
developerashkan commentedon Aug 1, 2026
Traced this against main — turns out this is documented, intended behavior rather than a bug. onEnter is evaluated once against the literal navigated-to URI, strictly before both legacy top-level redirect: and GoRoute.redirect: run, and it's never re-invoked as those redirects resolve intermediate hops. There's actually an existing regression test ('onEnter called once when top-level redirect chains', written for the related
#178984) that asserts this exact ordering for the same / → /a → /b pattern.
So next.uri.path will never equal /a here, because onEnter only ever sees the pre-redirect target (/). The fix is to put the guard inside a redirect: callback instead of onEnter — redirect: does see each resolved hop, since it's what produces them.
Since this is intentional and already covered by tests, I don't think there's a code bug to fix, but the doc comment didn't make this visibility gap explicit, so opened a small PR clarifying it and adding a regression test for this exact scenario plus one showing the workaround: flutter/packages#12337
jlnrrg commentedon Aug 6, 2026
Thank you for your effort of researching this "bug".
I would still question if this is supposed be the intended behaviour, as
onEnterwas at one point supposed to replace theredirectmethod.As far as I can see @omar-hanafy wanted to keep the deprecated
redirectonly for backwards compatibility flutter/packages#8339 (comment).I am unsure though if that changed further down the line.
Dhritimann commentedon Aug 7, 2026
Thanks for digging into this @developerashkan — makes sense. I'll hold off on a code change until the onEnter-vs-redirect precedence question is settled by maintainers. Happy to help with the regression tests on #12337 if useful.
[go_router] Clarify onEnter/redirect ordering, add regression test (#…
jlnrrg commentedon Aug 25, 2026
Thank you for the clarification doc addition @developerashkan, but tbh I don't think this issue should be closed, as we are still waiting for feedback.
As I was not a contributior, I'm not allowed to reopen this issue though.
[go_router] Clarify onEnter/redirect ordering, add regression test (f…
github-actions commentedon Sep 8, 2026
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of
flutter doctor -vand a minimal reproduction of the issue.