With go_router_builder, what do typed routes built from TypedGoRoute and GoRouteData give you over string paths?
answer
- annotation plus a generated part file
- constructor fields become path or query
- a field named $extra
- generated location, go and push
- $appRoutes feeds the GoRouter
basics
~20 sgo_router_builder reads @TypedGoRoute annotations on GoRouteData subclasses and generates a mixin with location, go, push and replace plus a $appRoutes list, so path and query parameters become typed constructor fields and a missing or mistyped one is a compile-time error.
solid answer
~30 sPlain go_router routes take strings: `context.go('/accounts/$id')` and `int.parse(state.pathParameters['id']!)` only fail at run time. With go_router_builder, each route is a class extending `GoRouteData` with the generated mixin (`with $AccountRoute`), annotated `@TypedGoRoute<AccountRoute>(path: '/accounts/:accountId')`. Constructor fields named in the path become path parameters, the others become query parameters, and a field called `$extra` carries `extra`. The generator converts `int`, `bool`, enums and extension types, and writes `location`, `go(context)`, `push<T>(context)`, `pushReplacement` and `replace`, plus a `$appRoutes` list for `GoRouter(routes: $appRoutes)`. So `AccountRoute(accountId: 42).go(context)` is checked by the compiler. Runtime matching is unchanged; the gain is at the call sites.
code
dart · 34 linesimport 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
part 'routes.g.dart';
enum Period { week, month, year }
@TypedGoRoute<AccountsRoute>(
path: '/accounts',
routes: <TypedGoRoute<GoRouteData>>[
TypedGoRoute<AccountRoute>(path: ':accountId'),
],
)
class AccountsRoute extends GoRouteData with $AccountsRoute {
const AccountsRoute();
@override
Widget build(BuildContext context, GoRouterState state) => const Placeholder();
}
class AccountRoute extends GoRouteData with $AccountRoute {
const AccountRoute({required this.accountId, this.period = Period.month});
final int accountId; // path parameter
final Period period; // query parameter, omitted when it equals the default
@override
Widget build(BuildContext context, GoRouterState state) => Text('$accountId ${period.name}');
}
final GoRouter router = GoRouter(routes: $appRoutes);
void openYear(BuildContext context) =>
const AccountRoute(accountId: 42, period: Period.year).go(context);go deeper
Recall that a GoRouteData subclass with a TypedGoRoute annotation gets generated location, go and push methods and joins $appRoutes.
Explain how fields map to path, query and $extra, which types convert automatically, and that runtime matching still uses plain GoRoute objects.
Judge when code generation pays off for a route table, and use build-time duplicate detection to catch unreachable routes before users do.
Decide whether a whole codebase standardises on typed routes, weighing refactor safety against the generation step and the cost of two routing styles.
## The problem typed routes solve go_router matches **strings**. Navigation calls interpolate strings, and builders read `state.pathParameters` and `state.uri.queryParameters`, which are `String` maps. Nothing checks at compile time that `/accounts/abc` has a numeric id, that a required parameter was supplied, or that a renamed path was updated everywhere. go_router_builder, a code generator published next to go_router, turns each route into a Dart class so those mistakes become compile errors. ## Declaring a typed route 1. Add `go_router_builder` and `build_runner` as dev dependencies and put `part 'routes.g.dart';` in the routes library. 2. Write a class that **extends `GoRouteData`** and mixes in the generated mixin, named `$` plus the class name: `class AccountRoute extends GoRouteData with $AccountRoute`. 3. Annotate the top-level route with `@TypedGoRoute<HomeRoute>(path: '/', routes: [TypedGoRoute<AccountRoute>(path: 'accounts/:accountId')])`; children are nested in the annotation's `routes` list. 4. Override `build(BuildContext, GoRouterState)`, or `buildPage` to control the `Page`, and optionally `redirect`. 5. Run the build and pass the generated top-level list: `GoRouter(routes: $appRoutes)`. ## How constructor fields map to the URL | Constructor field | Becomes | Notes | |---|---|---| | named in the path (`:accountId`) | path parameter | required | | not in the path, nullable | optional query parameter | omitted when `null` | | not in the path, with a default | query parameter | omitted when equal to the default | | named `$extra` | `extra` | passed beside the URL | - Simple types such as `int`, `bool`, enums and extension types are converted to and from the underlying strings. - A query key defaults to the kebab-case form of the field name (`myParameter` becomes `my-parameter`); the `TypedQueryParameter` annotation renames it, and its `encoder`, `decoder` and `compare` arguments (go_router_builder 4.3.0) handle custom types such as a `DateTime`. - `$extra` still travels outside the location: it does not survive a deep link or a browser refresh, exactly like `extra` on plain routes. ## What the generator writes - A **mixin** per route with a `location` getter and `go(context)`, `push<T>(context)`, `pushReplacement(context)` and `replace(context)`; since go_router 16.0 these live on `GoRouteData` through the mixin, which needs go_router_builder 3.0 or later. - A top-level `List<RouteBase> get $appRoutes` that aggregates every top-level typed route. - Plain `GoRoute` objects under the hood, so matching, redirects and error handling behave exactly as with hand-written routes. Typed routes also combine with redirects: a redirect can return `LoginRoute(from: state.matchedLocation).location` instead of a hand-built string, so the login path and its query key live in one place. ## Navigating, returning values and customising pages - `const AccountsRoute().go(context)` replaces the stack; `AccountRoute(accountId: 42).push<bool>(context)` stacks the page and returns `Future<bool?>` completed by `context.pop(true)`, just as `context.push<bool>` does on plain routes. - `location` returns the encoded URL string, useful for redirects, links and tests. - Overriding `buildPage` instead of `build` returns a custom `Page`, for example a `MaterialPage` with a fixed key; by default the builder uses the page type of the enclosing app with `state.pageKey`. - A route class may override `redirect(BuildContext, GoRouterState)`; a route whose redirect is unconditional does not need a `build` method at all. ## Build-time checks beyond types go_router_builder 4.5.0 detects **routes that resolve to the same URL pattern**, even across nesting, shell routes and parameter renames (`product/:id` and `product/:productId` collide). The `duplicate_route_paths` builder option in `build.yaml` sets the reaction: `warning` (the default), `error` or `ignore`. ## Trade-offs - **Gains:** compile-time parameter checks, one definition of each path, typed values in `build`, refactor-safe navigation. - **Costs:** a code-generation step in the workflow, generated files to keep current, and a second way of defining routes that a mixed codebase must reconcile. - It suits apps with many parameterised routes; a handful of static paths rarely justifies it.
- With go_router_builder, how does the generator decide whether a constructor field is a path or a query parameter?A field whose name appears as a `:name` segment in the `TypedGoRoute` path is a path parameter. Any other field is a query parameter, optional when nullable or when it has a default, and a field named `$extra` is sent as `extra`.
- With go_router_builder 4.5, what does the duplicate_route_paths builder option control?The build detects typed routes that resolve to the same URL pattern, even across nesting or with renamed parameters. The option in `build.yaml` sets the reaction: `warning` by default, `error` to fail the build, or `ignore` for deliberate duplicates.
- With go_router_builder, does a $extra field make a typed route safe for deep links?No. `$extra` is passed beside the location exactly like plain `extra`, so a deep link, a typed URL or a web refresh does not carry it. Anything a deep link must reproduce belongs in path or query fields.
saying these in an interview costs you the question
- Thinks typed routes change how go_router matches URLs at run time.
- Believes the generator encodes a $extra field into the URL.
- Edits the generated .g.dart file by hand to add a route.
- Expects every constructor field to become a path parameter.
- Assumes typed routes remove the need for redirect callbacks.