zenrouter

Implement and extend routing using the zenrouter Coordinator pattern. Use this skill when the user asks to add a route, feature module, layout, redirect rule, redirect guard, nested coordinator, or navigate programmatically. Triggers on: route, router, routing, coordinator, CoordinatorModular, RouteModule, NavigationPath, IndexedStackPath, RouteLayout, RouteRedirectRule, RedirectRule, RedirectResult, parseRouteFromUri, push, pop, replace, navigate, deep link, toUri, parentLayoutKey, layoutKey, bindLayout, RouteUnique, RouteGuard, RouteRedirect, notFoundRoute, zenrouter.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "zenrouter" with this command: npx skills add definev/zenrouter/definev-zenrouter-zenrouter

ZenRouter Skill

This project uses zenrouter — a Flutter router that supports imperative, declarative, and coordinator-based navigation. This skill covers the Coordinator + RouteModule pattern, which is the right choice when you need deep linking, URL sync, layouts, and modular feature organisation.

Deep-dive references — read these only when you need the specific topic:

FileWhen to read
ADVANCED.mdCoordinator-as-Module, tab navigation, composable redirect rules, parameter route examples
MIXIN.mdFull reference for RouteGuard, RouteRedirect, RouteDeepLink, RouteTransition, RouteQueryParameters, RouteRestorable
NAVIGATION.mdWhen to use push vs navigate vs replace and other navigation methods

Key Types

TypePackagePurpose
RouteTargetzenrouter_coreBase class for all routes; identity via props
RouteUniquezenrouter_coreMixin — adds URI identity; required for coordinator routes
Coordinator<T>zenrouterCentral hub: owns NavigationPaths, parses URIs, drives Flutter Router
CoordinatorModular<T>zenrouter_coreMixin on Coordinator — splits route parsing across RouteModules
RouteModule<T>zenrouter_coreHandles one feature's URI patterns and navigation paths
NavigationPath<T>zenrouterMutable stack of routes; one per layout group
IndexedStackPath<T>zenrouterFixed set of routes for tab-bar style navigation
RouteLayout<T>zenrouterMixin — layout route that wraps nested routes (shell, tab bar, drawer, etc.)
RouteRedirectRule<T>zenrouter_coreMixin — delegates redirect logic to a list of RedirectRules
RedirectRule<T>zenrouter_coreSingle composable guard; returns continueRedirect, redirectTo, or stop

1. Route Base Type

All routes in a coordinator must extend RouteTarget with RouteUnique:

abstract class AppRoute extends RouteTarget with RouteUnique {
  @override
  Widget build(covariant Coordinator coordinator, BuildContext context);
}

2. Coordinator

Simple (no modules)

class AppCoordinator extends Coordinator<AppRoute> {
  late final homeStack = NavigationPath<AppRoute>.createWith(
    label: 'home',
    coordinator: this,
  )..bindLayout(HomeLayout.new);

  @override
  List<StackPath> get paths => [...super.paths, homeStack];

  @override
  AppRoute parseRouteFromUri(Uri uri) => switch (uri.pathSegments) {
    [] || ['home'] => HomeRoute(),
    ['product', final id] => ProductRoute(id: id),
    _ => NotFoundRoute(uri: uri),
  };
}

// Wire up:
MaterialApp.router(routerConfig: AppCoordinator())

Modular (with feature modules)

Add CoordinatorModular<T> to delegate URI parsing across feature modules:

class AppCoordinator extends Coordinator<AppRoute>
    with CoordinatorModular<AppRoute> {
  @override
  Set<RouteModule<AppRoute>> defineModules() => {
    AuthModule(this),
    ShopModule(this),
    ProfileModule(this),
  };

  @override
  AppRoute notFoundRoute(Uri uri) => NotFoundRoute(uri: uri);
}

Rules:

  • Module order in defineModules() determines parsing priority — first non-null result wins.
  • CoordinatorModular overrides parseRouteFromUri automatically; do not override it.
  • notFoundRoute is called when all modules return null.

For nested feature groups with sub-modules, see Coordinator as Module in ADVANCED.md.


3. Route Module

class ShopModule extends RouteModule<AppRoute> {
  ShopModule(super.coordinator);

  late final shopStack = NavigationPath<AppRoute>.createWith(
    coordinator: coordinator,   // ← always the inherited `coordinator` field (= root)
    label: 'shop',
  )..bindLayout(ShopLayout.new);

