Web Front-end8-minute read

React Router Tutorial: Redirect Like a Pro

Naive React routing increases risk and maintenance. This tutorial provides a full exploration of routing approaches, achieving an elegant solution that seamlessly fits into any React code base.

Last updated: Jun 29, 2026

Toptalauthors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.

Naive React routing increases risk and maintenance. This tutorial provides a full exploration of routing approaches, achieving an elegant solution that seamlessly fits into any React code base.

Last updated: Jun 29, 2026

Toptalauthors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.
Nathan Babcock

Nathan is a front-end and full-stack developer, and an expert in streamlining UI/UX with React. As the lead design engineer at Motorola Solutions, he marshaled an internal product that included React, Angular, Svelte, and native web components from prototype to corporatewide deployment—garnering more than 100,000 downloads.

Expertise

Previously At

Motorola Solutions
Share

React Router is the de facto React page switching and routing solution. React Router was one of the first popular, open-source projects around React back in 2014 and has grown along with React to a prominent place within React’s ecosystem.

In this React Router tutorial, I start with a key concept and explain my choice of routing library. I then detail how to create a simple application with just enough programmatic logic to showcase various routing features. Lastly, I focus on implementing an elegant, secure, and reusable component to achieve a minimally intrusive and low-maintenance routing solution. The resulting routing code comports with React’s coding guidelines and style for a seamless fit within any recent React application.

Getting Started: Declarative Routing Basics

Declarative routing is the coding style used within React and React Router. React’s declarative routes are components and use the same plumbing available in any React application. Since routes are components, they benefit from consistent approaches.

These routes associate web addresses with specific pages and other components, leveraging React’s powerful rendering engine and conditional logic to turn routes on and off programmatically. Conditional routing in React allows us to implement application logic to ensure our routes are correct and adequately secured.

Of course, any router is only as good as its library. Many developers don’t consider quality of life when choosing a library, but React Router v7, the current stable release, delivers a bevy of powerful features to simplify routing tasks and should be the React routing solution of choice.

What makes React Router the best compared to other routing libraries?

  • It has declarative route definitions (using JSX inside of React components).
  • It is the industry standard.
  • It offers code samples galore and a plethora of online tutorials.
  • It provides modern React code conventions (using hooks, functional components, and a unified react-router package that replaces the separate react-router-dom install).

Developers who are using the previous version, React Router v5, should know about three key changes to React Router v6:

  • The <Switch> component has been renamed <Routes>.
  • A useRoutes() hook replaces react-router-config for defining routes as plain objects.
  • Every component child of <Routes> must be a <Route>. This can break some previous methods for organizing and composing routes.

The remainder of this article explores various v6-compatible patterns and ends with our ultimate and most elegant route composition. For more about upgrading from v5 to v6, check out the official migration guide.

React Router v7, released in November 2024, builds on v6’s Data Router foundation and is the version used in the Grand Finale below. If you’re on v6.4 or later, the migration is minimal. See the official v7 upgrade guide.

Time to Set Up a Basic React Application

Every React routing tutorial needs a basic chassis to showcase its desired features. We expect that your development system has npm installed. Let’s create a simple React project with Vite—there’s no need to install Vite separately—that provides our base React app structure, a standalone web server, and all necessary dependencies:

npm create vite@latest redirect-app -- --template react-ts

This command creates our basic app using TypeScript.

React Routes Basics

React Router redirects users to pages within the client according to associated web addresses. An application’s routing logic includes general program logic, as well as requests for unknown pages (i.e., redirecting to a 404 page).

Since React generates a single-page application (SPA), these routes simulate old-school web applications with separate physical or file-based routing. React ensures that the end user maintains the illusion of a website and its collection of pages while retaining the benefits of SPAs such as instant page transitions. The React Router library also ensures that the browser history remains accessible and the back button remains functional.

Protect Your React Route

React Routes provide access to specific components with an SPA and thus make information and functionality available to the end user. We want users to access only features authorized by our system’s requirements.

Whereas security is essential in our React client, any secure implementation should provide additional (and arguably primary) security features on the server to protect against unauthorized client malfeasance. Anything can happen, and savvy browser users can debug our application via browser development tools. Safety first.

