Docs / Core / Router
ven.js
Router
A tiny signal-driven client router. In real projects each page lives in its own component file, and the router itself is kept separate.
Recommended file structure
Developers usually keep one page per file and import them into a central router:
venjs/
components/
router.js # router definition + nav links
home.js # HomePage component
about.js # AboutPage component
contact.js # ContactPage component
login.js # LoginPage component
logic/
app.js # entry point: mounts the router
venjs.createRouter(routes, options)
const router = venjs.createRouter(routes, options);
| Option | Default | Description |
|---|---|---|
mode | "hash" | "hash" uses #/path; "history" uses the History API and pushState. |
base | "" | Subfolder base path (e.g. "/venjs"). Stripped/added around the route path. |
notFound | undefined | VNode or factory used when no route and no "*" match. |
routes
A map of path → VNode factory. The special key "*" is the catch-all/fallback.
Returned router object
| Member | Description |
|---|---|
path | A signal holding the current (base-stripped) path, e.g. "/home". |
navigate(to) | Navigates to a path, updating the URL and the path signal. |
resolve() | Resolves the current route to a VNode (passing { path, navigate } to factories). |
view() | Convenience: returns resolve() for embedding in a component. |
destroy() | Removes the URL listeners. |
Example: multi-file setup
Each page component is defined in its own file and exported as a named function. The router imports them and maps paths to factories.
components/home.js
const HomePage = () => venjs.div({ class: "page" }, [
venjs.h1({ class: "page-title" }, "Welcome"),
venjs.p({ class: "page-copy" }, "This is the home page.")
]);
window.HomePage = HomePage;
components/about.js
const AboutPage = () => venjs.div({ class: "page" }, [
venjs.h1({ class: "page-title" }, "About Us"),
venjs.p({ class: "page-copy" }, "We build clean VenJS apps.")
]);
window.AboutPage = AboutPage;
components/contact.js
const ContactPage = () => venjs.div({ class: "page" }, [
venjs.h1({ class: "page-title" }, "Contact"),
venjs.p({ class: "page-copy" }, "Reach us at hello@example.com.")
]);
window.ContactPage = ContactPage;
components/router.js
const router = venjs.createRouter({
"/home": () => window.HomePage(),
"/about": () => window.AboutPage(),
"/contact": () => window.ContactPage(),
"/login": () => window.LoginPage(),
"*": () => venjs.div({}, ["Page not found"])
}, { mode: "history", base: "/venjs" });
window.router = router;
logic/app.js
const app = document.getElementById("app");
const NavLink = (label, route) => venjs.button({
class: window.router.path.value === route ? "nav-btn active" : "nav-btn",
onclick: () => window.router.navigate(route)
}, label);
const App = () => venjs.div({ class: "app" }, [
venjs.nav({ class: "top-nav" }, [
NavLink("Home", "/home"),
NavLink("About", "/about"),
NavLink("Contact", "/contact"),
NavLink("Login", "/login")
]),
window.router.view()
]);
venjs.render(app, App);
if (window.router.path.value === "/") {
window.router.navigate("/home");
}
Route factory context
Factories receive a context object so a single factory can serve parameterized routes:
"/product": ({ path, navigate }) => ProductPage(path)
History mode + static hosting: With
mode:"history" you must serve index.html for unknown paths (the included .htaccess does this for Apache). Hash mode needs no server config.Path normalization rules
"","/","#","#/"all normalize to"/".- Leading
#is stripped; paths without a leading/get one. - The
baseis added on navigation and stripped when reading the path.