Guide to Building a Blog Site Using React Router v7 (Library Usage)
Introduction
In this article, we’ll look at how to build a blog site using React Router v7. React Router is a library that lets you configure routing in React applications easily and flexibly, making it ideal for multi-page applications such as blog sites. Through this article, you’ll learn the basics of React Router v7, including dynamic page transitions, nested routes, and setting up individual article pages.
We’ll also introduce practical techniques to take advantage of the new features in React Router v7 to achieve smooth and intuitive navigation. Read through to the end and apply what you learn to your own React applications.
Goal of this article
We’ll build something modeled after a blog site.
We’ll display a list of blog posts, and when you click a title, the blog content will be shown. If a user accesses a non-existent blog article, they will be automatically redirected to the top page.
We’ll also implement layout behavior such as changing the color of the navigation link for the current page to make it clear where the user is, and changing the background color per page.
What is React Router?
React Router is a routing library for React applications. Routing is the mechanism that controls which component is displayed when a user accesses a specific URL.
Main features
-
Declarative routing
With React Router, you define routes using JSX, which allows you to write intuitive and highly readable code. -
Dynamic routes
You can use dynamic segments and optional segments to flexibly display pages according to user requests. -
Nested routes
By defining child routes inside parent routes, you can design complex UIs in a simple way. -
Layout routes
You can define reusable layouts and efficiently manage UI that is shared across pages. -
Splats (wildcards)
You can configure routes that catch and handle unmatched parts of a path. -
Support for the latest React features
React Router v7 supports the latest versions of React and works with Suspense and lazy-loaded components.
Why use it?
- Essential when building SPAs (Single Page Applications). It provides a smooth user experience by switching components on page transitions without reloading the entire page.
- Even in large-scale applications, route management is simple and highly extensible.
- A clear URL structure contributes to better SEO and accessibility.
When is it useful?
- When managing multiple pages or categories, such as in blogs or e-commerce sites.
- When each page has a different layout, such as in dashboards.
- When dynamic page generation is required (e.g., paths like /users/:id).
What does “library usage” mean?
From v7, React Router can be used either to its fullest as a React framework, or minimally as a library that fits your own architecture.
As with previous versions (v6 and earlier), you can use it as a simple, declarative routing library. It matches URL and component pairs, provides access to URL data, and enables navigation within the app.
Users who have been using v6 will likely continue to use React Router as a library after upgrading.
The features introduced in this article are based on this “library usage” mode.
While the main focus is routing control, it also provides many essential features for building a blog site.
What does “framework usage” mean?
From v7, React Router can also be used as a React framework.
Specifically, it provides features such as:
- Integration with the Vite bundler and development server
- Hot Module Replacement
- Code splitting
- Type-safe file-system-based or config-based routing
- Type-safe data loading
- Type-safe actions
- Automatic revalidation of page data after actions
- SSR, SPA, and static rendering strategies
- Pending states and optimistic UI
- Deployment adapters
Setup
This time we’ll start a React app using Vite and then configure React Router v7 in it.
mkdir react-router-v7-tutorial
cd react-router-v7-tutorial
npx create-vite@latest .
We’ll choose the following options:
- Select a framework:
React - Select a variant:
TypeScript + SWC
Start React with the following commands:
npm install
npm run dev
If you can access http://localhost:5173/, the setup is complete.
There is also an article that covers the basics of Vite.
Please refer to it together with this one.
Installing React Router v7
Install React Router v7 with the following command:
npm i react-router
In the src/main.tsx file, wrap the application with <BrowserRouter> when rendering.
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
- import './index.css'
+ import { BrowserRouter } from "react-router";
import App from './App.tsx'
createRoot(document.getElementById('root')!).render(
<StrictMode>
+ <BrowserRouter>
<App />
+ </BrowserRouter>
</StrictMode>,
)
Now React Router v7 is ready to use.
We removed the CSS, so the design of the top page has changed slightly, but you don’t need to worry about that.
Routing
We’ll build the site as a blog and introduce the routing features of React Router v7 along the way.
Setting up routes
We’ll use / as the blog’s top page and display a list of articles.
First, let’s create a list of blog posts.
Preparing a backend tool such as a database or WordPress would be overkill here, so we’ll define the blog post list as follows:
export const posts = [
{ id: 1, title: 'Setting up React Router 7' },
{ id: 2, title: 'State Management in React' },
{ id: 3, title: 'Component Design Best Practices' },
]
Next, create a screen that reads and displays this list.
import { posts } from "../const/posts"
function Home () {
return (
<div>
- <h1>Home</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>
{post.title}
</li>
))}
</ul>
</div>
)
}
export default Home
Once this is set up, configure the route.
import { Routes, Route } from 'react-router'
import Home from './pages/Home'
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
</Routes>
);
}
export default App
As you can probably guess from looking at it, this configuration means “when there is access to /, display the Home component.”
When you access http://localhost:5173/, the blog list will be displayed.
Dynamic segments and route parameters
Next, we’ll configure the settings needed to view individual blog posts.
The idea is that when you click a blog title on the home page, you’ll be taken to that blog’s individual article page.
We’ve added a description to the blog list.
We also created a function getPostById that retrieves the corresponding blog information when given an id.
export const posts = [
{ id: 1, title: 'Setting up React Router 7', description: 'Introduction to setup and various features' },
{ id: 2, title: 'State Management in React', description: 'Introduction to useState, useContext, and more' },
{ id: 3, title: 'Component Design Best Practices', description: 'Introduction to directory structure and more' },
]
export const getPostById = (id: number) => {
return posts.find(post => post.id === id);
}
Next, create the Post component that displays an individual blog article.
import { useParams } from 'react-router';
import { getPostById } from '../const/posts';
function Post () {
const { id } = useParams()
const post = getPostById(Number(id))
if (!post) {
return (
<div>
Post not found
</div>
)
}
return (
<div>
<h1>{post.title}</h1>
{post.description}
</div>
);
};
export default Post;
Here’s a detailed explanation of the code:
import { useParams } from 'react-router';
const { id } = useParams()
This is called a route parameter, and it lets you retrieve values included in the URL.
This id can be obtained with a configuration like the following:
<Route path="/post/:id" element={<Post />} />
By setting :id, you can retrieve it as id.
For example, if you access the URL http://localhost:5173/post/1, then id = 1.
const post = getPostById(Number(id))
Next, we use the function we created earlier to get the blog’s detailed information.
if (!post) {
return (
<div>
Post not found
</div>
)
}
return (
<div>
<h1>{post.title}</h1>
{post.description}
</div>
);
Finally, we display the retrieved blog information.
If access comes in with a non-existent id, the individual blog article cannot be displayed, so we also implement rendering that takes that case into account.
This completes the creation of the Post component that displays individual blog articles.
Next, configure the route.
import { Routes, Route } from 'react-router'
import Home from './pages/Home'
+ import Post from './pages/Post';
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
+ <Route path="/post/:id" element={<Post />} />
</Routes>
);
}
export default App
We’ve now added the configuration introduced earlier for route parameters.
With this, you can display individual blog articles using URLs like http://localhost:5173/post/1.
Finally, configure navigation from the blog list.
import { posts } from "../const/posts"
+ import { Link } from "react-router"
function Home () {
return (
<div>
<h1>Home</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>
- {post.title}
+ <Link to={`/post/${post.id}`}>{post.title}</Link>
</li>
))}
</ul>
</div>
)
}
export default Home
By using Link from React Router, you can navigate to the URL specified in to when the link is clicked.
Now that everything is set up, let’s check whether we can navigate from the home screen.
When you click a blog title displayed on the home page, the individual blog article is shown.
Then, try forcibly accessing a non-existent blog ID.
You’ll see the message “Post not found.”
Navigation
By using a hook called useNavigate, you can perform page transitions without user interaction.
Previously, when we forcibly accessed a non-existent blog ID, we displayed the message "Post not found.".
Leaving it like that is not very user-friendly, so let’s automatically send the user back to the top page.
- import { useParams } from 'react-router';
+ import { useParams, useNavigate } from 'react-router';
import { getPostById } from '../const/posts';
function Post () {
const { id } = useParams()
+ const navigate = useNavigate()
const post = getPostById(Number(id))
if (!post) {
+ setTimeout(() => {
+ navigate('/')
+ }, 3000)
return (
<div>
- Post not found.
+ Post not found. Redirecting to top page...
</div>
)
}
return (
<div>
<h1>{post.title}</h1>
{post.description}
</div>
);
};
export default Post;
For the case where a non-existent blog ID is accessed, we configured navigate('/') (the command to navigate to the top page) to run after 3 seconds.
The message will be displayed for 3 seconds, and then the user will automatically be returned to the top page.
Nested routes and layout
Now we’ll create a My Page section for the blog site.
On My Page, we’ll create a screen to view account information and a screen to change settings.
First, create the two screens.
They’re simple screens that only display a title (this is not the main focus of this article).
const Account = () => {
return <h2>Account</h2>
}
export default Account
const Settings = () => {
return <h2>Settings</h2>
}
export default Settings
Next, configure routing for the screens you created.
The <Route> components are nested.
With this configuration, you can access the screens via the following URLs:
Account screen: http://localhost:5173/mypage/account
Settings screen: http://localhost:5173/mypage/settings
If you actually access them, you should see each screen displayed.
Layout
These two screens operate within a single frame called My Page.
We’ll create a header element and implement the ability to switch between screens.
To do that, we’ll first create a component that will serve as the common layout.
import { Outlet } from "react-router";
function MyPageLayout() {
const containerStyle: React.CSSProperties = {
backgroundColor: "#f0f0f0",
padding: "20px",
minHeight: "100vh",
}
return (
<div style={containerStyle}>
<h1>My Page</h1>
<Outlet />
</div>
);
}
export default MyPageLayout
Outlet is a React Router component that acts as a placeholder for displaying the content of nested routes.
Concretely, you define common layout or components (e.g., header or sidebar) in the parent route, and use Outlet to embed the child route’s content within it.
In this case, we display "My Page" in the header and set the background to gray.
import { Routes, Route } from 'react-router'
import Home from './pages/Home'
import Post from './pages/Post';
import Account from './pages/Account';
import Settings from './pages/Settings';
+ import MyPageLayout from './pages/MyPageLayout';
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/post/:id" element={<Post />} />
- <Route path="/mypage">
+ <Route path="/mypage" element={<MyPageLayout />}>
<Route path="account" element={<Account />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
);
}
export default App
We set a common layout on the parent route of the nested routes.
Now, access the following URLs again:
Account screen: http://localhost:5173/mypage/account
Settings screen: http://localhost:5173/mypage/settings
If you access them, you should see the header and gray background on both.
Navigation
Up to now, we’ve been accessing the account and settings screens by directly entering the URLs, but now we’ll configure navigation so we can move between them from the UI.
First, create the navigation.
import { NavLink } from "react-router";
import './styles.css';
export function MyPageNavigation() {
return (
<nav>
<NavLink to="/" end>Home</NavLink>
<NavLink to="/mypage/settings">Settings</NavLink>
<NavLink to="/mypage/account">Account</NavLink>
</nav>
);
}
NavLink is for navigation links that need to render an active state.
Specifically, when the corresponding URL is being accessed, the active class is applied.
You can then use CSS to change the text color or add a background color for active.
nav {
display: flex;
gap: 16px;
}
a.active {
color: red;
}
Here, we set the text color to red for active.
Now that the navigation is created, we’ll apply it to the screens and layout.
First, apply it to the My Page layout.
import { Outlet } from "react-router";
+ import { MyPageNavigation } from "./MyPageNavigation";
function MyPageLayout() {
const containerStyle: React.CSSProperties = {
backgroundColor: "#f0f0f0",
padding: "20px",
minHeight: "100vh",
}
return (
<div style={containerStyle}>
- <h1>My Page</h1>
+ <MyPageNavigation />
<Outlet />
</div>
);
}
export default MyPageLayout
We replaced the header text "My Page" with the navigation.
Next, add the navigation to the home screen as well.
import { posts } from "../const/posts"
import { Link } from "react-router"
+ import { MyPageNavigation } from "./MyPageNavigation"
function Home () {
return (
<div>
- <h1>Home</h1>
+ <MyPageNavigation />
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link to={`/post/${post.id}`}>{post.title}</Link>
</li>
))}
</ul>
</div>
)
}
export default Home
That’s all for the configuration.
Let’s access the app and check.
When you access the home screen, the navigation is displayed.
"Home" is highlighted in red, making it easy to see where you currently are.
Next, access the settings screen.
This time, the text color of “Settings” turns red.
Also, since this is the My Page layout, the background is gray.
The same applies when you access “Account”.
Conclusion
In this article, we learned how to build routing for a blog site using React Router v7. You should now have a better understanding of how to handle dynamic URL parameters and configure nested routes.
By leveraging the flexibility of React Router v7, you can design routing that best fits your application and provide a comfortable experience for your users. Based on what you’ve learned here, try implementing more complex routing and tackling performance optimization as well.
Thank you for reading.
Questions about this article 📝
If you have any questions or feedback about the content, please feel free to contact us.Go to inquiry form
Related Articles
React Router v7 (Framework Usage) Practical Guide: Learning Server-Side and Client-Side Rendering by Building a Blog Site
2025/01/23React Router v7 (Framework Usage) Practical Guide: Learn the Latest Routing by Building a Blog Site
2025/01/23Vite Introduction to Accelerate React Development: A Fast and Flexible Project Setup Guide
2025/01/17Practical Schema-Driven Development: Efficient API Design with React × Express × GraphQL
2024/10/12Frontend Test Automation Strategy: Optimizing Unit, E2E, and API Tests with Jest, Playwright, and MSW
2024/01/21













