Universal router for SolidJS
npm install @solidjs/router





Solid Router brings fine-grained reactivity to route navigation, enabling your single-page application to become multi-paged without full page reloads. Fully integrated into the SolidJS ecosystem, Solid Router provides declarative syntax with features like universal rendering and parallel data fetching for best performance.
Explore the official documentation for detailed guides and examples.
- All Routing Modes:
- History-Based for standard browser navigation
- Hash-Based for navigation based on URL hash
- Static Routing for server-side rendering (_SSR_)
- Memory-Based for testing in non-browser environments
- TypeScript: Full integration for robust, type-safe development
- Universal Rendering: Seamless rendering on both client and server environments
- Declarative: Define routes as components or as an object
- Preload Functions: Parallel data fetching, following the render-as-you-fetch pattern
- Dynamic Route Parameters: Flexible URL patterns with parameters, optional segments, and wildcards
- Data APIs with Caching: Reactive data fetching with deduplication and revalidation
- Getting Started
- Set Up the Router
- Configure Your Routes
- Create Links to Your Routes
- Dynamic Routes
- Nested Routes
- Hash Mode Router
- Memory Mode Router
- Data APIs
- Config Based Routing
- Components
- Router Primitives
- useParams
- useNavigate
- useLocation
- useSearchParams
- useIsRouting
- useMatch
- useCurrentMatches
- useBeforeLeave
- SPAs in Deployed Environments
``bash`use preferred package manager
npm add @solidjs/router
Install @solidjs/router, then start your application by rendering the router component
`jsx
import { render } from "solid-js/web";
import { Router } from "@solidjs/router";
render(() =>
`
This sets up a Router that will match on the url to display the desired page
Solid Router allows you to configure your routes using JSX:
1. Add each route to a using the Route component, specifying a path and a component to render when the user navigates to that path.
`jsx
import { render } from "solid-js/web";
import { Router, Route } from "@solidjs/router";
import Home from "./pages/Home";
import Users from "./pages/Users";
render(
() => (
),
document.getElementById("app")
);
`
2. Provide a root level layout
This will always be there and won't update on page change. It is the ideal place to put top level navigation and Context Providers
`jsx
import { render } from "solid-js/web";
import { Router, Route } from "@solidjs/router";
import Home from "./pages/Home";
import Users from "./pages/Users";
const App = (props) => (
<>
render(
() => (
),
document.getElementById("app")
);
`
3. Create a catch-all route (404 page)
We can create catch-all routes for pages not found at any nested level of the router. We use * and optionally the name of a parameter to retrieve the rest of the path.
`jsx
import { render } from "solid-js/web";
import { Router, Route } from "@solidjs/router";
import Home from "./pages/Home";
import Users from "./pages/Users";
import NotFound from "./pages/404";
const App = (props) => (
<>
render(
() => (
),
document.getElementById("app")
);
`
4. Lazy-load route components
This way, the Users and Home components will only be loaded if you're navigating to /users or /, respectively.
`jsx
import { lazy } from "solid-js";
import { render } from "solid-js/web";
import { Router, Route } from "@solidjs/router";
const Users = lazy(() => import("./pages/Users"));
const Home = lazy(() => import("./pages/Home"));
const App = (props) => (
<>
render(
() => (
),
document.getElementById("app")
);
`
Use an anchor tag that takes you to a route:
`jsx
import { lazy } from "solid-js";
import { render } from "solid-js/web";
import { Router, Route } from "@solidjs/router";
const Users = lazy(() => import("./pages/Users"));
const Home = lazy(() => import("./pages/Home"));
const App = (props) => (
<>
render(
() => (
),
document.getElementById("app")
);
`
If you don't know the path ahead of time, you might want to treat part of the path as a flexible parameter that is passed on to the component.
`jsx
import { lazy } from "solid-js";
import { render } from "solid-js/web";
import { Router, Route } from "@solidjs/router";
const Users = lazy(() => import("./pages/Users"));
const User = lazy(() => import("./pages/User"));
const Home = lazy(() => import("./pages/Home"));
render(
() => (
),
document.getElementById("app")
);
`
The colon indicates that id can be any string, and as long as the URL fits that pattern, the User component will show.
You can then access that id from within a route component with useParams.
Note on Animation/Transitions:
Routes that share the same path match will be treated as the same route. If you want to force re-render you can wrap your component in a keyed like:
`jsx`
---
Each path parameter can be validated using a MatchFilter.
This allows for more complex routing descriptions than just checking the presence of a parameter.
`jsx
import { lazy } from "solid-js";
import { render } from "solid-js/web";
import { Router, Route } from "@solidjs/router";
import type { MatchFilters } from "@solidjs/router";
const User = lazy(() => import("./pages/User"));
const filters: MatchFilters = {
parent: ["mom", "dad"], // allow enum values
id: /^\d+$/, // only allow numbers
withHtmlExtension: (v: string) => v.length > 5 && v.endsWith(".html"), // we want an *.html extension
};
render(
() => (
component={User}
matchFilters={filters}
/>
),
document.getElementById("app")
);
`
Here, we have added the matchFilters prop. This allows us to validate the parent, id and withHtmlExtension parameters against the filters defined in filters.
If the validation fails, the route will not match.
So in this example:
- /users/mom/123/contact.html would match,/users/dad/123/about.html
- would match,/users/aunt/123/contact.html
- would not match as :parent is not 'mom' or 'dad',/users/mom/me/contact.html
- would not match as :id is not a number,/users/dad/123/contact
- would not match as :withHtmlExtension is missing .html.
---
Parameters can be specified as optional by adding a question mark to the end of the parameter name:
`jsx`
// Matches stories and stories/123 but not stories/123/comments
:param lets you match an arbitrary name at that point in the path. You can use * to match any end of the path:
`jsx`
// Matches any path that begins with foo, including foo/, foo/a/, foo/a/b/c
If you want to expose the wild part of the path to the component as a parameter, you can name it:
`jsx`
Note that the wildcard token must be the last part of the path; foo/*any/bar won't create any routes.
Routes also support defining multiple paths using an array. This allows a route to remain mounted and not rerender when switching between two or more locations that it matches:
`jsx`
// Navigating from login to register does not cause the Login component to re-render
The following two route definitions have the same result:
`jsx`
`jsx`
/users/:id renders the component, and /users/ is an empty route.
Only leaf Route nodes (innermost Route components) are given a route. If you want to make the parent its own route, you have to specify it separately:
`jsx
//This won't work the way you'd expect
// This works
// This also works
`
You can also take advantage of nesting by using props.children passed to the route component.
`jsx
function PageWrapper(props) {
return (
);
}
`
The routes are still configured the same, but now the route elements will appear inside the parent element where the props.children was declared.
You can nest indefinitely - just remember that only leaf nodes will become their own routes. In this example, the only route created is /layer1/layer2, and it appears as three nested divs.
`jsx`
component={(props) => Onion starts here {props.children}}
>
component={(props) => Another layer {props.children}}
>
Innermost layer} />
Even with smart caches it is possible that we have waterfalls both with view logic and with lazy loaded code. With preload functions, we can instead start fetching the data parallel to loading the route, so we can use the data as soon as possible. The preload function is called when the Route is loaded or eagerly when links are hovered.
As its only argument, the preload function is passed an object that you can use to access route information:
`js
import { lazy } from "solid-js";
import { Route } from "@solidjs/router";
const User = lazy(() => import("./pages/users/[id].js"));
// preload function
function preloadUser({ params, location }) {
// do preloading
}
// Pass it in the route definition
`
| key | type | description |
| -------- | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| params | object | The route parameters (same value as calling useParams() inside the route component) |{ pathname, search, hash, query, state, key}
| location | | An object that you can use to get more information about the path (corresponds to useLocation()) |"initial", "navigate", "native", "preload"
| intent | | Indicates why this function is being called.
|
A common pattern is to export the preload function and data wrappers that corresponds to a route in a dedicated route.data.js file. This way, the data function can be imported without loading anything else.
`js
import { lazy } from "solid-js";
import { Route } from "@solidjs/router";
import preloadUser from "./pages/users/[id].data.js";
const User = lazy(() => import("/pages/users/[id].js"));
// In the Route definition
`
The preload function's return value is passed to the page component for any intent other than "preload", allowing you to initialize data or alternatively use our new Data APIs:
Keep in mind that these are entirely optional, but they demonstrate the power of our preload mechanism.
To prevent duplicate fetching and to handle refetching triggers, we provide a query API that accepts a function and returns the same function.
`jsx/api/users/${id}
const getUser = query(async (id) => {
return (await fetch()).json();`
}, "users"); // used as the query key + serialized arguments
It is expected that the arguments to the query function are serializable.
This query accomplishes the following:
1. It does deduping on the server for the lifetime of the request.
2. It fills a preload cache in the browser which lasts 5 seconds. When a route is preloaded on hover or when preload is called when entering a route it will make sure to dedupe calls.
3. We have a reactive refetch mechanism based on key. So we can tell routes that aren't new to retrigger on action revalidation.
4. It will serve as a back/forward cache for browser navigation up to 5 mins. Any user based navigation or link click bypasses this cache. Revalidation or new fetch updates the cache.
Using it with preload function might look like:
`js
import { lazy } from "solid-js";
import { Route } from "@solidjs/router";
import { getUser } from ... // the query function
const User = lazy(() => import("./pages/users/[id].js"));
// preload function
function preloadUser({params, location}) {
void getUser(params.id)
}
// Pass it in the route definition
`
Inside your page component you:
`jsx
// pages/users/[id].js
import { getUser } from ... // the query function
export default function User(props) {
const user = createAsync(() => getUser(props.params.id));
return
Cached function has a few useful methods for getting the key that are useful for invalidation.
`ts
let id = 5;getUser.key; // returns "users"
getUser.keyFor(id); // returns "users[5]"
`You can revalidate the query using the
revalidate method or you can set revalidate keys on your response from your actions. If you pass the whole key it will invalidate all the entries for the query (ie "users" in the example above). You can also invalidate a single entry by using keyFor.query can be defined anywhere and then used inside your components with:$3
This is light wrapper over
createResource that aims to serve as stand-in for a future primitive we intend to bring to Solid core in 2.0. It is a simpler async primitive where the function tracks like createMemo and it expects a promise back that it turns into a Signal. Reading it before it is ready causes Suspense/Transitions to trigger.`jsx
const user = createAsync((currentValue) => getUser(params.id));
`It also preserves
latest field from createResource. Note that it will be removed in the future.`jsx
const user = createAsync((currentValue) => getUser(params.id));
return {user.latest.name}
;
`Using
query in createResource directly won't work properly as the fetcher is not reactive and it won't invalidate properly.$3
Similar to
createAsync except it uses a deeply reactive store. Perfect for applying fine-grained changes to large model data that updates.
It also supports latest field which will be removed in the future.`jsx
const todos = createAsyncStore(() => getTodos());
`$3
Actions are data mutations that can trigger invalidations and further routing. A list of prebuilt response helpers can be found below.
`jsx
import { action, revalidate, redirect } from "@solidjs/router"// anywhere
const myAction = action(async (data) => {
await doMutation(data);
throw redirect("/", { revalidate: getUser.keyFor(data.id) }); // throw a response to do a redirect
});
// in component
//or
`Actions only work with post requests, so make sure to put
method="post" on your form.Sometimes it might be easier to deal with typed data instead of
FormData and adding additional hidden fields. For that reason Actions have a with method. That works similar to bind which applies the arguments in order.Picture an action that deletes Todo Item:
`js
const deleteTodo = action(async (formData: FormData) => {
const id = Number(formData.get("id"))
await api.deleteTodo(id)
})
`Instead with
with you can write this:`js
const deleteTodo = action(api.deleteTodo)
`Actions also take a second argument which can be the name or an option object with
name and onComplete. name is used to identify SSR actions that aren't server functions (see note below). onComplete allows you to configure behavior when actions complete. Keep in mind onComplete does not work when JavaScript is disabled.#### Notes on