A prime example includes client-side administrative functions. We want these functions protected with system authentication and authorization plumbing. We should allow only system administrators access to potentially destructive system behaviors.

The Easy Solution You Shouldn’t Choose

There is a broad spectrum of expertise within the React developer community. Many novice React developers tend to follow less elegant coding styles regarding routes and associated secure access logic.

Typical naive implementation attributes include:

  • Defining route protection on every page.
  • Relying on useEffect React hooks to accomplish page redirection where unauthorized page access is detected.
  • Requiring an entire page to load before redirect and route protection logic executes.

A naive routing component implementation might look like this:

import { useContext, useEffect } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import { UserContext } from '../UserContext'

export default function NaiveApproach() {
  const { loggedIn } = useContext(UserContext)
  const navigate = useNavigate()

  useEffect(() => {
    // Check if the user is logged in (after the page loads)
    // If they're not, redirect them to the homepage
    if (!loggedIn) navigate('/access-denied')
  })

  return (
    <div>Page content...</div>
  )
}

An application would use this routing component like this:

export default function App() {
  return (
    <Router>
      <Routes>
        {/* Method 1: Using `useEffect()` as a redirect */}
        <Route path="/naive-approach" element={<NaiveApproach />} />
      </Routes>
    </Router>
  )
}

This approach is often implemented but should be avoided, as it wastes system performance and annoys our user base. Naive routing will do three things:

  1. Negatively impact our app’s performance.
    • Other useEffect hooks could potentially run before the redirect in React happens.
    • We could see a system slowdown caused by unnecessary server-side requests. A 75% or more degradation would be unsurprising depending on the number of logic blocks encountered before running security checks.
  2. Potentially cause the site or page to flicker.
    • Because the protected page loads first, it briefly navigates to the requested web address but may redirect, depending on page security logic.
  3. Copy secure routing logic everywhere.
    • This routing logic implementation on every protected page in our application would cause a maintenance nightmare.

Better React Routing With a Custom Component

We want to make our secure routing more elegant. Three things that will help us achieve a better implementation are minimizing code maintenance, centralizing secure routing logic to minimize code impact, and improving application performance. We implement a custom ProtectedRoute component to achieve these goals:

import { ReactNode } from 'react'
import { Navigate } from 'react-router-dom'

/**
 * Only allows navigation to a route if a condition is met.
 * Otherwise, it redirects to a different specified route.
 */
export default function ConditionalRoute({
  condition,
  redirectTo,
  children,
}: ConditionalRouteProps): JSX.Element {
  return condition ? <>{children}</> : <Navigate to={redirectTo} replace />
}

export type ConditionalRouteProps = {
  /**
   * Route is created if its condition is true.
   * For example, `condition={isLoggedIn}` or `condition={isAdmin}`
   */
  condition: boolean

  /** The route to redirect to if `condition` is false */
  redirectTo: string

  children?: ReactNode
}

Our application code requires adjustment to make use of the new ConditionalRoute component:

export default function App() {
  return (
    <Router>
      <Routes>
       {/* Method 2: Using ConditionalRoute (better, but verbose) */}
        <Route
          path="/custom-component"
          element={
            <ConditionalRoute condition={isLoggedIn} redirectTo=”/”>
              <CustomComponentPage />
            </ConditionalRoute>
          }
        />
      </Routes>
    </Router>
  )
}

This implementation is markedly better than the easy, naive solution laid out earlier because it:

  • Achieves secure routing implementation in one component. This compartmentalized implementation significantly improves our code base maintenance cost.
  • Averts unnecessary and unauthorized page routes. This highly focused page routing logic potentially avoids unnecessary server calls and page rendering logic.

Although this implementation is better than others, it is far from perfect. The usage style seen in our application code sample tends to carry more code bloat than we like and is our motivation to write an even more elegant solution.

The Best React Router Solution

The higher-order component pattern above is a significant improvement, but it still has one blind spot: auth runs inside the React render cycle. That means the component tree starts mounting before the redirect happens, creating a brief flash of unauthorized content. React Router v7 solves this at the data layer with createBrowserRouter and loader functions. Loaders run before the component renders, so unauthorized users are redirected before a single pixel of protected content is painted.

