Back to blog

Monday, March 3, 2025

React Router Explained and How to Build Multi-Page Applications in React

cover

Welcome to the world of React Router! Building multi-page applications (MPAs) in React can be efficiently managed using React Router, a powerful library designed for handling routing in React applications. This blog will guide you through the basics of setting up routes, navigating between components, and managing nested routes, ensuring that your application is organized and user-friendly.

What is React Router?

React Router is a standard library for routing in React applications. It enables the creation of single-page applications (SPAs) that can navigate between components without reloading the page. It is declarative and integrates seamlessly with React, making it a popular choice for developers building complex web applications.

Key Features of React Router

  • Declarative routing: You declare your routes in a way that is closely aligned with the component tree.
  • Nested routing: Easily handle nested routes, which means you can have routes inside other routes.
  • Dynamic routing: Use dynamic segments in your routes to match different paths.
  • URL parameters: Capture and use URL parameters in your components.
  • Redirects: Manage redirects programmatically.
  • Query parameters: Pass and extract query parameters from the URL.

Setting Up React Router

To get started with React Router, you need to install it in your React project. This can be done using npm or yarn.

Installation

You can install React Router by running the following command in your terminal:

npm install react-router-dom

Alternatively, if you are using yarn:

yarn add react-router-dom

Basic Setup

After installation, you can set up React Router in your application. Here’s a simple example to get you started.

Creating a Basic React Application

First, create a new React application if you don't have one already:

npx create-react-app my-app
cd my-app

Now, let’s set up React Router in this application.

Step 1: Import Required Components

Open your src/index.js file and import the BrowserRouter component from react-router-dom. This is the component that wraps your application and provides the routing capabilities.

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import App from './App';

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById('root')
);
Step 2: Set Up Basic Routes

In your src/App.js file, import the Route and Routes components from react-router-dom. These components are used to define your routes.

import React from 'react';
import { Routes, Route, Link } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';

function App() {
  return (
    <div>
      <nav>
        <ul>
          <li>
            <Link to="/">Home</Link>
          </li>
          <li>
            <Link to="/about">About</Link>
          </li>
          <li>
            <Link to="/contact">Contact</Link>
          </li>
        </ul>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
      </Routes>
    </div>
  );
}

export default App;
Step 3: Create the Page Components

Create the components for your pages. For this example, create three files: Home.js, About.js, and Contact.js inside a pages directory.

Home.js

function Home() {
  return <h2>Home Page</h2>;
}

export default Home;

About.js

function About() {
  return <h2>About Page</h2>;
}

export default About;

Contact.js

function Contact() {
  return <h2>Contact Page</h2>;
}

export default Contact;
Step 4: Run Your Application

Run your application using npm start or yarn start and navigate between the pages using the links in the navigation menu.

Understanding Routing Concepts

Before diving deeper, it’s important to understand the basic routing concepts in React Router.

Route Component

The Route component is used to define routes in your application. It takes a path prop that specifies the URL path, and an element prop that specifies the component to render when the path matches the URL.

Routes Component

The Routes component wraps multiple Route components. It ensures that only one Route is rendered at a time.

The Link component is used to create navigation links that allow users to navigate between routes without reloading the page.

Nested Routing

Nested routing is a feature that allows you to define routes within other routes. This is particularly useful for creating complex and hierarchical structures in your application.

Setting Up Nested Routes

To illustrate nested routing, let's add a nested route to the About page. We will create a Team component that will be accessible at /about/team.

Step 1: Create the Team Component

Create a Team.js file in the pages directory.

Team.js

function Team() {
  return <h3>Team Page</h3>;
}

export default Team;
Step 2: Update the About Component

Modify the About.js file to include nested routes.

About.js

import { Routes, Route, Link } from 'react-router-dom';
import Team from './Team';

function About() {
  return (
    <div>
      <h2>About Page</h2>
      <Link to="team">Our Team</Link>
      <Routes>
        <Route path="team" element={<Team />} />
      </Routes>
    </div>
  );
}

