Migrating from go_router

Migrating from go_router

go_router gives you a route table the framework walks to decide which pages to show. back_stack gives you the stack itself — a List of typed destinations you push and pop. Migrating is mostly deleting the route tree and moving navigation from "describe a path" to "change a list."

The shift: in go_router the path is the source of truth and the stack is derived. In back_stack the stack is the source of truth and the URL is a projection of it. That's what removes the go vs push question.

At a glance

go_routerback_stack
GoRoute(path: '/product/:id', builder: …)a typed class Product extends AppKey { final int id; } + ..on<Product>(…)
the routes table (URL side)a NavLinks table — one entry per URL, both directions at once
context.go('/product/42')stack.replaceAll([Home(), Product(42)])
context.push('/product/42')stack.push(Product(42))
context.pop()stack.pop()
context.pushReplacement(…)stack.replaceTop(…)
state.pathParameters['id'] / state.extrakey.id — a real, typed field
redirect: (can loop)redirect (pure, runs once) + guard
async redirectAsyncRedirect
refreshListenable:stack.refreshListenable
ShellRoute / StatefulShellRouteBackStackTabsApp (one widget)
errorBuilder: / "no route found"NavLinks.notFound — only the deep-link boundary is untyped
GoRouter.of(context)BackStack.of<AppKey>(context)
routerConfig + MaterialApp.routerBackStackApp(stack: …, entries: …)

Routes → typed destinations

// go_router
GoRoute(
  path: '/product/:id',
  builder: (c, s) => ProductScreen(id: int.parse(s.pathParameters['id']!)),
);
 
// back_stack
class Product extends AppKey { const Product(this.id); final int id; }
final entries = NavEntries<AppKey>()
  ..on<Product>((context, key) => ProductScreen(id: key.id)); // key.id is an int

int.parse(...!) and state.extra as T casts are gone — the destination is the typed argument bundle.

Navigation

Intentgo_routerback_stack
Reset the stackcontext.go('/home')stack.replaceAll([const Home()])
Pushcontext.push('/product/42')stack.push(const Product(42))
Popcontext.pop()stack.pop()
Reuse an open screenmanualstack.pushOrMoveToTop(const Product(42))

What you can delete

  • The route-tree list and every path: string.
  • int.parse(state.pathParameters[...]!) and state.extra as T casts.
  • errorBuilder / "unknown route" screens for in-app navigation.
  • redirectLimit and redirect-loop worries.
  • context.go vs context.push decisions.

Gradual migration

NavDisplay is a normal widget, so you can host a back_stack-driven subtree inside an existing go_router route (give it a distinct key type) and migrate feature by feature — or flip the root to BackStackApp and port screens into NavEntries one ..on<T>() at a time.

The full guide, with before/after for every section, is in the repo: doc/MIGRATING_FROM_GO_ROUTER.md (opens in a new tab).