First, add a small bridge to UserContext so loaders, which run outside the React tree, can read auth state. The useContext hook cannot be called outside a component, so we expose a module-level store that UserContext keeps in sync:

// UserContext.tsx (updated)
import { createContext, useState } from 'react'

// Module-level store readable by route loaders
export const authStore = { loggedIn: false }

export const UserContext = createContext({
  loggedIn: false,
  setLoggedIn: (_: boolean) => {}
})

export function UserProvider({ children }: { children: React.ReactNode }) {
  const [loggedIn, setLoggedIn] = useState(false)
  authStore.loggedIn = loggedIn // keep store in sync

  return (
    <UserContext.Provider value={{ loggedIn, setLoggedIn }}>
      {children}
    </UserContext.Provider>
  )
}

Now define an auth loader. throw redirect() exits immediately and the component never loads:

// authLoader.ts
import { redirect } from 'react-router-dom'
import { authStore } from './UserContext'

export async function authLoader() {
  if (!authStore.loggedIn) {
    throw redirect('/home')
  }

  return null
}

Build the router with createBrowserRouter. Protected routes share the auth loader through a parent route, so no per-page logic is required:

// router.tsx

import { createBrowserRouter } from 'react-router-dom'
import { authLoader } from './authLoader'
import { GrandFinalePage } from './pages/GrandFinalePage'
import { HomePage } from './pages/HomePage'

export const router = createBrowserRouter([
  {
    path: '/',

    children: [
      { path: 'home', element: <HomePage /> },

      {
        // Method 3: Data Router — auth runs before any child renders
        loader: authLoader,

        children: [
          {
            path: 'react/react-router-tutorial',
            element: <GrandFinalePage />
          },
        ],
      },
    ],
  },
])

Finally, replace <BrowserRouter> in your entry point with <RouterProvider>:

// main.tsx

import { RouterProvider } from 'react-router-dom'
import { UserProvider } from './UserContext'
import { router } from './router'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <UserProvider>
    <RouterProvider router={router} />
  </UserProvider>
)

This Data Router setup is concisely coded, eliminates flash of unauthorized content, and scales cleanly: adding a new protected route means appending one object to the children array with no HOC wrapping and no per-component auth logic.

Routing in React Achieved

Application routing implementations can be coded naively or elegantly, like any other code. We have surveyed the basics of routing as a full exploration of the code for simple and complex React Router-based implementations.

I hope the final routing approach resonates with your desire to bring a beautiful, low-maintenance routing solution to your application. Regardless of the method, you can quickly grade your routing implementation’s effectiveness and security by comparing it to our various examples. Routing in React doesn’t have to be an uphill path. Feel free to share your tips, experiences, and React Router best practices in the comments section.

The Toptal Engineering Blog extends its gratitude to Marco Sanabria for reviewing the repository and code samples presented in this article.

Understanding the basics

  • Developers use the React Router library to manage screen flow within an application.

  • React Router is a major timesaver. It ensures developers do not have to code their routing logic from scratch.

  • The React Router library is the most popular routing implementation used by developers. It has the greatest support worldwide.

  • React Router was originally developed by two key React team members, Michael Jackson and Ryan Florence. Currently, the React Router code base is maintained by Remix Software.

  • React Router must be installed through a simple “npm install” command, after which routes in React are implemented declaratively with JSX elements.

Hire a Toptal expert on this topic.
Hire Now
Nathan Babcock

Nathan Babcock

Chicago, IL, United States

Member since December 29, 2021

About the author

Nathan is a front-end and full-stack developer, and an expert in streamlining UI/UX with React. As the lead design engineer at Motorola Solutions, he marshaled an internal product that included React, Angular, Svelte, and native web components from prototype to corporatewide deployment—garnering more than 100,000 downloads.

authors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.
Expertise
PREVIOUSLY AT
Motorola Solutions

World-class articles, delivered weekly.

By entering your email, you are agreeing to our privacy policy.

World-class articles, delivered weekly.

By entering your email, you are agreeing to our privacy policy.

Join the Toptal® community.