export default About;

Now, if you navigate to /about/team, you will see the Team component rendered inside the About component.

Programmatic Navigation

Sometimes, you might need to navigate between routes programmatically, rather than using Link components. React Router provides a useNavigate hook for this purpose.

Using the useNavigate Hook

Let’s add a button on the Home page that navigates to the About page when clicked.

Home.js

import { useNavigate } from 'react-router-dom';

function Home() {
  const navigate = useNavigate();

  const handleClick = () => {
    navigate('/about');
  };

  return (
    <div>
      <h2>Home Page</h2>
      <button onClick={handleClick}>Go to About Page</button>
    </div>
  );
}

export default Home;

Handling Parameters

React Router allows you to capture and use dynamic segments in your routes, which can be accessed via URL parameters.

Defining Routes with Parameters

Let’s create a route for a blog post that includes an ID parameter.

Step 1: Define the Route with a Parameter

Modify the App.js file to include a new route with a parameter.

App.js

import React from 'react';
import { Routes, Route, Link } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
import BlogPost from './pages/BlogPost';

function App() {
  return (
    <div>
      <nav>
        <ul>
          <li>
            <Link to="/">Home</Link>
          </li>
          <li>
            <Link to="/about">About</Link>
          </li>
          <li>
            <Link to="/contact">Contact</Link>
          </li>
        </ul>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
        <Route path="/post/:id" element={<BlogPost />} />
      </Routes>
    </div>
  );
}

export default App;
Step 2: Access the Parameter in the Component

The BlogPost component can access the id parameter using the useParams hook.

BlogPost.js

import { useParams } from 'react-router-dom';

function BlogPost() {
  const { id } = useParams();

  return (
    <div>
      <h2>Blog Post {id}</h2>
      <p>This is the blog post with id {id}</p>
    </div>
  );
}

export default BlogPost;

Managing Query Parameters

Query parameters are additional details appended to the URL after a question mark (?) and are useful for passing non-essential data.

Using Query Parameters

Let’s add a search feature to the blog posts. We will create a page that displays a list of blog posts and allows users to filter them using query parameters.

Step 1: Create a Blog Page

Create a new Blog.js file in the pages directory.

Blog.js

import { useSearchParams } from 'react-router-dom';

