DEV Community

loading...
Cover image for redwood router

redwood router

ajcwebdev profile image anthonyCampolo ・5 min read

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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
/>
Enter fullscreen mode Exit fullscreen mode

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()} />
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode
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()} />
}
Enter fullscreen mode Exit fullscreen mode

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"
/>
Enter fullscreen mode Exit fullscreen mode
// Routes.js

<Route
  path="/blog/{year}/{month}/{day}/{slug}"
  page={PostPage}
  name="post"
/>
Enter fullscreen mode Exit fullscreen mode

Receive route parameters

// PostPage.js

const PostPage = ({ year, month, day, slug }) => { ... }
Enter fullscreen mode Exit fullscreen mode

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 })} />
Enter fullscreen mode Exit fullscreen mode

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"
/>
Enter fullscreen mode Exit fullscreen mode

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"
/>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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()
  ...
}
Enter fullscreen mode Exit fullscreen mode

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())
  }
  ...
}
Enter fullscreen mode Exit fullscreen mode

Redirect

Declaratively redirects to a different page.

// SomePage.js

import { Redirect, routes } from '@redwoodjs/router'

const SomePage = () => {
  <Redirect to={routes.home()}/>
}
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Discussion

pic
Editor guide