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).
URL strategy
Section titled “URL strategy”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.
What’s preserved on back/forward
Section titled “What’s preserved on back/forward”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.
A guarded screen (say, an editor with unsaved changes) vetoes a back/forward that
would close it: its onPopBlocked runs and the address bar is put back — see
Guards & redirects.
Route → URL (links & sharing)
Section titled “Route → URL (links & sharing)”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=shoesIt’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 #.
Browser tab titles
Section titled “Browser tab titles”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}');Give raku(title: ...) a fallback for routes that declare none — otherwise
leaving a titled route for an untitled one keeps the old label:
raku(initial: const Home(), routes: [...], title: (_) => 'My App');It’s opt-in: with no title: anywhere and no fallback, 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.
URL matching rules
Section titled “URL matching rules”- Matching is case-sensitive —
/Notes/1doesn’t matchnotes/:id. - A trailing slash and doubled slashes are ignored —
/notes/1/=/notes/1. - The
#fragmentis ignored and isn’t round-tripped. - A repeated query key keeps its last value —
?q=a&q=breadsb. - Params are percent-decoded on the way in and re-encoded on the way out.
Transient URL state (no history spam)
Section titled “Transient URL state (no history spam)”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 keystrokeIt 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.