function Blog() {
  const [searchParams, setSearchParams] = useSearchParams();

  const query = searchParams.get('query');

  const blogPosts = [
    { id: 1, title: 'Introduction to React', content: 'Learn the basics of React...' },
    { id: 2, title: 'Advanced React Techniques', content: 'Explore advanced React techniques...' },
    // Add more blog posts as needed
  ];

  const filteredPosts = query
    ? blogPosts.filter(post => post.title.toLowerCase().includes(query.toLowerCase()))
    : blogPosts;

  return (
    <div>
      <h2>Blog Posts</h2>
      <input
        type="text"
        value={query || ''}
        placeholder="Search..."
        onChange={e => setSearchParams({ query: e.target.value })}
      />
      <ul>
        {filteredPosts.map(post => (
          <li key={post.id}>
            <Link to={`/post/${post.id}`}>{post.title}</Link>
            <p>{post.content}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default Blog;
Step 2: Add the Blog Route

Add the new Blog component to the routes in App.js.

App.js

import React from 'react';
import { Routes, Route, Link } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
import BlogPost from './pages/BlogPost';
import Blog from './pages/Blog';

function App() {
  return (
    <div>
      <nav>
        <ul>
          <li>
            <Link to="/">Home</Link>
          </li>
          <li>
            <Link to="/about">About</Link>
          </li>
          <li>
            <Link to="/contact">Contact</Link>
          </li>
          <li>
            <Link to="/blog">Blog</Link>
          </li>
        </ul>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
        <Route path="/post/:id" element={<BlogPost />} />
        <Route path="/blog" element={<Blog />} />
      </Routes>
    </div>
  );
}

export default App;

Now, you can navigate to the /blog page and use the search input to filter the blog posts.

Redirecting Users

Sometimes, you might need to redirect users to a different route based on certain conditions. React Router provides the Navigate component for this purpose.

Using the Navigate Component

Let’s create a scenario where users are redirected to the Home page if they try to access a non-existent route.

Step 1: Create a 404 Component

Create a NotFound.js component that will be displayed when a route is not found.

NotFound.js

function NotFound() {
  return <h2>404 - Page Not Found</h2>;
}

export default NotFound;
Step 2: Use the Navigate Component

Modify the App.js file to include a Route that redirects to the Home page if no match is found.

App.js

import React from 'react';
import { Routes, Route, Link, Navigate } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
import BlogPost from './pages/BlogPost';
import Blog from './pages/Blog';
import NotFound from './pages/NotFound';

function App() {
  return (
    <div>
      <nav>
        <ul>
          <li>
            <Link to="/">Home</Link>
          </li>
          <li>
            <Link to="/about">About</Link>
          </li>
          <li>
            <Link to="/contact">Contact</Link>
          </li>
          <li>
            <Link to="/blog">Blog</Link>
          </li>
        </ul>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
        <Route path="/post/:id" element={<BlogPost />} />
        <Route path="/blog" element={<Blog />} />
        <Route path="*" element={<Navigate to="/" replace />} />
      </Routes>
    </div>
  );
}

export default App;

In this example, the * path captures all unmatched routes and redirects them to the Home page using the Navigate component.

Conditional Routing

Sometimes, you might want to conditionally render routes based on certain conditions, such as user authentication.

Implementing Conditional Routing

Let’s create a protected route that only authenticated users can access. If a user tries to access this route without being authenticated, they will be redirected to the Login page.

Step 1: Create a Protected Route Component

Create a ProtectedRoute.js file in the components directory.

ProtectedRoute.js

import { Navigate, useLocation } from 'react-router-dom';

function ProtectedRoute({ children }) {
  const isAuthenticated = true; // This should be a real authentication check
  const location = useLocation();

  if (!isAuthenticated) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  return children;
}

export default ProtectedRoute;
Step 2: Create the Login Component

Create a Login.js file in the pages directory.

Login.js

function Login() {
  return <h2>Login Page</h2>;
}

export default Login;
Step 3: Add the Protected Route

Modify the App.js file to include the protected route.

App.js

import React from 'react';
import { Routes, Route, Link, Navigate } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
import BlogPost from './pages/BlogPost';
import Blog from './pages/Blog';
import NotFound from './pages/NotFound';
import Login from './pages/Login';
import ProtectedRoute from './components/ProtectedRoute';

function App() {
  return (
    <div>
      <nav>
        <ul>
          <li>
            <Link to="/">Home</Link>
          </li>
          <li>
            <Link to="/about">About</Link>
          </li>
          <li>
            <Link to="/contact">Contact</Link>
          </li>
          <li>
            <Link to="/blog">Blog</Link>
          </li>
          <li>
            <Link to="/dashboard">Dashboard</Link>
          </li>
        </ul>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
        <Route path="/post/:id" element={<BlogPost />} />
        <Route path="/blog" element={<Blog />} />
        <Route
          path="/dashboard"
          element={
            <ProtectedRoute>
              <div>
                <h2>Dashboard</h2>
                <p>Welcome to your dashboard</p>
              </div>
            </ProtectedRoute>
          }
        />
        <Route path="/login" element={<Login />} />
        <Route path="*" element={<Navigate to="/" replace />} />
      </Routes>
    </div>
  );
}

export default App;

In this example, the Dashboard route is wrapped with the ProtectedRoute component. If the user is not authenticated, they will be redirected to the Login page.

Handling Authentication State

To make the conditional routing example more realistic, let’s add a simple authentication state management using React’s useState hook.

Step 1: Add Authentication State

Modify the App.js file to include authentication state.

App.js

import React, { useState } from 'react';
import { Routes, Route, Link, Navigate } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
import BlogPost from './pages/BlogPost';
import Blog from './pages/Blog';
import NotFound from './pages/NotFound';
import Login from './pages/Login';
import ProtectedRoute from './components/ProtectedRoute';

function App() {
  const [isAuthenticated, setIsAuthenticated] = useState(false);

  return (
    <div>
      <nav>
        <ul>
          <li>
            <Link to="/">Home</Link>
          </li>
          <li>
            <Link to="/about">About</Link>
          </li>
          <li>
            <Link to="/contact">Contact</Link>
          </li>
          <li>
            <Link to="/blog">Blog</Link>
          </li>
          <li>
            <Link to="/dashboard">Dashboard</Link>
          </li>
          <li>
            <Link to="/login">Login</Link>
          </li>
        </ul>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
        <Route path="/post/:id" element={<BlogPost />} />
        <Route path="/blog" element={<Blog />} />
        <Route
          path="/dashboard"
          element={
            <ProtectedRoute isAuthenticated={isAuthenticated}>
              <div>
                <h2>Dashboard</h2>
                <p>Welcome to your dashboard</p>
              </div>
            </ProtectedRoute>
          }
        />
        <Route path="/login" element={<Login setIsAuthenticated={setIsAuthenticated} />} />
        <Route path="*" element={<Navigate to="/" replace />} />
      </Routes>
    </div>
  );
}

export default App;
Step 2: Update the Login Component

Modify the Login.js file to handle the login process.

Login.js

import { useNavigate } from 'react-router-dom';

function Login({ setIsAuthenticated }) {
  const navigate = useNavigate();

  const handleLogin = () => {
    setIsAuthenticated(true);
    navigate('/');
  };

  return (
    <div>
      <h2>Login Page</h2>
      <button onClick={handleLogin}>Login</button>
    </div>
  );
}

export default Login;

Now, when you click the Login button, the isAuthenticated state is set to true, and you can access the Dashboard page.

Advanced Routing Concepts

React Router offers several advanced features that can help you build more dynamic and interactive applications.

Conditional Navigation

You can conditionally navigate users based on certain conditions using the useNavigate hook.

Example: Conditional Navigation

Let’s add a feature that redirects users to the Home page if they are already authenticated when they try to access the Login page.

Login.js

import { useNavigate, useLocation } from 'react-router-dom';

function Login({ setIsAuthenticated }) {
  const navigate = useNavigate();
  const location = useLocation();
  const from = location.state?.from?.pathname || '/';

  const isAlreadyAuthenticated = true; // This should be a real authentication check

  if (isAlreadyAuthenticated) {
    return <Navigate to={from} />;
  }

  const handleLogin = () => {
    setIsAuthenticated(true);
    navigate('/');
  };

  return (
    <div>
      <h2>Login Page</h2>
      <button onClick={handleLogin}>Login</button>
    </div>
  );
}

export default Login;

Route Guards

Route guards are used to enforce rules before rendering a component. You can use the ProtectedRoute component for this purpose.

Example: Route Guard

We already have a ProtectedRoute component in place. You can extend it to include more complex logic if needed.

ProtectedRoute.js

import { Navigate, useLocation } from 'react-router-dom';

function ProtectedRoute({ isAuthenticated, children }) {
  const location = useLocation();

  if (!isAuthenticated) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  return children;
}

export default ProtectedRoute;

Nested Routes for Nested Components

Nested routes allow you to create a hierarchical structure in your routes, where a parent route can render its children.

Example: Nested Routes

Let’s add a nested route to the About page.

About.js

import { Routes, Route, Link } from 'react-router-dom';
import Team from './Team';

function About() {
  return (
    <div>
      <h2>About Page</h2>
      <Link to="team">Our Team</Link>
      <Routes>
        <Route path="team" element={<Team />} />
      </Routes>
    </div>
  );
}

export default About;

Team.js

function Team() {
  return <h3>Team Page</h3>;
}

export default Team;

Now, when you navigate to /about/team, the Team component will be rendered nested inside the About component.

Query Parameters

Query parameters are used to pass additional information through the URL. They are useful for filtering and sorting data without changing the route.

Using Query Parameters

Let’s update the Blog component to handle query parameters.

Blog.js

import { useSearchParams } from 'react-router-dom';

function Blog() {
  const [searchParams, setSearchParams] = useSearchParams();

  const query = searchParams.get('query');

  const blogPosts = [
    { id: 1, title: 'Introduction to React', content: 'Learn the basics of React...' },
    { id: 2, title: 'Advanced React Techniques', content: 'Explore advanced React techniques...' },
    // Add more blog posts as needed
  ];

  const filteredPosts = query
    ? blogPosts.filter(post => post.title.toLowerCase().includes(query.toLowerCase()))
    : blogPosts;

  return (
    <div>
      <h2>Blog Posts</h2>
      <input
        type="text"
        value={query || ''}
        placeholder="Search..."
        onChange={e => setSearchParams({ query: e.target.value })}
      />
      <ul>
        {filteredPosts.map(post => (
          <li key={post.id}>
            <Link to={`/post/${post.id}`}>{post.title}</Link>
            <p>{post.content}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default Blog;

Redirecting with Query Parameters

React Router allows you to redirect users to a new route while preserving query parameters.

Preserving Query Parameters on Redirect

Let’s modify the Login component to redirect users to the Home page while preserving any query parameters.

Login.js

import { useNavigate, useLocation } from 'react-router-dom';

function Login({ setIsAuthenticated }) {
  const navigate = useNavigate();
  const location = useLocation();
  const from = location.state?.from?.pathname || '/';

  const isAlreadyAuthenticated = true; // This should be a real authentication check

  if (isAlreadyAuthenticated) {
    return <Navigate to={from} state={location.state} />;
  }

  const handleLogin = () => {
    setIsAuthenticated(true);
    navigate(from, { state: location.state });
  };

  return (
    <div>
      <h2>Login Page</h2>
      <button onClick={handleLogin}>Login</button>
    </div>
  );
}

export default Login;

Summary of Features

React Router is a versatile tool that provides a wide range of features for managing routing in React applications. Here’s a quick summary of the features we covered:

FeatureDescription
Declarative routingRoutes are declared in a way that is closely aligned with the component tree.
Nested routingHandles nested routes easily, allowing you to create complex and hierarchical structures.
Dynamic routingUse dynamic segments in routes to match different paths.
URL parametersCapture and use URL parameters in components.
RedirectsManage redirects programmatically.
Query parametersPass and extract query parameters from the URL.
Conditional routingRender routes conditionally based on certain conditions.
Nested componentsUse nested routes to render components within other components.
Authentication checksEnforce authentication checks before rendering components.

Best Practices

Here are some best practices to follow when using React Router in your React applications:

  • Keep routes organized: Use nesting and grouping to keep your routes organized.
  • Use dynamic routes: Use dynamic routes to avoid hardcoding paths.
  • Protect sensitive routes: Use protected routes to secure sensitive components.
  • Preserve query parameters: Use the state parameter to preserve query parameters during redirects.
  • Lazy loading: Use lazy loading to load components only when they are needed.

Troubleshooting

If you encounter issues while setting up React Router, here are some common solutions:

  • Ensure you are using the correct version: Make sure you are using the latest version of react-router-dom.
  • Check your routes: Verify that your routes are correctly defined and that there are no typos.
  • Use element prop: Use the element prop instead of component prop for defining routes.
  • Check component imports: Ensure that all components are correctly imported and exported.
  • Debug the browser console: Use the browser console to debug any errors or warnings.

Conclusion

React Router is an essential tool for building multi-page applications in React. It simplifies routing, navigation, and data handling, making it easier to create complex and interactive applications. By following the steps and examples in this blog, you should be well-equipped to start using React Router in your projects.

Happy coding! Check out the project on GitHub for a complete implementation.