<Route/>

Renders children when location.path matches its path. It doesn't need a wrapper, and routes can be nested — a nested route's path is relative to its parent. :name segments in the path are captured and exposed via location.params. <Route.Default/> renders a default / 404 when no sibling route matches.

Pair with <A> for route-aware links and load to lazy-load a route's children.

Attributes

name type description
path? string when the location matches this path, the route's children render. When omitted, the route matches only when the parent route's path matches the location exactly (the final / index route).
params? Record<string, string> replaces :name segments in path with their URI-encoded values. <Route path="/some/:cat/:page" params={{ cat: 'variété', page: 'touché' }}/> becomes /some/vari%C3%A9t%C3%A9/touch%C3%A9.
when? When<any> optional condition to stop rendering even when the path matches.
fallback? JSX.Element rendered while the route is not shown — when the path doesn't match, or a when condition is falsy.
collapse? When<any> hide the route instead of unmounting it. Keeps state for iframes, canvas, video, audio, etc.
scroll? string | string[] selector(s) to scroll into view when the route matches — the URL hash wins first, then these selectors, then the top of the page.
children? JSX.Element what to render when the route matches.

Nested routes and params

Child <Route>s match relative to their parent's path, so paths compose by declaration. :name segments are captured into the route's location context; location.params is a reactive map whose keys update as the URL changes. Capture it once at component setup (const params = location.params) and read keys reactively.

Examples

Client-side routing

<Route path="..."> renders its children when the location matches — declare a route per page and let pota mount the matching one. <A> links drive navigation without a full page load.

import { render } from 'pota'
import { A, Route } from 'pota/components'

function App() {
	return (
		<div>
			<nav>
				<A href="/">home</A>
				{' · '}
				<A href="/about">about</A>
				{' · '}
				<A href="/users/42">user 42</A>
			</nav>

			<Route path="/">
				<h2>welcome</h2>
			</Route>
			<Route path="/about">
				<h2>about us</h2>
			</Route>
			<Route path="/users/:id">
				<h2>user profile</h2>
				<p>(open the URL — :id is captured)</p>
			</Route>
		</div>
	)
}

render(App)

Nested routes and params

Child routes match relative to their parent, and :name segments are captured into the reactive location.params map. Capture location.params once, then read keys with a {() => …} wrapper so the JSX updates as the URL changes.

import { render } from 'pota'
import { A, Route } from 'pota/components'
import { location } from 'pota/use/location'

function UserDetail() {
	const params = location.params
	return (
		<div>
			<h2>user #{() => params.id}</h2>
			<p>open a different id to see this update</p>
		</div>
	)
}

function Post() {
	const params = location.params
	return <p>post: {() => params.slug}</p>
}

function App() {
	return (
		<div>
			<nav>
				<A href="/users/1">user 1</A>
				{' · '}
				<A href="/users/2">user 2</A>
				{' · '}
				<A href="/users/42/posts/intro">42 / posts / intro</A>
			</nav>

			<Route path="/users">
				<Route path=":id">
					<UserDetail />
					<Route path="posts/:slug">
						<Post />
					</Route>
				</Route>
			</Route>
		</div>
	)
}

render(App)

Relative links and the index route

A <Route> without a path is the index route — it renders only when the parent path matches exactly. <A> links inside a route resolve relative to that route, so uno/ from within / navigates to /uno/. <Route.Default/> renders when no sibling route matched.

import { render } from 'pota'
import { A, Route } from 'pota/components'

function App() {
	return (
		<main>
			<A href="/">Index</A>
			<Route path="/">
				<section>
					relative links: <A href="uno/">uno/</A> -{' '}
					<A href="dos/">dos/</A>
				</section>

				<Route>
					<h2>landing — matched / exactly</h2>
				</Route>
				<Route path="uno/">
					<h2>this is uno</h2>
					<Route.Default>nothing under uno/</Route.Default>
				</Route>
				<Route path="dos/">
					<h2>this is dos</h2>
				</Route>
			</Route>
		</main>
	)
}

render(App)

Collapse to preserve state

collapse hides a route instead of unmounting it, avoiding a re-render — useful for iframes, canvas, video, or audio that would lose state on remount. The frame keeps playing as you navigate away and back.

import { render } from 'pota'
import { A, Route } from 'pota/components'

function App() {
	return (
		<main>
			<ul>
				<li>
					<A href="/#kilo/frame">frame</A>
				</li>
				<li>
					<A href="/#kilo/no-frame">no frame</A>
				</li>
			</ul>
			<Route path="/#">
				<Route path="kilo/">
					<Route
						path="frame"
						collapse={true}
					>
						<h3>frame stays alive</h3>
						<iframe
							width="560"
							height="315"
							src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ"
							title="frame"
							allowfullscreen
						/>
					</Route>
					<Route path="no-frame">
						<h3>no frame</h3>
					</Route>
				</Route>
			</Route>
		</main>
	)
}

render(App)