Single Page Applications (SPAs) are web apps that dynamically update a single page without requiring full page reloads. This results in a smooth user experience similar to a native mobile app.
React is a popular JavaScript library for building user interfaces and is commonly used for building SPAs. To handle navigation in a React SPA, we need a routing solution.
React Router is the most widely used routing library for React. It allows us to build a single-page app with multiple views and navigational components while keeping the UI in sync with the URL.
In this comprehensive beginner’s guide, we will cover the following:
- What is client-side routing, and how does React Router implement it
- Installing and setting up React Router in a React app
- Creating routes to define app navigation
- Matching route parameters to render dynamic components
- Implementing nested routing to organize related screens
- Building navigation with React Router links
- Navigating programmatically with React Router hooks
- Redirecting and preventing navigation
- Animating transitions between routes
- Lazy loading routes to optimize performance
- Protecting routes with authentication
- Handling 404 pages and route not found errors
- React Router configuration and customization
Follow along as we explore each topic in detail and build a dynamic React app with routing from scratch!
What is Client-Side Routing?
Traditionally, routing was handled on the server-side. This required making a round-trip to the server anytime you needed to navigate to a new page.
With client-side routing, navigation happens on the front-end without requiring a server request. The router manipulates the browser history and updates the UI in response to URL changes.
Some benefits of client-side routing:
- Faster page transitions without reloading
- A smoother user experience similar to a mobile app
- Easier to implement features like transitions and animations
React Router embraces client-side routing and gives us routing capabilities directly in our React app.
How React Router Works
React Router implements client-side routing by keeping your UI in sync with the browser URL.
It uses the browser history API to intercept URL changes and navigation events. When the URL changes, React Router will match a route, render the appropriate screen, and pass down router parameters to that screen.
The components of React Router include:
- <BrowserRouter> – The main router component
- <Route> – Defines our routes and renders components
- <Link>/<NavLink> – Link components for navigation
- useParams, useNavigate, etc – Router hooks
By wiring up <Route>’s and <Link>’s, we can build a complete client-side routing solution for our React Single Page App.
Set Up a React App
First, we need a React app to work with. Make sure you have Node.js installed, then run:
npx create-react-app my-app
cd my-app
This will scaffold a new React app using Create React App. Alternatively, you can use your preferred method to set up React.
When you create a new React app, it sets up a development server using webpack that bundles your app and automatically refreshes when you make changes.
The start script in your package.json runs this development server:
// package.json
"scripts": {
 "start": "react-scripts start"Â
}
So after installing dependencies with npm install, you can run:
npm start
This will start the React dev server and open your app in the browser at http://localhost:3000.
Some key points on npm start:
- It compiles your React code and bundles it ready for the browser
- It watches for changes and automatically reloads the browser
- It sets up Hot Module Replacement for instant refresh without losing state
- It configures Webpack and Babel behind the scenes
- The dev server supports hot reloading of CSS/modules without refreshing
npm start handles all the complex config required to run and develop a React app – it builds the app, runs the dev server, and refreshes as you code!
Once you’re ready to deploy, you would then use npm run build to build the optimized production bundle output. But npm start is all you need to build your React app for development.
Installing React Router
To install React Router in your project:
npm install react-router-dom
This will install the react-router-dom package containing the routing components we need.
Alternatively, use Yarn:
yarn add react-router-dom
Now, let’s set up a React Router in a React app.
Set Up React Router in the App
In your App.js, import the core routing components:
import { BrowserRouter, Routes, Route } from 'react-router-dom';
- <BrowserRouter> – The router wrapper component
- <Routes> – Holds all the <Route>’s
- <Route> – Defines the routes
Wrap the app in <BrowserRouter>:
function App() {
 return (
  <BrowserRouter>
   <h1>React Router App</h1>
  </BrowserRouter>
 );
}
This sets up the basic React Router structure.
Defining Routes
The <Route> component defines our routes and renders UI when a location matches.
It takes two key props:
- path – The route path URL
- element – The JSX element to render
<Route path="/about" element={<About />} />
This will match the /about path and render the <About> component.
We can define multiple routes:
<Routes>
 <Route path="/" element={<Home />} />
 <Route path="/about" element={<About />} />
 <Route path="/contact" element={<Contact />} />
</Routes>
Now, different components will render on each route!
Exact Path Matching
By default, <Route> will match any path that starts with its path.
To make it match exactly, use the exact prop:
<Route exact path="/" element={<Home />} />
This will only render <Home> on the exact / URL.
Linking Between Routes
To link between routes, use the <Link> component:
<Link to="/about">About</Link>
This will render an anchor tag that navigates to /about when clicked.
We should use <Link> instead of anchor tags to enable client-side routing.
Matching Route Parameters
To match a dynamic segment in the URL, we can use the: colon syntax:
<Route path="/users/:id" element={<User />} />
This will match /users/1, /users/2, etc.
The :id parameter can be accessed inside <User> with the useParams hook:
let { id } = useParams();
We can now render user data dynamically based on the ID!
Nested Routing
We can render components as children of other routes to achieve nested routing:
<Route path="/users" element={<Users />}>
 <Route path=":id" element={<User />} />
