Routing
Routing is possibly the most important concept to understand in SolidStart. Everything starts with your routes: the compiler, the initial request, and almost every user interaction afterward. In this section, you'll learn how to write basic routes, navigate between routes, and handle more complex/dynamic routing scenarios.
There are two categories of routes:
- UI routes, which define the user interfaces in your app
- API routes, which define data endpoints in your app
This section of the documentation will mainly focus on UI routes, but you can learn more about API routes in the API Routes section.
Creating new pages
SolidStart uses file based routing. This means that the directory structure of your routes folder will translate exactly to the route structure in your application.
Files in the routes directory will be treated as routes. Directories will be treated as additional route segments. For UI routes, they can be used along with parent layout components to form nested routes.
Here are a few examples of how to arrange files in the routes directory to match a given url. (Note: The file extension could be either: .tsx or .jsx depending on whether or not you are using TypeScript in your application.)
To create a new route/page in your application, just create a new file in the routes directory with the same name.
hogwarts.com/blog ➜ /routes/blog.tsxhogwarts.com/contact ➜ /routes/contact.tsxhogwarts.com/directions ➜ /routes/directions.tsx
To create new pages after a given route segment, simply create a directory with the name of the preceding route segment, and create new files in that directory.
hogwarts.com/admin/edit-settings ➜ /routes/admin/edit-settings.tsxhogwarts.com/amenities/chamber-of-secrets ➜ /routes/amenities/chamber-of-secrets.tsxhogwarts.com/amenities/quidditch-pitch ➜ /routes/amenities/quidditch-pitch.tsx
Files named index will be rendered when there are no additional URL route segments being requested for a matching directory.
hogwarts.com ➜ /routes/index.tsxhogwarts.com/admin ➜ /routes/admin/index.tsxhogwarts.com/staff/positions ➜ /routes/staff/positions/index.tsx
Additionally, there are some special file names that map to URLPattern patterns, e.g.
hogwarts.com/students/:id ➜ /src/routes/students/[id].tsxhogwarts.com/students/:id/:name ➜ /src/routes/students/[id]/[name].tsxhogwarts.com/*missing ➜ /src/routes/[...missing].tsx
We put all our routes in the same top-level directory, src/routes. This includes our pages, but also our API routes. For a route to be rendered as a page, it should default export a Component. This component represents the content that will be rendered when users visit the page:
routes/index.tsx
tsx
export default function Indexfunction Index(): JSX.Element
() { return <div(property) JSX.HTMLElementTags.div: JSX.HTMLAttributes<HTMLDivElement>
>Welcome to Hogwarts!</div(property) JSX.HTMLElementTags.div: JSX.HTMLAttributes<HTMLDivElement>
>; }
routes/index.tsx
tsx
export default function Indexfunction Index(): JSX.Element
() { return <div(property) JSX.HTMLElementTags.div: JSX.HTMLAttributes<HTMLDivElement>
>Welcome to Hogwarts!</div(property) JSX.HTMLElementTags.div: JSX.HTMLAttributes<HTMLDivElement>
>; }
In this example, visiting hogwarts.com/ will render a <div> with the text "Welcome to Hogwarts!" inside it.
Under the hood, SolidStart traverses your routes directory, collects all the routes, and makes them accessible using the <FileRoutes /> component. The <FileRoutes /> component only includes your UI routes, and not your API routes. You can use it instead of manually entering all your Routes inside the <Routes /> component in root.tsx. Let the compiler do the boring work!
root.tsx
tsx
import { Html(alias) function Html(props: ComponentProps<"html">): JSX.Element
import Html
, Body(alias) function Body(props: ComponentProps<"body">): JSX.Element
import Body
, Routes(alias) const Routes: (props: RoutesProps) => JSX.Element
import Routes
, FileRoutes(alias) function FileRoutes(): JSX.Element
import FileRoutes
} from "solid-start";
export default function Rootfunction Root(): JSX.Element
() { return (
<Html(alias) function Html(props: ComponentProps<"html">): JSX.Element
import Html
> <Body(alias) function Body(props: ComponentProps<"body">): JSX.Element
import Body
> <Routes(alias) const Routes: (props: RoutesProps) => JSX.Element
import Routes
> <FileRoutes(alias) function FileRoutes(): JSX.Element
import FileRoutes
/> </Routes(alias) const Routes: (props: RoutesProps) => JSX.Element
import Routes
> </Body(alias) function Body(props: ComponentProps<"body">): JSX.Element
import Body
> </Html(alias) function Html(props: ComponentProps<"html">): JSX.Element
import Html
> );
}
root.tsx
tsx
import { Html(alias) function Html(props: ComponentProps<"html">): JSX.Element
import Html
, Body(alias) function Body(props: ComponentProps<"body">): JSX.Element
import Body
, Routes(alias) const Routes: (props: RoutesProps) => JSX.Element
import Routes
, FileRoutes(alias) function FileRoutes(): JSX.Element
import FileRoutes
} from "solid-start";
export default function Rootfunction Root(): JSX.Element
() { return (
<Html(alias) function Html(props: ComponentProps<"html">): JSX.Element
import Html
> <Body(alias) function Body(props: ComponentProps<"body">): JSX.Element
import Body
> <Routes(alias) const Routes: (props: RoutesProps) => JSX.Element
import Routes
> <FileRoutes(alias) function FileRoutes(): JSX.Element
import FileRoutes
/> </Routes(alias) const Routes: (props: RoutesProps) => JSX.Element
import Routes
> </Body(alias) function Body(props: ComponentProps<"body">): JSX.Element
import Body
> </Html(alias) function Html(props: ComponentProps<"html">): JSX.Element
import Html
> );
}
This means that all you have to do is create a file in your routes folder and SolidStart takes care of everything else needed to make that route available to visit in your application!
Navigating between pages
While the user can enter your app from any route, once they are in, you can provide them a designed user experience. You need a way for the user to travel between your routes. The HTML spec has the <a> tag for this purpose. You can use <a> tags to add links between pages in your app. Nothing special. That will work in SolidStart as well.
But SolidStart also provides an enhanced <a> tag, the <A> component. It is a wrapper around the <a> tag and provides a few additional features. Once the app is mounted, when the user navigates to a new page, the <A> will take over the navigation and will render the new page without a full page refresh. This is commonly known as client-side routing. It also knows what to do when the app is running in other modes.
Using links
The best way to add a link to another page in your app is to use the enhanced anchor tag <A>. You can add the href prop to the <A> tag, and we will navigate to that route in SPA style.
tsx
import { A(alias) const A: (props: AnchorProps) => JSX.Element
import A
} from 'solid-start';
export default function Indexfunction Index(): JSX.Element
() { return (
<div(property) JSX.HTMLElementTags.div: JSX.HTMLAttributes<HTMLDivElement>
> <A(alias) const A: (props: AnchorProps) => JSX.Element
import A
href(property) AnchorProps.href: string
="/about">About</A(alias) const A: (props: AnchorProps) => JSX.Element
import A
> </div(property) JSX.HTMLElementTags.div: JSX.HTMLAttributes<HTMLDivElement>
> );
}
tsx
import { A(alias) const A: (props: AnchorProps) => JSX.Element
import A
} from 'solid-start';
export default function Indexfunction Index(): JSX.Element
() { return (
<div(property) JSX.HTMLElementTags.div: JSX.HTMLAttributes<HTMLDivElement>
> <A(alias) const A: (props: AnchorProps) => JSX.Element
import A
href(property) AnchorProps.href: string
="/about">About</A(alias) const A: (props: AnchorProps) => JSX.Element
import A
> </div(property) JSX.HTMLElementTags.div: JSX.HTMLAttributes<HTMLDivElement>
> );
}
You can specify class names to add to the <A> tag when the current location matches the href of the anchor using the activeClass prop. Use the inactiveClass prop to add a class name to the <a> tag if the current route does not match the href of the anchor.
tsx
import { A(alias) const A: (props: AnchorProps) => JSX.Element
import A
} from "solid-start"
export default function UsersLayoutfunction UsersLayout(): JSX.Element
() { return (
<div(property) JSX.HTMLElementTags.div: JSX.HTMLAttributes<HTMLDivElement>
> <A(alias) const A: (props: AnchorProps) => JSX.Element
import A
href(property) AnchorProps.href: string
="./projects" activeClass(property) AnchorProps.activeClass?: string | undefined
="active-link" inactiveClass(property) AnchorProps.inactiveClass?: string | undefined
="inactive-link" >
Projects
</A(alias) const A: (props: AnchorProps) => JSX.Element
import A
>
// renders this when the user is on /users/1/projects
<a(property) JSX.HTMLElementTags.a: JSX.AnchorHTMLAttributes<HTMLAnchorElement>
href(property) JSX.AnchorHTMLAttributes<HTMLAnchorElement>.href?: string | undefined
="/users/1/projects" class(property) JSX.HTMLAttributes<HTMLAnchorElement>.class?: string | undefined
="active-link">Projects</a(property) JSX.HTMLElementTags.a: JSX.AnchorHTMLAttributes<HTMLAnchorElement>
>
// and this when the user is on /users/1/tasks
<a(property) JSX.HTMLElementTags.a: JSX.AnchorHTMLAttributes<HTMLAnchorElement>
href(property) JSX.AnchorHTMLAttributes<HTMLAnchorElement>.href?: string | undefined
="/users/1/projects" class(property) JSX.HTMLAttributes<HTMLAnchorElement>.class?: string | undefined
="inactive-link">Projects</a(property) JSX.HTMLElementTags.a: JSX.AnchorHTMLAttributes<HTMLAnchorElement>
> </div(property) JSX.HTMLElementTags.div: JSX.HTMLAttributes<HTMLDivElement>
> );
}
tsx
import { A(alias) const A: (props: AnchorProps) => JSX.Element
import A
} from "solid-start"
export default function UsersLayoutfunction UsersLayout(): JSX.Element
() { return (
<div(property) JSX.HTMLElementTags.div: JSX.HTMLAttributes<HTMLDivElement>
> <A(alias) const A: (props: AnchorProps) => JSX.Element
import A
href(property) AnchorProps.href: string
="./projects" activeClass(property) AnchorProps.activeClass?: string | undefined
="active-link" inactiveClass(property) AnchorProps.inactiveClass?: string | undefined
="inactive-link" >
Projects
</A(alias) const A: (props: AnchorProps) => JSX.Element
import A
>
// renders this when the user is on /users/1/projects
<a(property) JSX.HTMLElementTags.a: JSX.AnchorHTMLAttributes<HTMLAnchorElement>
href(property) JSX.AnchorHTMLAttributes<HTMLAnchorElement>.href?: string | undefined
="/users/1/projects" class(property) JSX.HTMLAttributes<HTMLAnchorElement>.class?: string | undefined
="active-link">Projects</a(property) JSX.HTMLElementTags.a: JSX.AnchorHTMLAttributes<HTMLAnchorElement>
>
// and this when the user is on /users/1/tasks
<a(property) JSX.HTMLElementTags.a: JSX.AnchorHTMLAttributes<HTMLAnchorElement>
href(property) JSX.AnchorHTMLAttributes<HTMLAnchorElement>.href?: string | undefined
="/users/1/projects" class(property) JSX.HTMLAttributes<HTMLAnchorElement>.class?: string | undefined
="inactive-link">Projects</a(property) JSX.HTMLElementTags.a: JSX.AnchorHTMLAttributes<HTMLAnchorElement>
> </div(property) JSX.HTMLElementTags.div: JSX.HTMLAttributes<HTMLDivElement>
> );
}
There are cases where the anchor is not right for your navigation needs. For example,
- You want to navigate after an async process completes
- You want to navigate after the user clicks a button, and we do some logic.
For these use cases you can use an imperative navigate function that you can get by calling useNavigate().
Redirecting
The primary way of redirecting from a route to another is to use the <Navigate /> component in the JSX. For example, if you want to redirect to the home page, you can use the following code.
routes/inactive.tsx
tsx
import { Navigate(alias) function Navigate(props: NavigateProps): null
import Navigate
} from "solid-start";
export default function InactivePagefunction InactivePage(): JSX.Element
() { return <Navigate(alias) function Navigate(props: NavigateProps): null
import Navigate
href(property) NavigateProps.href: string | ((args: {
navigate: Navigator;
location: Location<unknown>;
}) => string) ="/" />; }
routes/inactive.tsx
tsx
import { Navigate(alias) function Navigate(props: NavigateProps): null
import Navigate
} from "solid-start";
export default function InactivePagefunction InactivePage(): JSX.Element
() { return <Navigate(alias) function Navigate(props: NavigateProps): null
import Navigate
href(property) NavigateProps.href: string | ((args: {
navigate: Navigator;
location: Location<unknown>;
}) => string) ="/" />; }
- Server: When we get a request for this page, we will send a
308 (Redirect) response with Location header set to the home page. The browser will then do its normal redirect routine. This also helps crawlers to understand that the page should be redirected. - Client: When you navigate to this page from another page in the site, we will immediately navigate to the home page.
There is another way for redirects to happen in SolidStart. Using your data functions and actions.
When you throw/return a redirect Response from your data function, SolidStart will take the user to the specified location.
routes/users.tsx
tsx
import { redirectA redirect response. Sets the status code and the `Location` header.
Defaults to "302 Found".
(alias) function redirect(url: string, init?: number | ResponseInit): Response
import redirect
, createServerData$(alias) const createServerData$: {
<T, S = true>(fetcher: RouteDataFetcher<S, T>, options?: RouteDataOptions<undefined, S> | undefined): Resource<T | undefined>;
<T, S = true>(fetcher: RouteDataFetcher<...>, options: RouteDataOptions<...>): Resource<...>;
}
import createServerData$ } from "solid-start/server";
export default function Userfunction User(): JSX.Element
() { const dataconst data: Resource<{
id: number;
} | undefined> = createServerData$(alias) createServerData$<{
id: number;
}, true>(fetcher: RouteDataFetcher<true, {
id: number;
}>, options?: RouteDataOptions<undefined, true> | undefined): Resource<{
id: number;
} | undefined> (+1 overload)
import createServerData$ ((_, { request(parameter) request: Request
}) => { if (!isLoggedInfunction isLoggedIn(req: Request): boolean
(request(parameter) request: Request
)) { throw redirectA redirect response. Sets the status code and the `Location` header.
Defaults to "302 Found".
(alias) redirect(url: string, init?: number | ResponseInit): Response
import redirect
("/login"); }
});
return <div(property) JSX.HTMLElementTags.div: JSX.HTMLAttributes<HTMLDivElement>
>User {dataconst data: () => {
id: number;
} | undefined ()?.id(property) id: number | undefined
}</div(property) JSX.HTMLElementTags.div: JSX.HTMLAttributes<HTMLDivElement>
>; }
routes/users.tsx
tsx
import { redirectA redirect response. Sets the status code and the `Location` header.
Defaults to "302 Found".
(alias) function redirect(url: string, init?: number | ResponseInit): Response
import redirect
, createServerData$(alias) const createServerData$: {
<T, S = true>(fetcher: RouteDataFetcher<S, T>, options?: RouteDataOptions<undefined, S> | undefined): Resource<T | undefined>;
<T, S = true>(fetcher: RouteDataFetcher<...>, options: RouteDataOptions<...>): Resource<...>;
}
import createServerData$ } from "solid-start/server";
export default function Userfunction User(): JSX.Element
() { const dataconst data: Resource<{
id: number;
} | undefined> = createServerData$(alias) createServerData$<{
id: number;
}, true>(fetcher: RouteDataFetcher<true, {
id: number;
}>, options?: RouteDataOptions<undefined, true> | undefined): Resource<{
id: number;
} | undefined> (+1 overload)
import createServerData$ ((_, { request(parameter) request: Request
}) => { if (!isLoggedInfunction isLoggedIn(req: Request): boolean
(request(parameter) request: Request
)) { throw redirectA redirect response. Sets the status code and the `Location` header.
Defaults to "302 Found".
(alias) redirect(url: string, init?: number | ResponseInit): Response
import redirect
("/login"); }
});
return <div(property) JSX.HTMLElementTags.div: JSX.HTMLAttributes<HTMLDivElement>
>User {dataconst data: () => {
id: number;
} | undefined ()?.id(property) id: number | undefined
}</div(property) JSX.HTMLElementTags.div: JSX.HTMLAttributes<HTMLDivElement>
>; }
Similarly, if your actions dispatched using createRouteAction/createServerAction$ throw/return a redirect Response, SolidStart will take the user to the specified location.
Dynamic routes
Dynamic routes are routes that can match any value for one segment of the route. For example, /users/1 and /users/2 are both valid routes. If this is the case for any userId, you can't go around defining separate routes for each user. You need dynamic routes. In SolidStart, dynamic routes are defined using square brackets ([]). For example, /users/[id] is a dynamic route, where id is the dynamic segment.
tsx
|-- routes/
|-- users/
|-- [id].tsx
tsx
|-- routes/
|-- users/
|-- [id].tsx
routes/users/[id].tsx
tsx
import { useParams(alias) const useParams: <T extends Params>() => T
import useParams
} from "solid-start";
export default function UserPagefunction UserPage(): JSX.Element
() { const params = useParams(alias) useParams<Params>(): Params
import useParams
(); return <div(property) JSX.HTMLElementTags.div: JSX.HTMLAttributes<HTMLDivElement>
>User {params.id}</div(property) JSX.HTMLElementTags.div: JSX.HTMLAttributes<HTMLDivElement>
>; }
routes/users/[id].tsx
tsx
import { useParams(alias) const useParams: <T extends Params>() => T
import useParams
} from "solid-start";
export default function UserPagefunction UserPage(): JSX.Element
() { const params = useParams(alias) useParams<Params>(): Params
import useParams
(); return <div(property) JSX.HTMLElementTags.div: JSX.HTMLAttributes<HTMLDivElement>
>User {params.id}</div(property) JSX.HTMLElementTags.div: JSX.HTMLAttributes<HTMLDivElement>
>; }
Optional parameter
Optional parameter
matches users and users/123
tsx
|-- routes/
|-- users/
|-- [[id]].tsx
tsx
|-- routes/
|-- users/
|-- [[id]].tsx
Catch all routes
Catch all routes are routes that can match any value for any number of segments. For example, /blog/a/b/c and /blog/d/e are both valid routes. You can define catch-all routes using square brackets with ... before the label for the route.For example, /blog/[...post] is a dynamic route, where post is the dynamic segment.
post will be a property on the params object that is returned by the useParams hook. It will be a string with the value of the dynamic segment.
tsx
|-- routes/
|-- blog/
|-- index.tsx
|-- [...post].tsx
tsx
|-- routes/
|-- blog/
|-- index.tsx
|-- [...post].tsx
routes/blog/[...post].tsx
tsx
import { useParams(alias) const useParams: <T extends Params>() => T
import useParams
} from "solid-start";
export default function BlogPagefunction BlogPage(): JSX.Element
() { const params = useParams(alias) useParams<Params>(): Params
import useParams
(); return <div(property) JSX.HTMLElementTags.div: JSX.HTMLAttributes<HTMLDivElement>
>Blog {params.post}</div(property) JSX.HTMLElementTags.div: JSX.HTMLAttributes<HTMLDivElement>
>; }
routes/blog/[...post].tsx
tsx
import { useParams(alias) const useParams: <T extends Params>() => T
import useParams
} from "solid-start";
export default function BlogPagefunction BlogPage(): JSX.Element
() { const params = useParams(alias) useParams<Params>(): Params
import useParams
(); return <div(property) JSX.HTMLElementTags.div: JSX.HTMLAttributes<HTMLDivElement>
>Blog {params.post}</div(property) JSX.HTMLElementTags.div: JSX.HTMLAttributes<HTMLDivElement>
>; }
Nested routes
As most UIs evolve, they normally have shared layouts for sections of the app, often driven by the location itself. If you want to add a parent layout to all the children routes in one folder, you can add a file with the same name as that folder, next to that folder itself.
tsx
|-- routes/
|-- users.tsx
|-- users/
|-- index.tsx
|-- [id].tsx
|-- projects.tsx
tsx
|-- routes/
|-- users.tsx
|-- users/
|-- index.tsx
|-- [id].tsx
|-- projects.tsx
routes/users.tsx
tsx
import { Outlet(alias) const Outlet: () => JSX.Element
import Outlet
} from "solid-start";
export default function UsersLayoutfunction UsersLayout(): JSX.Element
() { return (
<div(property) JSX.HTMLElementTags.div: JSX.HTMLAttributes<HTMLDivElement>
> <h1(property) JSX.HTMLElementTags.h1: JSX.HTMLAttributes<HTMLHeadingElement>
>Users</h1(property) JSX.HTMLElementTags.h1: JSX.HTMLAttributes<HTMLHeadingElement>
> <Outlet(alias) const Outlet: () => JSX.Element
import Outlet
/> </div(property) JSX.HTMLElementTags.div: JSX.HTMLAttributes<HTMLDivElement>
> );
}
routes/users.tsx
tsx
import { Outlet(alias) const Outlet: () => JSX.Element
import Outlet
} from "solid-start";
export default function UsersLayoutfunction UsersLayout(): JSX.Element
() { return (
<div(property) JSX.HTMLElementTags.div: JSX.HTMLAttributes<HTMLDivElement>
> <h1(property) JSX.HTMLElementTags.h1: JSX.HTMLAttributes<HTMLHeadingElement>
>Users</h1(property) JSX.HTMLElementTags.h1: JSX.HTMLAttributes<HTMLHeadingElement>
> <Outlet(alias) const Outlet: () => JSX.Element
import Outlet
/> </div(property) JSX.HTMLElementTags.div: JSX.HTMLAttributes<HTMLDivElement>
> );
}
Route Groups
With file system routing, folders map directly to URL Paths. However, there might be times when you want to create folders for the sake of organization without affecting the URL structure. This can be done by using a Route Group. In SolidStart, Route Groups are defined using parenthesis surrounding the folder name (example).
tsx
|-- routes/
|-- (static)
|-- about-us // example.com/about-us
|-- index.tsx
|-- contact-us // example.com/contact-us
|-- index.tsx
tsx
|-- routes/
|-- (static)
|-- about-us // example.com/about-us
|-- index.tsx
|-- contact-us // example.com/contact-us
|-- index.tsx
Renaming Index
By default, the component that is rendered for a route comes from the default export of the index.tsx file in each folder. However, this could make it harder to find which index.tsx file is the correct one when searching since there will be multiple files with that name. To avoid this pitfall, we also render the default export from any file that follows (fileName).tsx syntax.
tsx
|-- routes/
|-- (home).tsx // example.com
|-- users.tsx
|-- users/
|-- (all-users).tsx // example.com/users
|-- [id].tsx
|-- projects.tsx
tsx
|-- routes/
|-- (home).tsx // example.com
|-- users.tsx
|-- users/
|-- (all-users).tsx // example.com/users
|-- [id].tsx
|-- projects.tsx