With go_router, how do you show a full-screen page above the bottom navigation bar from a route nested inside a shell?
answer
- which Navigator a page lands on
- a GlobalKey for the root Navigator
- pass it to GoRouter's navigatorKey
- set parentNavigatorKey on the GoRoute
- must name an ancestor's key
basics
~20 sGive GoRouter a GlobalKey<NavigatorState> as navigatorKey and set that same key as the parentNavigatorKey of the nested GoRoute; the page is then placed on the root Navigator above the shell, hiding the bar, while its URL stays nested.
solid answer
~40 sBy default a `GoRoute` under a `ShellRoute` or a `StatefulShellBranch` is placed on the nearest shell's Navigator, so it appears inside the body with the bar still visible. To cover the bar, for example a full-screen photo viewer or a compose-post page in a social app, I create `final rootNavigatorKey = GlobalKey<NavigatorState>()`, pass it to `GoRouter(navigatorKey: rootNavigatorKey)`, and set `parentNavigatorKey: rootNavigatorKey` on that route. go_router then stacks it on the root Navigator, above the whole shell, while its path can stay nested (`/feed/post/:postId/photo`). The key must belong to an ancestor: the `GoRouter`'s key or an enclosing shell's; go_router asserts this. The same property can target an outer shell's Navigator in nested shells. Back or `context.pop()` then returns to the page under the shell.
code
dart · 38 linesimport 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
final GlobalKey<NavigatorState> rootNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'root');
final GoRouter router = GoRouter(
navigatorKey: rootNavigatorKey,
initialLocation: '/feed',
routes: <RouteBase>[
StatefulShellRoute.indexedStack(
builder: (BuildContext context, GoRouterState state, StatefulNavigationShell navigationShell) =>
Scaffold(body: navigationShell),
branches: <StatefulShellBranch>[
StatefulShellBranch(
routes: <RouteBase>[
GoRoute(
path: '/feed',
builder: (BuildContext context, GoRouterState state) => const Text('Feed'),
routes: <RouteBase>[
GoRoute(
path: 'post/:postId',
builder: (BuildContext context, GoRouterState state) => const Text('Post'),
routes: <RouteBase>[
GoRoute(
path: 'photo',
parentNavigatorKey: rootNavigatorKey, // covers the whole shell
builder: (BuildContext context, GoRouterState state) => const Text('Photo'),
),
],
),
],
),
],
),
],
),
],
);go deeper
Recall that a GoRoute under a shell lands on the shell's Navigator unless parentNavigatorKey points it at the root key passed to GoRouter.
Explain the default placement rule, the ancestor-only assertion, and how nesting with parentNavigatorKey preserves the stack beneath a full-screen page.
Show you design full-screen flows so deep links, back navigation and transitions behave the same as taps, choosing nesting over top-level routes deliberately.
Set a navigation convention for the app: which flows cover the chrome, which stay inside tabs, and how keys are owned so teams do not collide.
## Which Navigator shows a page A go_router app with shells has **several Navigators**: the root one created by the `GoRouter`, one per `ShellRoute`, and one per `StatefulShellBranch`. Every matched `GoRoute` is placed on exactly one of them. The default rule is simple: a route goes onto the Navigator of its **nearest shell ancestor**, or onto the root Navigator when it has none. That default is what keeps pages inside the shell's body. It is also why a photo viewer opened from a post in a social app appears with the bottom bar still under it, which is usually wrong for full-screen media, a compose page, or a modal-style flow. ## parentNavigatorKey `GoRoute.parentNavigatorKey` (and the same field on shell routes) overrides the default by naming the Navigator to use: 1. Create a key: `final GlobalKey<NavigatorState> rootNavigatorKey = GlobalKey<NavigatorState>();` 2. Hand it to the router: `GoRouter(navigatorKey: rootNavigatorKey, ...)`. Without this, go_router creates its own root key, and your key would refer to nothing. 3. Set `parentNavigatorKey: rootNavigatorKey` on the route that must cover the shell. The page is now stacked on the root Navigator, **above** the shell widget and its bar. Its path is unaffected: it can remain a child route such as `photo` under `/feed/post/:postId`, so the location, deep links and the pages under it stay the same. ## The rules go_router enforces - The key must be the `GoRouter`'s `navigatorKey` or the `navigatorKey` of an **ancestor** `ShellRoute` or branch; anything else fails an assertion reading that the key must refer to an ancestor ShellRoute's navigatorKey or GoRouter's navigatorKey. - Once a route jumps to an outer Navigator, its descendants may only use that Navigator or ones **above** it. - Branch Navigator keys must be unique and must not reuse an ancestor's key. | Route | `parentNavigatorKey` | Shown on | Bar visible | |---|---|---|---| | `/feed` | none | Feed branch Navigator | yes | | `/feed/post/:postId` | none | Feed branch Navigator | yes | | `/feed/post/:postId/photo` | `rootNavigatorKey` | root Navigator | no | | `/compose` (outside the shell) | none | root Navigator | no | ## Choosing between nesting and a top-level route A full-screen page can also be declared **outside** the shell as a top-level route (`/compose`). The difference is what sits beneath it: - With `parentNavigatorKey` on a nested route, the location keeps the parent path, so going to `/feed/post/7/photo` builds Feed, then the post inside the shell, then the photo above everything. Back returns to the post. - As a top-level route, `/compose` has no shell under it on a deep link; back from a cold-start link has nowhere to go inside the app. Nesting with `parentNavigatorKey` is the better fit when the full-screen page belongs to a specific context, such as a photo of a specific post. ## Pushing into shells The same placement logic explains how `context.push` behaves near shells. Pushing a route with no shell over a page inside a shell places it entirely on top; pushing a route of the **same** shell places it inside the shell; pushing a route of a **different** shell places that shell and the page on top of the current screen. ## Nested shells and outer Navigators `parentNavigatorKey` is not limited to the root. In a layout with a `StatefulShellRoute` for the tabs and, inside the Profile branch, a `ShellRoute` that adds a settings side panel, a route deep in the settings shell can name the **Profile branch Navigator's key** to appear over the side panel but still under the bottom bar. The rule is the same at every level: the named Navigator must be an ancestor, and the page is drawn by that Navigator with that Navigator's transition. This is also why the page's entrance animation changes: a route shown on the root Navigator slides or fades over the entire screen, bar included, while the same route on a branch Navigator animates only inside the shell's body. ## Mistakes to avoid - Creating the key but not passing it to `GoRouter(navigatorKey: ...)`. - Using a sibling branch's key, which is not an ancestor and fails the assertion. - Hiding the bar with a flag in the shell builder instead of changing which Navigator shows the page, which leaves the page's transition clipped to the body.
- With go_router, what happens if a route's parentNavigatorKey names a sibling branch's Navigator?go_router's configuration check fails an assertion in debug builds: the key must refer to an ancestor ShellRoute's navigatorKey or the GoRouter's navigatorKey. A sibling branch is not an ancestor, so the route cannot be placed there.
- With go_router, what is beneath a nested photo route with parentNavigatorKey set to the root key when opened by a deep link?go builds the stack from the route tree: the shell with the Feed branch and the post page, then the photo page on the root Navigator above the shell. Back therefore returns to the post inside its tab.
- With go_router, what does context.push do with a route that belongs to the same shell as the current page?The pushed page is placed inside that shell, on its nested Navigator, so the bar stays visible. A route with no shell is placed entirely on top, and a route of a different shell is pushed together with its own shell above the current screen.
saying these in an interview costs you the question
- Believes parentNavigatorKey changes the route's URL path.
- Creates a root key but never passes it to GoRouter's navigatorKey.
- Hides the bar with a flag rather than placing the page on the root Navigator.
- Points parentNavigatorKey at a sibling branch's Navigator.
- Thinks every route under a shell is always drawn inside the shell.