Redwood Router (RR) is the built-in router for Redwood apps. It takes inspiration from Ruby on Rails, React Router, and Reach Router, but is very opinionated in its own way. It is designed to list all routes in a single file, without any nesting, for ease of tracking which routes map to which pages.
Install and use outside of a Redwood app
RR was designed for use in Redwood apps, and if you use yarn create-redwood-app
it will be installed for you. Even though RR was designed for use in Redwood apps, it can also be used outside of Redwood apps.
If you'd like to use RR in a non-Redwood app start by installing it with yarn.
$ yarn add @redwoodjs/router
You can then import and use the various RR components like normal. Since Redwood automatically makes all your Pages available in the Routes.js
file you'll need to do this on your own.
const HomePage = {
name: 'HomePage',
loader: () => import('path/to/HomePage.js'),
}
...
<Router>
<Route path="/" page={HomePage} name="home" />
</Router>
RR also provides code splitting by default for every Page so you'll need to define each Page as an object to mimic this.
import HomePage from 'path/to/HomePage.js'
...
<Router>
<Route
path="/"
page={HomePage}
name="home"
/>
</Router>
Router/Route
Router
contains all of your routes and each route is specified with a Route
. RR attempts to:
- Match the current URL to each route in turn
- Stopping when it finds a match
- Rendering that route only
- Only exception to this is
notfound
route, which can be placed anywhere in the list and only matches when no other routes do
Render a single Route
with a notfound
prop when no other route matches
// Routes.js
import { Router, Route } from '@redwoodjs/router'
const Routes = () => (
<Router>
<Route notfound page={NotFoundPage} />
</Router>
)
export default Routes
To create a route to a normal Page, you'll pass three props:
-
path
- URL path to match starting with the beginning slash -
page
- Page component to render for matching path -
name
- Name for the named route function
// Routes.js
<Route
path="/"
page={HomePage}
name="home"
/>
Link and named route functions
RR doesn't have to hardcode URL paths and can generate links to your pages in a Page component. Link
generates a link to one of your routes and can access URL generators for any of your routes from the routes
object.
// SomePage.js
import { Link, routes } from '@redwoodjs/router'
const SomePage = () => <Link to={routes.home()} />
Named route functions simply return a string, so hardcoded strings can still be passed with the to
prop of the Link
component. If you change the path
but keep the same name
then you won't need to change any of the Link
's.
Active links
NavLink
is a special version of Link
that will add an activeClassName
to the rendered element when it matches the current URL. useMatch
can create your own component with active styles since NavLink
uses it internally.
Renders <a href="/" className="link activeLink">
on home page
// MainMenu.js
import { NavLink, routes } from '@redwoodjs/router'
const MainMenu = () => <NavLink className="link" activeClassName="activeLink" to={routes.home()} >Home</NavLink>
import { Link, routes, useMatch } from '@redwoodjs/router'
const CustomLink = ({to, ...rest}) => {
const matchInfo = useMatch(to)
return <SomeStyledComponent as={Link} to={to} isActive={matchInfo.match} />
}
const MainMenu = () => {
return <CustomLink to={routes.about()} />
}
Route parameters
You can use route parameters to match variable data in a path. They will match up to the next slash or end-of-string by default. Once extracted, they are sent as props to the Page component.
// Routes.js
<Route
path="/user/{id}>"
page={UserPage}
name="user"
/>
// Routes.js
<Route
path="/blog/{year}/{month}/{day}/{slug}"
page={PostPage}
name="post"
/>
Receive route parameters
// PostPage.js
const PostPage = ({ year, month, day, slug }) => { ... }
Named route functions with parameters
Routes with route parameters will take an object of those parameters as an argument into their named route function. All parameters will be converted to strings before being inserted into the generated URL.
// SomePage.js
<Link to={routes.user({ id: 7 })} />
Route parameter types
RR auto-converts certain types right in the path
specification. Adding :Int
onto the route parameter tells RR to only match /\d+/
and then use Number()
to convert the parameter into a number.
// Routes.js
<Route
path="/user/{id:Int}"
page={UserPage}
name="user"
/>
A request for /user/ajcwebdev
will fail to match the first route but succeed in matching the second.
// Routes.js
<Route
path="/user/{id:Int}"
page={UserIntPage}
name="userInt"
/>
<Route
path="/user/{id}"
page={UserStringPage}
name="userString"
/>
Core route parameter types
Core parameter types are built-in parameter types and begin with a capital letter. Int
for matching and converting integers is currently the only core parameter type.
User route parameter types
RR allows you to define your own route parameter types. Custom types must begin with a lowercase letter. The team created a custom slug
route parameter type defined by a constraint
and a transform
. Both are optional.
// Routes.js
const userRouteParamTypes = {
slug: {
constraint: /\w+-\w+/,
transform: (param) => param.split('-'),
}
}
<Router paramTypes={userRouteParamTypes}>
<Route path="/post/{name:slug}" page={PostPage} name={post} />
</Router>
useParams
RR uses the useParams
hook to pull in the id
route parameter without needing them passed in from anywhere.
// SomeDeeplyNestedComponent.js
import { useParams } from '@redwoodjs/router'
const SomeDeeplyNestedComponent = () => {
const { id } = useParams()
...
}
navigate
The navigate
function enables programmatic navigation between different pages.
// SomePage.js
import { navigate, routes } from '@redwoodjs/router'
const SomePage = () => {
const onSomeAction = () => {
navigate(routes.home())
}
...
}
Redirect
Declaratively redirects to a different page.
// SomePage.js
import { Redirect, routes } from '@redwoodjs/router'
const SomePage = () => {
<Redirect to={routes.home()}/>
}
Code-splitting vs. not code splitting
RR will code-split on every Page by default and create separate lazy-loaded webpack bundles. The new Page module will be loaded before re-rendering when navigating between pages to prevent the "white-flash" effect.
You can override the default lazy-loading behavior and include certain Pages in the main webpack bundle.
// Routes.js
import HomePage from 'src/pages/HomePage'
PageLoadingContext
usePageLoadingContext
signals to the user that something is happening after they click a link. When the lazy-loaded page is loading, PageLoadingContext.Consumer
will pass { loading: true }
to the render function, or false otherwise.
This context can still be used anywhere in the application. RR will only show the loader when it takes more than 1000 milliseconds for the page to load.
// SomeLayout.js
import { usePageLoadingContext } from '@redwoodjs/router'
const SomeLayout = (props) => {
const { loading } = usePageLoadingContext()
return (
<div>
{loading && <div>Loading...</div>}
<main>{props.children}</main>
</div>
)
}
We'll tell the loader to show up after 500ms of load time. You can set this value to 0 or change the network speed in developer tools to "Slow 3G".
// Routes.js
<Router pageLoadingDelay={500}>...</Router>
Private Routes
All Routes
nested in <Private>
require authentication.
Unauthenticated users attempting to visit this route will be redirected to the route passed as the unauthenticated
prop. The useAuth
hook determines under the hood if the user is authenticated.
// Routes.js
<Router>
<Route
path="/"
page={HomePage}
name="home"
/>
<Private unauthenticated="home">
<Routes
path="/admin"
page={AdminPage}
name="admin"
/>
</Private>
</Router>
Discussion