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);
OptionDefaultDescription
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.
notFoundundefinedVNode 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

MemberDescription
pathA 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 base is added on navigation and stripped when reading the path.