Defining Routes
In most web applications, you want to show different pages when the user visits different URLs (like /home or /about). In Retend, we do this using a Router.
The router keeps track of the current URL and decides which components to show on the screen. Retend includes its own router built-in, so you don't need to install any extra libraries!
Setting Up Routes
To set up your application's routing, you define an array of Route Records. Each record simply connects a URL path to a component.
We use the defineRoutes helper function to create this array:
import { Router, defineRoutes, createRouterRoot } from 'retend/router'; import { renderToDOM } from 'retend-web'; // 1. Create your page components const Home = () => <div>Welcome to the Home Page</div>; const About = () => <div>About Us</div>; // 2. Define your routes const routes = defineRoutes([ { path: '/', component: Home }, { path: '/about', component: About }, ]); // 3. Initialize the Router const router = new Router({ routes }); const root = document.getElementById('app')!; // 4. Render the router to the screen renderToDOM(root, () => createRouterRoot(router));
The createRouterRoot function takes your router and turns it into a component that can be rendered.
Nested Routes and Layouts
Most applications share common layout elements across different pages—like a header at the top or a navigation sidebar on the left.
Instead of copying and pasting the header into every single page component, you can use Nested Routes. You define a parent route that acts as the layout, and use the special <Outlet /> component to mark where the child pages should be rendered.
import { defineRoutes, Outlet } from 'retend/router'; function DashboardLayout() { return ( <div class="dashboard-layout"> <aside>Sidebar Navigation</aside> <main> {/* The child page content will appear here */} <Outlet /> </main> </div> ); } const Overview = () => <h2>Dashboard Overview</h2>; const Settings = () => <h2>User Settings</h2>; const routes = defineRoutes([ { path: '/dashboard', component: DashboardLayout, children: [ { path: 'overview', component: Overview }, { path: 'settings', component: Settings }, ], }, ]);
Now, when a user visits /dashboard/overview, the DashboardLayout appears on the screen, and the Overview component is placed right where the <Outlet /> is. When they navigate to /dashboard/settings, the sidebar stays exactly where it is, and only the <Outlet /> part changes.
Default Child Routes
If a user visits exactly /dashboard, the <Outlet /> would be empty because no child route was specified. You can create a default child route by using an empty string ('') as the path:
const routes = defineRoutes([ { path: '/products', component: ProductsLayout, children: [ { path: '', component: ProductList }, // This loads by default at /products { path: 'categories', component: CategoriesList }, ], }, ]);
Dynamic URLs
Sometimes URLs need to contain variable data, like a specific user's ID or a blog post's title. You can create a dynamic route by adding a colon (:) before a segment in the path:
const routes = defineRoutes([ // This matches /users/123, /users/alice, etc. { path: '/users/:userId', component: UserProfile }, // You can even have multiple dynamic segments { path: '/posts/:category/:postId', component: BlogPost }, ]);
We will cover how to actually access those variables (like the userId) from inside your components in the next few sections.
Handling "404 Not Found"
If a user visits a URL that doesn't exist in your routes, you probably want to show a custom "Not Found" page. You can use an asterisk (*) to create a route that matches absolutely anything.
Because the router checks routes in order from top to bottom, always put your "catch-all" route at the very end:
const routes = defineRoutes([ { path: '/', component: Home }, { path: '/about', component: About }, // If nothing above matched, this will run { path: '*', component: NotFoundPage }, ]);
With your routes defined, your application now knows exactly what to show for any URL.