</Route>
This will match nested paths like:
- /users – Renders parent <Users>
- /users/1 – Renders child <User>
Great for related screens!
Using Outlet for Nested Routes
The parent route element can render the child route using the <Outlet> component:
<Route path="/users" element={<Users />}>
 {/* Users.js */}
 <Outlet />
</Route>
This will render the child route element in the <Outlet> position.
Index Routes
We can define an “index” child route that renders when no child path is defined:
<Route path="/users" element={<Users />}>
 <RouteÂ
  index
  element={<FeaturedUsers />}Â
 />
 <RouteÂ
  path=":id"Â
  element={<User />}Â
 />
</Route>
Now /users will render the <FeaturedUsers> index route.
Linking with Active Styling
The <NavLink> component adds styling when active:
<NavLinkÂ
 to="/about"
 style={({ isActive }) => (isActive ? { color: 'red' } : undefined)}
>
 About
</NavLink>
This will turn red when on the /about page.
Navigating Programmatically
Sometimes, we want to navigate on some event like a form submission.
The useNavigate hook returns a navigate function to navigate programmatically:
import { useNavigate } from 'react-router-dom';
function Login() {
 let navigate = useNavigate();Â
 function handleLogin() {
  // Login logic...
  navigate('/dashboard');
 }
 return (
  <button onClick={handleLogin}>Login</button>
 );
}
Calling navigate(‘/dashboard’) will navigate there.
The navigate function can also pass state:
navigate('/dashboard', { user:
res.data
.user })
And replace the current entry in history:
navigate('/dashboard', { replace: true })
Using Params and State
We can access route params, state, location etc. using hooks:
import { useParams, useLocation } from 'react-router-dom'
function Dashboard() {
 // Get route params
 let { userId }  = useParams();
 // Get route state
 let { state } = useLocation();
 return (
  <div>
   <h1>Dashboard</h1>
   <p>User ID: {userId}</p>
   <p>State: {JSON.stringify(state)}</p>Â
  </div>
 );
}
This provides values from the matched route.
Redirecting
The <Navigate> component can redirect to another route:
<Route path="/login" element={<Login />} />
<RouteÂ
 path="/dashboard"
 element={
  loggedIn ? <Dashboard /> : <Navigate to="/login" />
 }
/>
If not logged in, this will redirect to /login.
We can also use the replace prop to replace the history entry.
Handling 404 Pages
To show a 404 page on unknown routes, use a * catch-all route:
<Route path="*" element={<NotFound />} />
This will match any unknown route. We can also redirect to 404:
<Route path="*" element={<Navigate to="/" replace />} />
Now, unknown routes redirect to the home page.
Lazy Loading Routes
We can optimize our app by lazy loading route components only when needed:
const Users = lazy(() => import('./Users'));
function App() {
 return (
  <Routes>
   <RouteÂ
    path="/users"
    element={
     <Suspense fallback={<Spinner />}>
      <Users />
     </Suspense>
    }
   />
  </Routes>
 );
}
Users will now load dynamically with React.lazy().
Animating Route Transitions
To animate transitions between routes, we can use:
- CSS transitions based on location
- React transition libraries like Framer Motion
- Wrap routes in animated components
This enables smooth animated transitions as users navigate!
Scroll Restoration
By default, the scroll position resets on navigation.
To restore scroll on backward/forward navigation:
<BrowserRouter>
 <ScrollRestoration />
</BrowserRouter>
<ScrollRestoration> will maintain the scroll position.
Router Configuration
We can customize React Router with a <Router> component:
<Router
 basename="/app"
 history={createBrowserHistory()}
>
</Router>
Allows configuring:
- basename – Base URL for all locations
- history – Custom history implementation
And more!
Conclusion
In this guide, we’ve explored the core concepts and APIs that power routing in React with React Router, including:
- Using <Route> and <Link> to define routes and navigate
- Dynamic route matching with parameters and nested routes
- Programmatic navigation with hooks like useNavigate
- Redirecting, animating, lazy loading, and more
React Router provides a robust routing solution for React single-page apps right out of the box. With declarative routes, dynamic matching, and location tracking, you can build complex navigational experiences and keep your UI in sync with URLs.
There is still much more to cover with React Router, including protected routes, custom hooks, server-side rendering, and more.
I hope this guide provides a solid foundation for getting started with routing in your React apps!
If you want to become a programmer, then this article is your guide to becoming one. It explains everything from start to finish on how to build technical skills and what to do.
If you find this post exciting, find more exciting posts on Learnhub Blog; we write everything tech from Cloud computing to Frontend Dev, Cybersecurity, AI, and Blockchain.