  @override
  List<StackPath> get paths => [shopStack];

  @override
  FutureOr<AppRoute?> parseRouteFromUri(Uri uri) => switch (uri.pathSegments) {
    ['shop'] => ShopHomeRoute(),
    ['shop', 'products', final id] => ProductDetailRoute(id: id),
    _ => null,   // ← MUST return null for unrecognised URIs
  };
}

Rules:

  • Always return null for unrecognised URIs so other modules can claim them.
  • Use coordinator (inherited field) for NavigationPath.createWith — it always refers to the root coordinator that owns the navigation state.
  • bindLayout(LayoutClass.new) takes the constructor, not an instance.

4. Route Definition

// Standard route
class ShopHomeRoute extends AppRoute {
  @override
  Object? get parentLayoutKey => ShopLayout;   // matches RouteLayout.layoutKey (default: runtimeType)

  @override
  Uri toUri() => Uri.parse('/shop');

  @override
  Widget build(covariant AppCoordinator coordinator, BuildContext context) {
    return ShopHomePage(
      onProductTap: (id) => coordinator.push(ProductDetailRoute(id: id)),
    );
  }
}

// Route with parameters — must override props
class ProductDetailRoute extends AppRoute {
  ProductDetailRoute({required this.id});
  final String id;

  @override
  List<Object?> get props => [id];

  @override
  Object? get parentLayoutKey => ShopLayout;

  @override
  Uri toUri() => Uri.parse('/shop/products/$id');

  @override
  Widget build(covariant AppCoordinator coordinator, BuildContext context) =>
      ProductDetailPage(id: id);
}

Rules:

  • parentLayoutKey must exactly match layoutKey of the target RouteLayout. Default layoutKey is runtimeType, so using the layout class Type is simplest.
  • Override props for routes with parameters.
  • toUri() is used for deep linking and URL sync.

For redirect-only routes, see RedirectRule in ADVANCED.md.


5. Layout

class ShopLayout extends AppRoute with RouteLayout<AppRoute> {
  @override
  StackPath<AppRoute> resolvePath(covariant AppCoordinator coordinator) =>
      coordinator.getModule<ShopCoordinator>().shopStack;

  // layoutKey defaults to runtimeType — override only if you need a custom value
  // @override Object get layoutKey => 'ShopLayout';

  @override
  Widget build(covariant AppCoordinator coordinator, BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Shop')),
      body: buildPath(coordinator),   // renders the active child route
    );
  }
}

Rules:

  • Use buildPath(coordinator) to render child routes — do not call super.build().
  • resolvePath must return the exact NavigationPath that was bindLayout-ed in the module.
  • For tab navigation, see IndexedStackPath in ADVANCED.md.

6. Navigation

coordinator.push(ProductDetailRoute(id: '42'));    // add to stack
coordinator.navigate(ShopHomeRoute());             // pop-to-existing or push new
coordinator.replace(SettingsRoute());              // full state reset
coordinator.pop();                                 // pop top route

// Navigate from a URI string (e.g. deep link):
final route = await coordinator.parseRouteFromUri(Uri.parse('/shop/products/42'));
coordinator.push(route!);

Redirect rules run automatically on every navigation call.

For full details on pushReplacement, pushOrMoveToTop, tryPop, recover, and decision flowcharts, see NAVIGATION.md.


7. File Structure Convention

