Skip to content

Adding Routing

Package on GitHub

The router tracks the URL and matches it against your route definitions.


Start with just route definitions and a router:

import { compose } from '@rimitive/core';
import { SignalModule, ComputedModule } from '@rimitive/signals/extend';
import { createRouterModule } from '@rimitive/router';
// Define routes - pure data
const routes = [
{ id: 'home', path: '' },
{ id: 'about', path: 'about' },
];
// Compose with router
const svc = compose(
SignalModule,
ComputedModule,
// Add this 👇
createRouterModule(routes)
);
const { router } = svc;

The router gives you reactive signals:

router.currentPath(); // '/' or '/about'
router.matches(); // [{ id: 'home', pattern: '/', params: {}, path: '/' }]

Navigate programmatically:

router.navigate('/about');
router.currentPath(); // '/about'
router.matches(); // [{ id: 'about', pattern: '/about', params: {}, path: '/about' }]

Use match() to render based on the matched route:

// Page components
const Home = ({ el }: Service) => () => el('div')(
el('h1')('Home'),
el('p')('Welcome!')
);
const About = ({ el }: Service) => () => el('div')(
el('h1')('About'),
el('p')('Learn more about us.')
);
const NotFound = ({ el }: Service) => () => el('div')(
el('h1')('404'),
el('p')('Page not found.')
);
// App with routing
const App = (svc: Service) => () => {
const { el, match, router } = svc;
const HomePage = svc(Home);
const AboutPage = svc(About);
const NotFoundPage = svc(NotFound);
const pages: Record<string, () => ReturnType<typeof el>> = {
home: HomePage,
about: AboutPage,
};
return el('div')(
match(router.matches, (matches) => {
const route = matches[0];
if (!route) return NotFoundPage();
const Page = pages[route.id];
return Page ? Page() : NotFoundPage();
})
);
};
const AppComponent = svc(App);
const app = mount(AppComponent());
document.body.appendChild(app.element!);

When router.matches changes, match() swaps the rendered component.


Use Link for declarative navigation that works with the router:

import { createLinkModule } from '@rimitive/router/link';
// Add to service composition
const svc = compose(
// ... other modules
createLinkModule()
);
const Nav = ({ el, Link }: Service) => () => el('nav')(
Link({ href: '/' })('Home'),
Link({ href: '/about' })('About')
);
const App = (svc: Service) => () => {
const { el, match, router } = svc;
const NavComponent = svc(Nav);
const NotFoundPage = svc(NotFound);
// ... page setup from earlier
return el('div')(
NavComponent(),
el('main')(
match(router.matches, (matches) => {
const route = matches[0];
if (!route) return NotFoundPage();
const Page = pages[route.id];
return Page ? Page() : NotFoundPage();
})
)
);
};

Link renders an <a> tag that intercepts clicks and uses router.navigate().

For programmatic navigation:

const Home = ({ el, router }: Service) => () => el('div')(
el('h1')('Home'),
el('button').props({
onclick: () => router.navigate('/about')
})('Go to About')
);

Capture dynamic segments with :param syntax:

const routes = [
{ id: 'home', path: '' },
{ id: 'products', path: 'products' },
{ id: 'product-detail', path: 'products/:id' },
];

When the URL is /products/123, the match includes the parameter:

router.navigate('/products/123');
router.matches();
// [{ id: 'product-detail', pattern: '/products/:id', params: { id: '123' }, path: '/products/123' }]

Use the params in your component:

const ProductDetail = ({ el }: Service) => (params: { id: string }) => el('div')(
el('h1')(`Product ${params.id}`),
el('p')('Product details here...')
);
// In the App component's router match
const ProductDetailPage = svc(ProductDetail);
match(router.matches, (matches) => {
const route = matches[0];
if (!route) return NotFoundPage();
if (route.id === 'product-detail') {
return ProductDetailPage(route.params as { id: string });
}
const Page = pages[route.id];
return Page ? Page() : NotFoundPage();
});

The router parses query strings into reactive signals:

// URL: /products?sort=price&category=electronics
router.search(); // '?sort=price&category=electronics'
router.query(); // { sort: 'price', category: 'electronics' }

React to query changes:

const Products = ({ el, computed, router }: Service) => () => {
const sortOrder = computed(() => router.query().sort || 'name');
return el('div')(
el('h1')('Products'),
el('p')(computed(() => `Sorted by: ${sortOrder()}`)),
el('button').props({
onclick: () => router.navigate('/products?sort=price')
})('Sort by Price')
);
};

Use router.currentPath to style the active link:

const NavLink = ({ computed, router, Link }: Service) => (href: string, label: string) => {
const isActive = computed(() => router.currentPath() === href);
return Link({
href,
className: computed(() => isActive() ? 'nav-link active' : 'nav-link')
})(label);
};
const Nav = (svc: Service) => () => {
const { el } = svc;
const navLink = svc(NavLink);
return el('nav')(
navLink('/', 'Home'),
navLink('/about', 'About'),
navLink('/products', 'Products')
);
};