React Router is one of the most popular libraries for handling navigation in React applications. It allows you to create single-page applications (SPA, Single Page Application).
In traditional web development (Multi-Page Application), every time we clicked a link, the browser requested a new HTML file from the server. The screen would go blank for an instant and everything would reload.
In React, we aim to create a SPA (Single Page Application). In an SPA, there is only one HTML file (index.html).
When the user “navigates”, we don’t request anything from the server. JavaScript simply intercepts the URL and instantly changes the content on the screen. No white flashes or full reloads.
To achieve this, we need the standard: React Router 👇.
Installation
React does not come with a built-in router (unlike Angular). The de facto official library is react-router.
To install it, go to your project’s terminal and run:
npm install react-router
In older projects, you’ll often see react-router-dom. In current versions of React Router, the recommended installation for an SPA with Vite is react-router, and from there we import BrowserRouter, Routes, Route, Link, etc.
Configuring the main router
For the router to control our URL and browser history, it needs to “wrap” our entire application. The ideal place to do this is our entry point: src/main.jsx.
Let’s import the BrowserRouter component and use it as the parent of our <App />.
// src/main.jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router' // <--- 1. Import
import App from './App.jsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
{/* 2. Wrap the App with the Router */}
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>,
)
From now on, any component inside <App /> (i.e., the entire application) has navigation capabilities.
Defining our first routes
Now let’s go to src/App.jsx. This is where we will define the “map” of our website. We need to import two key pieces:
<Routes>: Acts as a giant conditional (aswitch). It looks among its children for the route that matches the current URL and renders only that one.<Route>: Defines a rule: “When the URL is X, render component Y”.
Let’s clean up our App.jsx and create a basic structure:
// src/App.jsx
import { Routes, Route } from 'react-router';
// Simple components for testing (we'll move them to their own files later)
const Home = () => <h1>Home Page 🏠</h1>;
const About = () => <h1>About Us 👥</h1>;
const Contact = () => <h1>Contact 📧</h1>;
function App() {
return (
<div className="App">
<header>My SPA Website</header>
{/* Changing content area */}
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
<footer>Fixed footer</footer>
</div>
);
}
export default App;
We have defined that:
- If the URL is
http://localhost:5173/,<Home />is displayed. - If the URL is
http://localhost:5173/about,<About />is displayed. - The
<header>and<footer>are outside the<Routes>, so they remain fixed and visible throughout navigation. Only the central part changes.
The 404 Route (Not Found)
What happens if the user types a URL that doesn’t exist, like /something-else? By default, React Router won’t render anything inside <Routes>.
To display a custom 404 error page, we use the wildcard *.
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
{/* Catch-all / 404 route */}
<Route path="*" element={<h1>404 - Page not found 😱</h1>} />
</Routes>
The * path means “any route that didn’t match the ones above”. It is important to put it at the end.
