Guide to Building a Blog Site Using React Router v7 (Library Usage)

  • vite
    vite
Published on 2025/01/20

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.

Image from Gyazo

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.

Image from Gyazo

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.

https://reactrouter.com/home

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

Image from Gyazo

Start React with the following commands:

npm install
npm run dev

If you can access http://localhost:5173/, the setup is complete.

Image from Gyazo

There is also an article that covers the basics of Vite.

Please refer to it together with this one.

https://shinagawa-web.com/en/blogs/react-vite-setup-guide-for-fast-development

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.

src/main.tsx
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.

Image from Gyazo

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:

src/const/posts.ts
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.

src/pages/Home.tsx
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.

src/App.tsx
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.

Image from Gyazo

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.

src/const/posts.ts
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.

src/pages/Post.tsx
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.

src/App.tsx
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.

src/page/Home.tsx
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.”

Image from Gyazo

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.

src/page/Post.tsx
- 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).

src/pages/Account.tsx
const Account = () => {
  return <h2>Account</h2>
}

export default Account
src/pages/Settings.tsx
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.

Image from Gyazo

Image from Gyazo

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.

src/pages/MyPageLayout.tsx
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.

App.tsx
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.

Image from Gyazo

Image from Gyazo

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.

src/pages/MyPageNavigation.tsx
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.

src/pages/styles.css
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.

src/pages/MyPageLayout.tsx
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.

Home.tsx
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.

Image from Gyazo

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.

Image from Gyazo

The same applies when you access “Account”.

Image from Gyazo

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.

Xでシェア
Facebookでシェア
LinkedInでシェア

Questions about this article 📝

If you have any questions or feedback about the content, please feel free to contact us.
Go to inquiry form