@real-router/hash-plugin
v0.15.0
Published
Hash-based routing plugin for Real-Router
Maintainers
Readme
@real-router/hash-plugin
Hash-based routing plugin for Real-Router. Uses URL hash fragment (
#/path) for navigation — no server configuration needed.
Works on static hosting (GitHub Pages, S3, Netlify) without redirect rules. Tradeoff: URLs include # (example.com/#!/users vs example.com/users).
Looking for clean URLs? Use
@real-router/browser-plugin(History API).
Installation
npm install @real-router/hash-pluginPeer dependency: @real-router/core
Quick Start
import { createRouter } from "@real-router/core";
import { hashPluginFactory } from "@real-router/hash-plugin";
const router = createRouter([
{ name: "home", path: "/" },
{ name: "users", path: "/users/:id" },
]);
router.usePlugin(hashPluginFactory());
await router.start(); // reads hash from browser locationOptions
| Option | Type | Default | Description |
| ----------------- | --------- | ------- | ----------------------------------------------------- |
| hashPrefix | string | "" | Prefix after # (e.g., "!" → #!/path) |
| base | string | "" | Base path before hash (e.g., "/app" → /app#/path) |
| forceDeactivate | boolean | false | If true, back/forward skip canDeactivate guards. Default false respects guards (#524/#1645) |
router.usePlugin(hashPluginFactory({ hashPrefix: "!", base: "/app" }));
router.navigate("users", { id: "123" });
// URL: /app#!/users/123Router Extensions
The plugin extends the router instance with three methods via extendRouter():
| Method | Returns | Description |
| -------------------------------------------- | -------------------- | ------------------------------------- |
| buildUrl(name, params?, search?) | string | Build full URL with hash and prefix |
| matchUrl(url) | State \| undefined | Parse hash URL to router state |
| replaceHistoryState(name, params?, search?) | void | Update browser URL without 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). |
router.buildUrl("users", { id: "123" });
// => "#!/users/123" (with hashPrefix "!")
router.matchUrl("https://example.com/#!/users/123");
// => { name: "users", params: { id: "123" }, search: {}, 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 hash
router.buildUrl("users", { id: 1 }); // "#!/users/1" — plugin, with hash prefixForm Protection
Guards are respected on back/forward by default. Set forceDeactivate: true only to opt OUT:
router.usePlugin(hashPluginFactory()); // 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
},
);Limitations
URL Fragments via <Link hash> / opts.hash
Hash-plugin uses # as the route delimiter — URL fragments are structurally incompatible. The signatures of router.buildUrl(name, params, search, options?) and router.replaceHistoryState(name, params, search, options?) accept { hash } for typing parity with the other URL plugins (options at position 4 since RFC-4 M2, #1548), but at runtime the option is silently ignored and a one-time console.warn is emitted on the first invocation:
[@real-router/hash-plugin] `hash` option is ignored — `#` is reserved for the route delimiter.
Use browser-plugin or navigation-plugin for URL fragments.state.context.url is not populated under hash-plugin (undefined at runtime). Hash-aware sources (useIsActiveRoute, <Link hash> active state) consequently return false for any non-undefined hash. Only one URL plugin (browser-plugin, navigation-plugin, or hash-plugin) may be installed per router instance.
If you need URL fragments for tab-style UIs or anchor scrolling, use @real-router/browser-plugin or @real-router/navigation-plugin instead — see the Hash Fragment Support wiki page.
SSR Support
SSR-safe — automatically detects the environment and falls back to no-ops:
router.usePlugin(hashPluginFactory());
router.buildUrl("home"); // returns hash path
router.matchUrl("/path"); // returns undefinedAdvanced Exports
| Export | Description |
|--------|-------------|
| Browser | Browser interface type (from browser-env) — for custom browser implementations |
| isState | Type guard for a RESTORABLE history.state entry — (value: unknown) => value is RestorableEntry. Narrowed from State in #1838: it validates the four channels an entry may carry, not all six members of State. Screens params AND search by value (#1837), and answers false rather than throwing when the entry's own accessors throw. |
Documentation
Full documentation: Wiki — hash-plugin
Related Packages
| Package | Description | | ---------------------------------------------------------------------------------------- | -------------------------------------- | | @real-router/core | Core router (required peer dependency) | | @real-router/browser-plugin | History API routing (clean URLs) | | @real-router/react | React integration |
Contributing
See contributing guidelines for development setup and PR process.
