Skip to content

Web

raku(...) is a standard Navigator 2.0 RouterConfig, so the browser’s address bar and back/forward buttons drive it for free — each typed navigation updates the URL, and a back/forward delivers the previous URL, which the router reconciles in place: you land on the right screen with the other tabs’ history and unchanged screens’ state intact (not a rebuilt-from-scratch tree).

raku_router stays dependency-free, so the URL strategy is yours to pick — call it once in main() before runApp:

import 'package:flutter_web_plugins/url_strategy.dart';
void main() {
usePathUrlStrategy(); // clean paths (/feed/notes/42) instead of the hash (/#/…)
runApp(const MyApp());
}

Omit it for the default hash strategy. Either way the same route tree resolves.

A cold deep link starts inactive tabs at their initial route (a URL can only encode the active path). But within a session, browser back/forward preserves inactive tabs’ history and the element state of unchanged screens — verified by a back/forward sequence test.

raku(...) returns a RakuRouter — a RouterConfig that also exposes the tree’s reverse direction. Turn a typed route into its URL for a share link, a deep link, or an <a href>:

final router = raku(initial: const Home(), routes: [...]);
router.hrefOf(const Note('42')); // '/feed/notes/42'
router.uriOf(const Search('shoes')); // Uri: /search?q=shoes

It’s built by the same tree that parses URLs, so route → URL and URL → route can never drift — there’s no second, hand-written “path builder” to keep in sync. The value is the app-internal location; under the default hash strategy a real anchor’s href is that value after a #.

Give a node a title: to set the browser tab (and Android task-switcher) label while that route is the active leaf. Derive it from the route so detail pages read well:

route('notes/:id', (p) => Note(p('id')), (n) => NoteScreen(id: n.id),
title: (n) => 'Note ${n.id}');

It’s opt-in and per-route: a route with no title: leaves the label untouched, and if you declare none the platform is never called. Under the hood it uses the same SystemChrome.setApplicationSwitcherDescription call as Flutter’s Title widget — and because raku sets it deeper in the tree, it wins over MaterialApp.title.

context.replaceSilently(route) updates the address bar in place — no new browser history entry, so back/forward skips it. Use it for URL state that should be shareable and restorable but shouldn’t clutter history: a search query, an active filter, a within-page selection.

onChanged: (q) => context.replaceSilently(Search(q)),
// one history entry, not one per keystroke

It wraps Flutter’s Router.neglect (which works because raku’s delegate reports currentConfiguration). Outside a deep-linked app — no Router, no URL — it degrades to a plain replace.