@real-router/browser-plugin
v0.20.14
Published
Browser integration plugin with History API, hash routing, and popstate support
Maintainers
Readme
@real-router/browser-plugin
Browser History API integration for Real-Router. Synchronizes router state with browser URL and handles back/forward navigation.
Installation
npm install @real-router/browser-pluginPeer dependency: @real-router/core
Quick Start
import { createRouter } from "@real-router/core";
import { browserPluginFactory } from "@real-router/browser-plugin";
const router = createRouter([
{ name: "home", path: "/" },
{ name: "users", path: "/users/:id" },
]);
router.usePlugin(browserPluginFactory());
await router.start(); // path inferred from browser locationOptions
router.usePlugin(
browserPluginFactory({
base: "/app", // Base path prefix for all routes
forceDeactivate: false, // Respect canDeactivate guards on back/forward (default)
}),
);| Option | Type | Default | Description |
| ----------------- | --------- | ------- | ---------------------------------------------------------------------- |
| base | string | "" | Base path for all routes (e.g., "/app" → URLs start with /app/...) |
| forceDeactivate | boolean | false | If true, browser back/forward skip canDeactivate guards. Default false respects guards — matches hash-plugin and navigation-plugin (#524/#1645) |
Hash routing? Use
@real-router/hash-plugininstead.
Router Extensions
The plugin extends the router instance with three methods via extendRouter():
| Method | Returns | Description |
| --------------------------------------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| buildUrl(name, params?, search?, options?: { hash? }) | string | Build full URL with base path. Query channel at position 3 (RFC-4 M2, #1548); options shift to 4. options.hash (decoded) is encoded and appended. |
| matchUrl(url) | State \| undefined | Parse URL to router state |
| replaceHistoryState(name, params?, search?, options?: { hash? }) | void | Update browser URL without triggering navigation. The URL and the history.state record are both built from the RESOLVED target, so they always describe the same state — a forwardTo route writes its destination, and a query key injected by persistent-params reaches both (#1585). Tri-state hash: undefined preserves, "" clears, value sets. |
router.buildUrl("users", { id: "123" });
// => "/app/users/123" (with base "/app")
router.matchUrl("/app/users/123");
// => { name: "users", params: { id: "123" }, path: "/users/123" }
// Update URL silently (no transition, no guards)
router.replaceHistoryState("users", { id: "456" });buildUrl vs buildPath
router.buildPath("users", { id: 1 }); // "/users/1" — core, no base
router.buildUrl("users", { id: 1 }); // "/app/users/1" — plugin, with basereplaceHistoryState vs navigate(..., { replace: true })
router.replaceHistoryState(name, params); // URL only, no transition
router.navigate(name, params, undefined, { replace: true }); // Full transition + URL updateURL Fragment ("hash") Support
browser-plugin ships first-class URL-fragment support: <Link hash> from any framework adapter, programmatic router.navigate(name, params, search?, { hash }) (options at position 4 since RFC-4 M2, #1548), and a state.context.url = { hash, hashChanged } namespace populated on every transition.
// Programmatic — tri-state opts.hash (options at position 4 since RFC-4 M2)
router.navigate("settings", {}, undefined, { hash: "profile" }); // set
router.navigate("settings", {}, undefined, { hash: "" }); // clear
router.navigate("settings"); // preserve current
// In subscribers
router.subscribe(({ route }) => {
console.log(route.context.url?.hash); // "profile"
console.log(route.context.url?.hashChanged); // true on hash-only nav
});See the Hash Fragment Support wiki page for the full surface (encoding, F5 priming, hash-aware active state).
Navigation Source (state.context.browser.source)
On every successful transition the plugin tags state.context.browser with the trigger origin — use this in subscribe handlers to distinguish back/forward from programmatic navigation:
router.subscribe(({ route }) => {
if (route.context.browser?.source === "popstate") {
// back/forward button — restore scroll, skip analytics "view" event, ...
} else {
// router.navigate()/router.start() — programmatic
}
});Both values are frozen singletons, so object-identity comparison is safe and zero-allocation.
| Value | Meaning |
| ------------ | ------------------------------------------------------------------------------ |
| "navigate" | Triggered by router.navigate(), router.start(), or replaceHistoryState() |
| "popstate" | Triggered by browser back/forward buttons (popstate event) |
Form Protection
Guards are respected on back/forward by default. Set forceDeactivate: true only to opt OUT — e.g. to avoid a dead end where the user cannot leave with the browser buttons:
router.usePlugin(browserPluginFactory()); // guards respected — the default
import { getLifecycleApi } from "@real-router/core/api";
const lifecycle = getLifecycleApi(router);
lifecycle.addDeactivateGuard(
"checkout",
(router, getDep) => (toState, fromState) => {
return !hasUnsavedChanges(); // false blocks back/forward
},
);SSR Support
The plugin is SSR-safe — createSafeBrowser detects the absence of window/history and swaps the History API calls (pushState, replaceState, addPopstateListener, getLocation, getHash) for warn-once no-ops. Pure URL helpers are environment-agnostic and behave identically on the server:
// Server-side — no crashes, History API calls become no-ops
router.usePlugin(browserPluginFactory({ base: "/app" }));
router.buildUrl("home"); // "/app/home" — base is still applied
router.matchUrl("/app/users/123"); // { name: "users", params: { id: "123" }, path: "/users/123" }
await router.start("/app/home"); // transitions normally; no popstate subscriptionThe first call to any History API method emits a one-time console warning so accidental SSR usage is visible without spamming logs.
Documentation
Full documentation: Wiki — browser-plugin
Related Packages
| Package | Description |
| -------------------------------------------------------------------------------------- | -------------------------------------- |
| @real-router/core | Core router (required peer dependency) |
| @real-router/hash-plugin | Hash-based routing (#/path) |
| @real-router/react | React integration |
| @real-router/logger-plugin | Development logging |
Contributing
See contributing guidelines for development setup and PR process.
