61. Framework Docs
Link SPA Router
Link is Braid's client-side SPA router (braid-lang/lib/frameworks/link/link.br, 336 lines). It provides route matching with :param patterns, route guards, lazy loading, nested routes, path validators, history stack integration, and a full navigation API.
Core Architecture
The LinkRouter struct holds the route configuration, current route state, extracted params, guards, transition hooks, lazy component cache, history stack reference, and path validators.
Router Setup
import link;
let router = link.create_router({
routes: [
{ path: "/", component: Home, guards: [], loader: nil,
lazy_load: nil, meta: {}, param_validators: {}, children: [] },
{ path: "/users", component: Users, guards: [], ... },
{ path: "/users/:id", component: UserProfile, ... }
],
history: window.history, // optional history adapter
fallback: { path: "/404", component: NotFound }
});Route Matching
router.navigate("/users/42");
// Internal matching (router.match_route(path)):
// - Exact match: "/users" == "/users"
// - Param match: "/users/:id" matches "/users/42"
// - Nested match: parent path + child path
// - Falls back to config.fallback
// Extracted params:
let params = router.get_current_params(); // { id: "42" }
let meta = router.get_current_route_meta();Path Matching Algorithm
fn path_matches(self, pattern, path) {
// 1. Exact string match
// 2. Split both into segments by "/"
// 3. Compare segment-by-segment
// 4. If pattern segment starts with ":", treat as param
// 5. Run param_validator if configured
// 6. All segments must match
}Route Factories
// Standard route
fn route_with_meta(path, component, meta) -> object
// Guarded route — requires passing guard function
fn guarded_route(path, component, guard_fn) -> object
// Route with param validators
fn route_with_validator(path, component, validators) -> object
// Lazy-loaded route
fn lazy_route(path, loader_fn, component_fn) -> objectRoute Guard Example
let auth_guard = fn(route, path) {
let is_authenticated = check_auth();
if !is_authenticated {
return "/login"; // redirect path
}
return nil; // allow navigation
};
let protected_route = link.guarded_route("/dashboard", Dashboard, auth_guard);
router.add_route(protected_route);
router.add_guard(auth_guard); // or add globallyLazy Route Example
let admin_route = link.lazy_route(
"/admin",
fn(params) { return fetch("/api/admin/data"); }, // loader
fn() { return AdminPanel; } // lazy component resolver
);
router.add_route(admin_route);
fn is_lazy_route(route) -> bool // check if route is lazy-loadedNavigation API
// Navigate to a path
fn link_push(router, path)
// Replace current history entry
fn link_replace(router, path)
// Navigate back
fn link_back(router)
// Router method
router.navigate(path)
// Generate an anchor element with SPA navigation
let spa_link = router.link_to(path, "Click me", { class: "nav-link" });
// Returns: <a href="..." onClick={preventDefault + navigate}>Click me</a>Route Guards (beforeEnter / beforeLeave)
router.add_before_enter_hook(fn(route) {
track_analytics(route);
});
router.add_before_leave_hook(fn(current_route) {
save_form_draft(current_route);
});
// Guards can return a redirect path (string) to cancel and redirect,
// or nil to allow navigation through.
// Guards are checked in order: global beforeLeave -> guards -> beforeEnterNested Routes
let parent_route = {
path: "/settings",
component: SettingsLayout,
children: [
{ path: "/profile", component: Profile },
{ path: "/security", component: Security }
]
};
// Matches: /settings/profile, /settings/security
// Router walks children and concatenates parent + child pathsPath Validators
let route = link.route_with_validator("/users/:id", UserProfile, {
id: fn(value) {
let id = number(value);
return id > 0 && id <= 99999; // only valid IDs
}
});History Stack Integration
// When history adapter is provided:
// - navigate() calls history_stack.push_state(path, { route: path })
// - link_replace() calls history_stack.replace_state(path, {})
// - link_back() calls history_stack.back()
// - Router detects URL changes (popstate) and re-runs matchingRoute Data Loading
// When route.loader is defined, the router calls it before rendering
// and injects result into route.component.props.__route_data
route.loader = fn(params) {
return fetch("/api/users/" + params.id);
};
// After navigation:
component.props.__route_data // contains loaded dataSPA Navigation Example
import bond;
import link;
let router = link.create_router({
routes: [
link.route_with_meta("/", Home, { title: "Home" }),
link.route_with_meta("/about", About, { title: "About" }),
link.route_with_meta("/contact", Contact, { title: "Contact" })
],
history: window.history,
fallback: link.route_with_meta("/404", NotFound, { title: "Not Found" })
});
fn NavBar() {
return bond.nav({ class: "navbar" }, [
router.link_to("/", "Home"),
router.link_to("/about", "About"),
router.link_to("/contact", "Contact")
]);
}
fn App() {
return bond.div({}, [
bond.createComponent(NavBar, {}),
bond.createComponent(router.current_route.component, {
__route_data: router.current_route.loader != nil
? router.current_route.loader(router.params)
: nil
})
]);
}Auth Guard Example
import bond;
import link;
let session = { is_authenticated: false };
fn require_auth(route, path) {
if !session.is_authenticated {
return "/login?redirect=" + path;
}
return nil;
}
let router = link.create_router({
routes: [
link.route_with_meta("/login", LoginPage, { title: "Login" }),
link.guarded_route("/dashboard", Dashboard, require_auth),
link.guarded_route("/admin", AdminPanel, require_auth),
],
history: window.history
});
fn LogoutButton() {
return bond.button("Logout", {
onClick: fn() {
session.is_authenticated = false;
link.link_push(router, "/login");
}
});
}Low-Level Helpers
// Split a path into segments
fn split_path(self, path) -> list // "/users/42" -> ["users", "42"]
// Extract param values from matched path
fn extract_params(self, pattern, path) -> object
// Validate a single param value against registered validator
fn validate_param(self, param_name, value) -> bool
// Check if path matches pattern (including param segments)
fn path_matches(self, pattern, path) -> bool
// Add a route after construction
router.add_route(route)
// Add a global guard
router.add_guard(guard_fn)