lib/src/router/
├── coordinator.dart          ← Root Coordinator or Coordinator-as-Module
├── route.dart                ← Base route type (e.g. AppRoute)
├── _public.dart              ← Barrel: export module + public routes + public rules
├── rules/
│   ├── auth_required.dart
│   └── force_redirect.dart
└── routes/
    ├── (auth)/               ← Route group (shares a layout)
    │   ├── _layout.dart      ← Layout for this group
    │   ├── sign_in.dart      ← /sign-in
    │   └── forgot_password.dart
    ├── (dashboard)/
    │   ├── _layout.dart
    │   ├── _index.dart       ← Index / redirect-only route
    │   └── transactions/     ← URI segment directory
    │       ├── _index.dart   ← /transactions
    │       └── [id].dart     ← /transactions/:id  (named parameter route)
    │   └── blog/
    │       ├── _layout.dart   ← blog layout
    │       └── [...slug].dart ← /blog/*             (catch-all parameter route)
    └── not_found.dart

Conventions:

  • (group)/ — parenthesised directories are layout groups (organisational only). They do not appear in the URI. Routes inside share a layout.
  • group/ — bare directories (no parentheses) do appear in the URI. transactions/ → the URI includes /transactions/....
  • _layout.dart — the RouteLayout for its group; prefixed with _ because it's structural, not a user-facing route.
  • _index.dart — the index route for a directory (often a redirect-only route).
  • [param].dart — a named parameter route file. The brackets mirror dynamic URI segments (e.g. [id].dart/transactions/:id).
  • [...param].dart — a catch-all parameter route file. Captures all remaining URI segments as a single list (e.g. [...slug].dart/blog/*).
  • _public.dart — barrel file that exports only public symbols.
  • rules/ — reusable RedirectRule implementations.

For detailed examples of parameter route classes, see Named Parameter Routes and Catch-All Parameter Routes in ADVANCED.md.


8. Naming Conventions

Route Classes

PatternExampleWhen
<Feature>RouteSignInRoute, TransactionRouteStandard page route
<Feature>DetailRouteTransactionDetailRouteDetail page with [id]
<Feature>IndexRouteDashboardIndexRouteIndex / redirect-only route
<Feature>TabHomeTab, ShopTabTab in an IndexedStackPath
NotFoundRouteNotFoundRoute404 catch-all

Layout, Module, and Rule Classes

PatternExampleWhen
<Feature>LayoutAuthLayout, DashboardLayoutLayout shell
<Feature>ModuleAuthModule, ShopModuleSimple RouteModule
<Feature>CoordinatorShopCoordinatorCoordinator-as-Module (has sub-modules)
<Condition>RuleAuthRequiredRule, AlreadyAuthRuleRedirect rule

URI Patterns

PatternExampleRoute
/feature/sign-inSignInRoute
/feature/:id/transaction/abc123TransactionDetailRoute(id: 'abc123')
/group/feature/shop/productsProductListRoute
/group/feature/:id/shop/products/42ProductDetailRoute(id: '42')

Use kebab-case for URI segments. Use singular nouns for resource detail paths (/transaction/:id not /transactions/:id).

NavigationPath labels use kebab-case: 'auth', 'dashboard', 'shop-products'.


9. Adding a New Feature: Checklist

  1. Create route — extend base route type; set parentLayoutKey, toUri(), build(), props.
  2. Register in parseRouteFromUri of the owning module.
  3. Export from _public.dart barrel if navigated to from outside.
  4. New layout group? — create (group)/_layout.dart with RouteLayout, new NavigationPath with bindLayout, add to paths.
  5. New module? — create module.dart extending RouteModule<T>, register in parent's defineModules().
  6. New feature group with sub-modules? — use Coordinator-as-Module pattern.

Common Mistakes

MistakeFix
parseRouteFromUri in a module not returning null for non-owned URIsMust return null so other modules can claim the URI
parentLayoutKey doesn't match layoutKeyDefault layoutKey is usually runtimeType — use the layout class Type as parentLayoutKey
Forgetting ...super.paths when overriding pathsAlways spread super.paths to include root and inherited module paths
Overriding parseRouteFromUri on a CoordinatorModular coordinatorDon't — CoordinatorModular handles it; override notFoundRoute instead
Standalone Coordinator returning null from parseRouteFromUriStandalone coordinators must never return null; add a catch-all case

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Web3

Element Nft Tracker

Element Market API integration. Requires 'ELEMENT_API_KEY' configured in OpenClaw secrets. Strictly requires explicit user consent before accessing personal...

Registry SourceRecently Updated
930Profile unavailable
Web3

Onchain Analyzer

Analyze wallet addresses and on-chain activity — transaction history, token holdings, DeFi positions, and trading patterns across EVM chains and Solana. Use...

Registry SourceRecently Updated
1390Profile unavailable
Web3

Crypto Tracker Cn

Track cryptocurrency markets in real-time. 加密货币行情追踪、比特币价格、以太坊ETH、市值排行、DeFi数据、恐惧贪婪指数、趋势币种、空投信息、RSI技术分析、均线分析、金叉死叉、DeFi收益率对比、Gas费查询。Use when checking crypto pri...

Registry SourceRecently Updated
2030Profile unavailable
Web3

Crypto Whale Tracker

Track large cryptocurrency transfers (whale movements) using public APIs like Whale Alert and Etherscan. Set thresholds, monitor wallets, and generate format...

Registry SourceRecently Updated
1740Profile unavailable