Preact Routing — Client-Side Navigation with preact-router
Learn how to implement client-side routing in Preact using preact-router for single-page application navigation with minimal configuration.
In this lesson, you'll understand preact-router, route matching, link components, and how to structure multi-page Preact applications.
What You'll Learn
How to install preact-router, define routes, navigate with Link components, handle dynamic route parameters, and create nested layouts.
Why It Matters
Client-side routing enables single-page applications where navigation doesn't reload the page. Users get faster transitions and smoother experiences.
Real-World Use
DodaZIP's settings panel uses preact-router to navigate between General, Security, and Compression settings tabs without page reloads, preserving the extraction state in the background.
flowchart TD
A[App Shell] --> B[/ Home]
A --> C[/about About]
A --> D[/settings Settings]
A --> E[/users/:id User Profile]
B --> F[Route Matcher]
C --> F
D --> F
E --> F
style A fill:#673ab8,color:#fff
style F fill:#4a148c,color:#fff
Installing preact-router
npm install preact-router
preact-router is a lightweight routing library built specifically for Preact, with no React dependencies.
Basic Routing
import { Router } from 'preact-router';
import { h, render } from 'preact';
import Home from './routes/Home';
import About from './routes/About';
import Contact from './routes/Contact';
function App() {
return (
<div>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
<Router>
<Home path="/" />
<About path="/about" />
<Contact path="/contact" />
<div default>404 — Page not found</div>
</Router>
</div>
);
}
Output: Navigating to /about renders the About component without a page reload. The path prop on each component defines the URL it matches. The default prop creates a fallback for unmatched routes.
Link Component
Use Link for accessible, state-preserving navigation:
import { Router, Link } from 'preact-router';
function Nav() {
return (
<nav>
<Link href="/">Home</Link>
<Link href="/about">About</Link>
<Link href="/contact">Contact</Link>
<Link href="/users/42">User 42</Link>
</nav>
);
}
function App() {
return (
<div>
<Nav />
<Router>
<Home path="/" />
<About path="/about" />
<Contact path="/contact" />
<User path="/users/:id" />
</Router>
</div>
);
}
Output: <Link> renders an <a> tag but intercepts clicks to prevent full page reloads. It updates the URL and renders the matching route component.
Dynamic Route Parameters
Access URL parameters in route components:
import { Router } from 'preact-router';
function User({ id }) {
// The `id` prop comes from the :id segment in the path
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${id}`)
.then(res => res.json())
.then(setUser);
}, [id]);
if (!user) return <p>Loading user {id}...</p>;
return (
<div>
<h2>{user.name}</h2>
<p>Email: {user.email}</p>
</div>
);
}
function App() {
return (
<Router>
<User path="/users/:id" />
<Home path="/" />
</Router>
);
}
Output: Navigating to /users/42 passes id="42" as a prop to the User component. The component fetches the user data and displays it.
Programmatic Navigation
Navigate imperatively with the router instance:
import { route } from 'preact-router';
function LoginForm() {
const handleSubmit = async (e) => {
e.preventDefault();
const success = await login(/* ... */);
if (success) {
// Navigate to dashboard after login
route('/dashboard');
}
};
return <form onSubmit={handleSubmit}>/* ... */</form>;
}
The route() function navigates to a new URL without a full page reload. It accepts a path and an optional second parameter for state or replace behavior.
Nested Routes
preact-router supports nested routers for complex layouts:
import { Router } from 'preact-router';
function Settings() {
return (
<div style={{ display: 'flex' }}>
<aside>
<Link href="/settings/general">General</Link>
<Link href="/settings/security">Security</Link>
<Link href="/settings/notifications">Notifications</Link>
</aside>
<main>
<Router>
<General path="/settings/general" />
<Security path="/settings/security" />
<Notifications path="/settings/notifications" />
<div default>Select a setting</div>
</Router>
</main>
</div>
);
}
function App() {
return (
<Router>
<Home path="/" />
<Settings path="/settings" />
<Settings path="/settings/:page" />
</Router>
);
}
Output: Navigating to /settings/security renders the Settings layout with the Security sub-route. The nested router handles the second URL segment.
Route Matching and Order
preact-router matches routes in order. More specific routes should come first:
<Router>
<Home path="/" />
<UserList path="/users" />
<User path="/users/:id" />
<UserPosts path="/users/:id/posts" />
<NotFound default />
</Router>
Output: /users matches UserList. /users/42 matches User. /users/42/posts matches UserPosts. The default route catches everything else.
Common Mistakes
- Forgetting to import
Linkfrom preact-router: Using regular<a>tags causes full page reloads, losing application state. - Using wrong path syntax: Paths use colon-prefixed segments for params (
:id), not React Router's:id?or*syntax. - Placing routes in wrong order: preact-router returns the first match. Put more specific routes before less specific ones.
- Not handling the
defaultroute: Without a default, unmatched URLs render nothing, leaving users with a blank page. - Calling
route()on the server: preact-router uses the browser history API. Guardroute()calls withtypeof window !== 'undefined'during SSR.
Practice Questions
How do you define a route with a dynamic parameter? Answer: Use colon syntax in the path prop:
<User path="/users/:id" />. The parameter value is passed as a prop to the component.What does
Linkdo differently from a regular<a>tag? Answer:Linkintercepts clicks and uses the history API to navigate without a full page reload. It also ensures the router re-renders the correct component.How do you create a 404 fallback route? Answer: Add a component with the
defaultprop instead of apathprop. It matches any URL that doesn't match other routes.What function navigates programmatically? Answer:
route(path)frompreact-router. It navigates without page reload and preserves application state.
Challenge
Build a blog with preact-router that has routes for: home (/), post list (/posts), individual post (/posts/:slug), and author page (/authors/:id). Use nested routes for the blog section.
Mini Project
Create a multi-tab documentation viewer with preact-router. Each tab (Installation, Usage, API, Examples) is a route. Use nested routing for API sub-sections.
FAQ
What's Next
Learn about Preact Forms to handle form input, validation, and submission in Preact applications.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro