This project (07reactRouter) is a hands-on implementation of client-side routing in a modern React application. It uses React 19, Vite, Tailwind CSS, and React Router DOM v7 to demonstrate how to build multi-page user experiences efficiently without full page refreshes.
- Server-Side Routing: When a user clicks a link, the browser requests a new HTML document from the server. This causes the entire page to reload, leading to slower transitions and a flashing blank screen.
- Client-Side Routing: The app intercepts page requests. Instead of fetching a new HTML page from the server, React Router DOM dynamically updates the URL in the address bar and re-renders only the components that need to change. This results in a fast, seamless single-page application (SPA) experience.
In standard HTML, we use the anchor tag <a> for navigation:
<!-- Avoid this in React! -->
<a href="/about">About Us</a>Problem: Clicking this tag forces a full browser reload, discarding the current React state and making the page load slower.
Solution: React Router DOM provides <Link> and <NavLink> components. They update the URL using the browser's History API without triggering a full page refresh:
import { Link } from 'react-router-dom';
<Link to="/about">About Us</Link>Our project uses the Data Router approach (createBrowserRouter & RouterProvider), which is the recommended method in modern React Router applications.
graph TD
main[src/main.jsx] -->|Imports & Renders| provider[RouterProvider]
provider -->|Loads Config| routes[src/AppRoutes.jsx]
routes -->|Defines Layout| App[src/App.jsx]
App -->|Renders Static| Header[Header.jsx]
App -->|Renders Static| Footer[Footer.jsx]
App -->|Renders Dynamic Children| Outlet[Outlet]
Outlet -->|Path: ''| Home[Home.jsx]
Outlet -->|Path: 'about'| About[About.jsx]
Outlet -->|Path: 'contact'| Contact[contant.jsx]
Outlet -->|Path: 'user/:userid'| User[User.jsx]
Outlet -->|Path: 'github'| Github[Github.jsx]
In src/AppRoutes.jsx, we define our routes array using createBrowserRouter. This is where we declare layouts, children routes, path parameters, and loaders.
π File: src/AppRoutes.jsx
import { createBrowserRouter } from "react-router-dom";
import Home from "./components/Home/Home.jsx";
import About from "./components/About/About.jsx";
import Contact from "./components/Contact/contant.jsx";
import User from "./components/User/User.jsx";
import Github from "./components/Github/Github.jsx";
import { githubInfoLoader } from "./components/loaders/githubLoader.js";
import App from "./App.jsx";
export const router = createBrowserRouter([
{
path: "/",
element: <App />, // Root layout component
children: [
{ path: "", element: <Home /> },
{ path: "about", element: <About /> },
{ path: "contact", element: <Contact /> },
{ path: "user/:userid", element: <User /> }, // Dynamic route parameter
{ path: "github", element: <Github />, loader: githubInfoLoader }, // Router Loader
]
}
]);We wrap our application in RouterProvider at the entry point of the app and inject the configured router.
π File: src/main.jsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import { RouterProvider } from 'react-router-dom'
import { router } from './AppRoutes'
createRoot(document.getElementById('root')).render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>,
)To prevent code duplication, we use a single layout component (App.jsx) containing headers and footers that remain static. The <Outlet /> component tells React Router where to render the matching nested child component.
π File: src/App.jsx
import Header from "./components/Header/Header.jsx";
import Footer from "./components/Footer/Footer.jsx";
import { Outlet } from "react-router-dom";
export default function App() {
return (
<>
<Header />
<Outlet /> {/* Child components (Home, About, Contact, etc.) will render here */}
<Footer />
</>
);
}<NavLink> is a special version of <Link> that knows whether or not it is active. This allows us to style the active link differently.
We pass a function to the className attribute. This function receives an object with an isActive property, which we use to dynamically apply active styling classes:
π File Excerpt: src/components/Header/Header.jsx
<NavLink
to="/"
className={({ isActive }) =>
`block py-2 pr-4 pl-3 duration-200 ${
isActive ? "text-orange-700" : "text-gray-700"
} border-b border-gray-100 hover:bg-gray-50 lg:hover:bg-transparent lg:border-0 hover:text-orange-700 lg:p-0`
}
>
Home
</NavLink>- If the user is on the
/route,isActiveistrueand the text turns orange (text-orange-700). - Otherwise, the text remains gray (
text-gray-700).
Dynamic routing allows us to match paths like /user/123, /user/456, or /user/sam using wildcards.
{
path: "user/:userid",
element: <User />
}:useridacts as a dynamic placeholder variable.
We use the useParams hook inside the component to extract the dynamic value from the URL.
π File: src/components/User/User.jsx
import { useParams } from 'react-router-dom'
function User() {
// Extracting 'userid' param from URL
const { userid } = useParams();
return (
<div className='bg-gray-600 text-white text-center text-5xl italic p-4 mt-15'>
User: {userid}
</div>
)
}
export default User;Traditionally in React, you fetch data using a useEffect hook after the component renders:
- Component mounts (shows loading state/empty screen).
useEffectfires.- Fetch request completes.
- Component re-renders with new data.
This is known as a Render-then-Fetch waterfall, which can feel slow.
React Router DOM loaders allow you to define a loader function that fetches data during the route transition, before the component is even rendered.
Create an asynchronous function to fetch data and return the response.
π File: src/components/loaders/githubLoader.js
export const githubInfoLoader = async () => {
const response = await fetch(
"https://api.github.com/users/DeveloperSRGonline"
);
return response.json(); // Returns a promise which resolves to the data
};Attach the loader function to the /github route configuration.
π File Excerpt: src/AppRoutes.jsx
{
path: "github",
element: <Github />,
loader: githubInfoLoader // Fetches data as soon as route transition starts
}Inside the component, retrieve the preloaded data using the useLoaderData hook.
π File: src/components/Github/Github.jsx
import { useLoaderData } from "react-router-dom";
const Github = () => {
// Data is already fetched and resolved here automatically by the router
const data = useLoaderData();
return (
<div className="text-center m-4 text-white flex flex-col items-center justify-center gap-10 p-4 text-3xl mt-15">
<img
className="rounded-full border-4 gradient border-[#DB5746]"
src={data?.avatar_url}
alt="Git picture"
width={200}
/>
<h1 className="font-bold text-6xl text-[#DB5746]">{data?.login}</h1>
<h1 className="font-bold text-3xl text-black">
<span className="font-normal font-sans">Followers:</span>{" "}
<span className="text-[#DB5746]">{data?.followers}</span>
</h1>
</div>
);
};
export default Github;| API Name | Type | Purpose | Example Usage in Project |
|---|---|---|---|
createBrowserRouter |
Function | Configures routes and nested layout hierarchy. | AppRoutes.jsx |
RouterProvider |
Component | Context provider that renders the router configuration. | main.jsx |
<Outlet /> |
Component | A placeholder component rendering the matched child route. | App.jsx |
<Link> |
Component | Navigates between views without reloading the page. | Header.jsx |
<NavLink> |
Component | A navigation link that applies active styling state. | Header.jsx |
useParams |
Hook | Extracts dynamic parameters from the active URL path. | User.jsx |
useLoaderData |
Hook | Reads preloaded loader data before the component renders. | Github.jsx |