React: Getting Started with React Router

Last updated: 2026-08-26

Tom is developing an e-commerce admin panel. Initially, he used useState to switch between "pages"—one state variável each for the home page, product list, and order details. As the number of pages grew to 10, the state logic became a tangled mess, and the URL bar remained stuck at /, making it impossible to directly share a link to a specific page. He realized: he was missing a professional routing solution.


1. What You'll Learn



2. Conceptual Diagrams

100%
flowchart LR
    A[BrowserRouter<br/>Routing Container] --> B[Routes<br/>Routing Table]
    B --> C["Route path='/'<br/>→ Home"]
    B --> D["Route path='/products'<br/>→ ProductList"]
    B --> E["Route path='/products/:id'<br/>→ ProductDetail"]
    B --> F["Route path='*'<br/>→ NotFound"]
    C --> G[Rendering Component]
    D --> G
    E --> G
    F --> G
    style A fill:#e1f5fe,stroke:#0288d1
    style B fill:#fff3e0,stroke:#f57c00
    style G fill:#e8f5e9,stroke:#388e3c

A user visits a different URL → BrowserRouter captures it → Routes match the most appropriate route → The corresponding component is rendered.



3. A Real-Life Scenario

Tom's admin panel needs three main pages: Dashboard, Product Management, and System Settings. In addition, the Product Management section includes two subpages: Product List and Product Details. He wants each page to have a unique URL so that users can navigate using their browser's forward and back buttons.

(1) Selecting a Routing Mode

React Router offers two routing modes, which differ fundamentally in how they handle URLs:

Pattern URL Example Principle Use Cases
BrowserRouter example.com/users Manipulating URLs Using the History API Projects Where the Server Can Configure URL Rewriting
HashRouter example.com/#/users Preventing server requests triggered by changes to the URL hash Static hosting (GitHub Pages, CDN)

Recommendations: For projects that can be hosted on a server, always use BrowserRouter—it results in a cleaner URL and is more SEO-friendly. For static hosting, use HashRouter.

▶ Example 1: Basic Routing Configuration

Output:

TEXT 📖 Display only
Subheading: "Dashboard Home Page". routing via React Router
JSX
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom'

function Home() {
  return <h2>Dashboard Home Page</h2>
}

function ProductList() {
  return <h2>Product List</h2>
}

function Settings() {
  return <h2>System Settings</h2>
}

function NotFound() {
  return <h2>404 — Page Not Found</h2>
}

function App() {
  return (
    <BrowserRouter>
      <nav style={{ display: 'flex', gap: '1rem', padding: '1rem', background: '#f0f0f0' }}>
        <Link to="/">Home</Link>
        <Link to="/products">Product Management</Link>
        <Link to="/settings">System Settings</Link>
      </nav>

      <main style={{ padding: '1rem' }}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/products" element={<ProductList />} />
          <Route path="/settings" element={<Settings />} />
          <Route path="*" element={<NotFound />} />
        </Routes>
      </main>
    </BrowserRouter>
  )
}

export default App
▶ Try it Yourself

Output:

TEXT 📖 Display only
URL routing: / → Home, /about → About, /contact → Contact. Navigation without page reload.

How It Works: When you click a navigation link, the URL changes and the page content updates, but the browser does not reload the entire page—this is the core experience of SPA routing.

About path="*": The wildcard * matches all paths not defined in preceding routes; it is typically placed at the end of the route list to generate a 404 page. In React Router v6, * can only appear as the final character of a path.

Navigation Component Purpose Activation Style Use Cases
<Link to="/path"> Declarative Navigation None General Navigation Links
<NavLink to="/path"> Navigation with Active State isActive Callback Sidebar/Top Navigation Highlight
navigate('/path') Programmatic Redirects Redirects after login, redirects after form submission
<Navigate to="/path" /> Declarative Redirect Conditional Redirect Component

Tom wants to highlight the current page in the navigation bar so users can clearly see "where they are." The <NavLink> component provides the isActive parameter, which allows you to dynamically set styles based on the current route. It has two additional properties compared to <Link>: style and className both support accepting a callback function with the isActive and isPending properties.

▶ Example 2: Navigation Bar with Active Style

Output:

TEXT 📖 Display only
Subheading: "Dashboard Home Page". Displays: "Dashboard Home Page". React Router handles navigation
JSX
import { NavLink } from 'react-router-dom'

function NavBar() {
  const linkStyle = {
    padding: '8px 16px',
    textDecoration: 'none',
    borderRadius: '6px',
    transition: 'all 0.2s'
  }

  const activeStyle = {
    ...linkStyle,
    backgroundColor: '#1976d2',
    color: '#fff',
    fontWeight: 'bold'
  }

  const inactiveStyle = {
    ...linkStyle,
    color: '#333'
  }

  return (
    <nav style={{ display: 'flex', gap: '12px', padding: '12px', background: '#fafafa' }}>
      <NavLink
        to="/"
        style={({ isActive }) => (isActive ? activeStyle : inactiveStyle)}
        end  // Exact Match,Avoid "/" Match all that start with "/" Starting path
      >
        Home
      </NavLink>
      <NavLink
        to="/products"
        style={({ isActive }) => (isActive ? activeStyle : inactiveStyle)}
      >
        Product Management
      </NavLink>
      <NavLink
        to="/settings"
        style={({ isActive }) => (isActive ? activeStyle : inactiveStyle)}
      >
        System Settings
      </NavLink>
    </nav>
  )
}

Output:

TEXT 📖 Display only
NavBar component renders the described interactive UI

Key Point: The end property ensures that the / path is activated only when there is an exact match; otherwise, all paths will trigger the homepage’s activation style. NavLink’s className also supports callback functions, making it suitable for projects that use CSS class names.

(3) Sharing Nested Routes and Layouts

Tom discovered that the product management page contained two subpages—“Product List” and “Add Product”—which shared the same sidebar layout. Writing the layout code repeatedly for each subpage would be both redundant and difficult to maintain. React Router’s nested routing combined with <Outlet> perfectly solves this problem.

The core concept of nested routing is that the parent route defines the layout framework, and the child route injects content via outlets. The parent component does not concern itself with what the child route specifically renders; it is solely responsible for the layout framework.

▶ Example 3: Nested Routes and Outlets

Output:

TEXT 📖 Display only
Displays: "Home"
JSX
import { BrowserRouter, Routes, Route, Link, Outlet, useParams } from 'react-router-dom'

// Parent Layout Component — Shared Sidebar + Outlet
function ProductsLayout() {
  return (
    <div style={{ display: 'flex' }}>
      <aside style={{ width: '200px', padding: '16px', background: '#f5f5f5' }}>
        <h3>Product Management</h3>
        <nav style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
          <Link to="list">Product List</Link>
          <Link to="add">Add Item</Link>
        </nav>
      </aside>
      <main style={{ flex: 1, padding: '16px' }}>
        {/* The components of the child route are rendered here */}
        <Outlet />
      </main>
    </div>
  )
}

function ProductList() {
  const products = [
    { id: 1, name: 'React Programming Books', price: 79 },
    { id: 2, name: 'TypeScript Guide', price: 59 },
    { id: 3, name: 'Node.js Real-World Experience', price: 69 }
  ]

  return (
    <div>
      <h2>Product List</h2>
      <ul>
        {products.map(p => (
          <li key={p.id}>
            <Link to={`/products/detail/${p.id}`}>
              {p.name} — ${p.price}
            </Link>
          </li>
        ))}
      </ul>
    </div>
  )
}

function AddProduct() {
  return (
    <div>
      <h2>Add Item</h2>
      <form onSubmit={e => { e.preventDefault(); alert('Submission Successful!') }}>
        <div><label>Product Name:<input name="name" /></label></div>
        <div><label>Price:<input name="price" type="number" /></label></div>
        <button type="submit">Submit</button>
      </form>
    </div>
  )
}

function ProductDetail() {
  const { id } = useParams()
  return <h2>Product Details(ID:{id})</h2>
}

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<h2>Home</h2>} />
        {/* Nested Routes:Parent Route with Layout,Subnet routing is enabled Outlet Rendering */}
        <Route path="/products" element={<ProductsLayout />}>
          <Route index element={<ProductList />} />          {/* /products Default Display */}
          <Route path="list" element={<ProductList />} />     {/* /products/list */}
          <Route path="add" element={<AddProduct />} />       {/* /products/add */}
          <Route path="detail/:id" element={<ProductDetail />} /> {/* /products/detail/1 */}
        </Route>
        <Route path="*" element={<h2>404 Not found</h2>} />
      </Routes>
    </BrowserRouter>
  )
}

Output:

TEXT 📖 Display only
URL routing: / → Home, /about → About, /contact → Contact. Navigation without page reload.

Flow Logic: Visit /products/listProductsLayout (render the sidebar) → Outlet (display the ProductList component at that location). The URL structure corresponds one-to-one with the component structure, making it clear and easy to maintain.

About the index route: The index route is the default subroute for the parent path. When /products is accessed and no subpath matches (neither list nor add matches), the content of the index route appears in the Outlet. This ensures that the parent path does not display a blank area.



4. Dynamic Routing and Path Parameters

Path Pattern URL Example useParams Return Value Description
/users/:id /users/42 { id: '42' } Single Dynamic Parameter
/users/:userId/posts/:postId /users/42/posts/99 { userId: '42', postId: '99' } Multi-stage dynamic parameters
/files/* /files/a/b/c { '*': 'a/b/c' } Wildcard matching the remaining path
/categories/:catId/:tab? Two routes must be defined { catId, tab } Optional parameters (no native support)

Dynamic routing is one of the most important features of a routing system. Tom’s product detail page needs to display different content based on the product ID, and he can’t possibly write a route for every product ID—this is where the :param syntax comes in to define dynamic paths.

(1) Using useParams

:id is a placeholder for a dynamic parameter; the actual value is extracted from the URL via the useParams hook.

JSX
function UserDetail() {
  const { userId, postId } = useParams()
  return <p>User {userId} Article {postId}</p>
}
// URL: /users/42/posts/99 → userId=42, postId=99
▶ Try it Yourself

▶ Example 4: Product Details Page

Output:

TEXT 📖 Display only
Subheading: "Product List". Displays: "Product Management". Button: Submit. Input types: submit, number. Form with submit handling. React Router handles navigation
JSX
import { useParams, Link, useNavigate } from 'react-router-dom'

// Simulated Product Data
const products = [
  { id: '1', name: 'React Programming Books', price: 79, description: 'Mastering It from Scratch React 18 Development' },
  { id: '2', name: 'TypeScript Guide', price: 59, description: 'Systematic Study TypeScript Type System' },
  { id: '3', name: 'Node.js Real-World Experience', price: 69, description: 'Backend Development: From Beginner to Advanced' }
]

function ProductDetail() {
  const { id } = useParams()
  const navigate = useNavigate()
  const product = products.find(p => p.id === id)

  if (!product) {
    return (
      <div>
        <h2>The product does not exist.</h2>
        <button onClick={() => navigate('/products')}>Back to Product List</button>
      </div>
    )
  }

  return (
    <div>
      <h2>{product.name}</h2>
      <p className="price">Price:${product.price}</p>
      <p className="desc">{product.description}</p>
      <Link to="/products">← Back to List</Link>
    </div>
  )
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
URL /users/42 → useParams() returns { id: "42" }. Dynamic route renders user profile for ID 42.

Note: When using dynamic parameters, you need to account for situations where "a parameter value has no corresponding data." The if (!product) branch in the code above handles empty data to prevent the page from displaying a blank screen when attempting to access a nonexistent ID.

(2) Multi-segment dynamic parameters

A path can contain multiple dynamic parameters, which is common in scenarios involving nested resources:

JSX
// Route Definitions
<Route path="/categories/:catId/products/:prodId" element={<ProductView />} />

// Extracted from the component
function ProductView() {
  const { catId, prodId } = useParams()
  // URL: /categories/electronics/products/42
  // catId = "electronics", prodId = "42"
  return <h2>Categories {catId} Items under {prodId}</h2>
}
▶ Try it Yourself

(3) Optional Parameters and Wildcards

React Router v6 does not directly support optional parameters, but you can achieve a similar effect in two ways:

JSX
// Method 1:Define two Route(Recommendations)
<Route path="/categories/:catId" element={<CategoryPage />} />
<Route path="/categories/:catId/:tab" element={<CategoryPage />} />

// Method 2:Make the determination within the component itself
function CategoryPage() {
  const { catId, tab } = useParams()
  const activeTab = tab || 'overview'  // Default value
  return <h2>Categories {catId} - {activeTab}</h2>
}
▶ Try it Yourself

The advantage of Option 1 is that the URL is semantically clear, while Option 2 is more concise but reduces the URL's readability.

(4) A Detailed Explanation of Path Matching Priorities

Path matching in React Router v6 is based on a scoring algorithm, rather than the "first-come, first-served" approach used in traditional frameworks. Understanding these rules can help troubleshoot issues where routes aren't working as expected.

JSX
// Suppose we have the following routing configuration
<Routes>
  <Route path="/products/new" element={<NewProduct />} />     {/* Static Path */}
  <Route path="/products/:id" element={<ProductDetail />} />  {/* Dynamic Path */}
  <Route path="/products/:id/edit" element={<EditProduct />} /> {/* Hybrid Path */}
</Routes>
▶ Try it Yourself

Match Priority (from highest to lowest):

  1. Static path segments (new) take precedence over dynamic parameter segments (:id)
  2. Paths with more static segments take precedence over paths with fewer static segments
  3. Paths with more segments take precedence over those with fewer segments.

Therefore, a request to /products/new matches <NewProduct />, a request to /products/42 matches <ProductDetail />, and a request to /products/42/edit matches <EditProduct />. Developers do not need to worry about the order—the system automatically selects the "best match."


❓ FAQ

Q Why does a 404 error occur when refreshing after deploying BrowserRouter to the server?
A Because the browser requests the specific path /products/detail/1 from the server, but that file does not exist on the server. Solution: Configure try_files $uri $uri/ /index.html in Nginx to redirect all route requests to index.html, and let React Router handle the matching on the front end. If you cannot configure the server, switch to HashRouter (the portion after # will not be sent to the server).
Q What is the difference between <Link> and the native <a> tag?
A Clicking <a> triggers a full page refresh in the browser, causing the SPA to lose all its in-memory state. <Link> prevents the default navigation, updates the URL via the History API, and notifies React Router to render a new component—without refreshing the page or losing state. In SPAs, always use Link or NavLink instead of <a>.
Q What are the path-matching rules for routes? Does the order of multiple routes matter?
A React Router v6 uses automatic ranking for matching (rather than sequential matching). The system automatically scores paths based on their “specificity”: /users/new is more specific than /users/:id and therefore takes precedence. * acts as a wildcard that matches all unmatched paths and is typically placed last to serve as a 404 page. Paths use prefix matching by default; adding the end attribute changes this to exact matching.
Q What is the purpose of the "index" route?
A <Route index element={...} /> defines the default subroute for the parent route. When accessing the parent route itself (e.g., /products), if the parent route uses an Outlet, the content of the "index" route will be displayed in the Outlet. It acts as the "default page under the parent path," preventing a blank area from appearing when the parent path is accessed.
Q What are the main differences between React Router v6 and v5?
A There are three major changes in v6: ① <Routes> + <Route> replaces <Switch>, and the Route component automatically matches the most specific path; ② Nested routes now use <Outlet> instead of manually rendering child routes; ③ useNavigate() replaces useHistory()navigate('/path') replaces history.push('/path'), and navigate(-1) replaces history.goBack(). The v6 API is more concise, but migrating requires significant code changes.

(5) Relative Paths vs. Absolute Paths

In nested routing, the path for <Link to="..."> is relative to the current route, while <Link to="/..."> is an absolute path. Understanding this distinction is crucial for avoiding navigation errors.

JSX
// Currently in /products under (ProductsLayout Within the component)

<Link to="list">        {/* → /products/list(Relative Path,After appending it to the current route) */}
<Link to="/list">       {/* → /list(Absolute Path,Replace directly) */}
<Link to="../settings"> {/* → /settings(Parent-level relative path) */}
▶ Try it Yourself

In nested routing, if a child route is located within ProductsLayout, all Links within it should use relative paths (without the leading /). This ensures that when the parent route’s path changes, the child route’s Links automatically adapt.



5. 404 Pages and Routing Design Patterns

(1) Wildcard Routing

path="*" matches all undefined routes and is the standard way to implement a 404 page. However, in React Router v6, * can only appear at the end of a route and cannot be used like path="/users/*/edit".

JSX
function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/products" element={<ProductList />} />
      <Route path="/products/:id" element={<ProductDetail />} />
      <Route path="/about" element={<About />} />

      {/* Wildcard routes must be placed last */}
      <Route path="*" element={<NotFound />} />
    </Routes>
  )
}

function NotFound() {
  return (
    <div style={{ textAlign: 'center', padding: '40px' }}>
      <h1>404</h1>
      <p>Sorry, the page you are looking for does not exist.。</p>
      <Link to="/">Back to Home</Link>
    </div>
  )
}
▶ Try it Yourself

(2) Basic Usage of useNavigate

Although useNavigate will be covered in detail in the advanced course, Tom will also need to use it in certain scenarios during the beginner stage—such as redirecting after a login page countdown or redirecting after a form submission.

JSX
import { useNavigate } from 'react-router-dom'

function OrderSuccess() {
  const navigate = useNavigate()
  const [countdown, setCountdown] = useState(5)

  useEffect(() => {
    const timer = setInterval(() => {
      setCountdown(prev => {
        if (prev <= 1) {
          clearInterval(timer)
          navigate('/orders')  // Automatically redirect when the countdown ends
          return 0
        }
        return prev - 1
      })
    }, 1000)

    return () => clearInterval(timer)
  }, [navigate])

  return (
    <div>
      <h2>Order Placed Successfully!</h2>
      <p>{countdown} You will be automatically redirected to the order page in seconds.</p>
      <button onClick={() => navigate('/orders')}>Check it out now</button>
    </div>
  )
}
▶ Try it Yourself

▶ Example 5: An Admin Panel Combining All Features

Output:

TEXT 📖 Display only
navigate("/dashboard") → programmatic redirect. navigate(-1) → go back. Used after login, form submit, etc.

Bring together all the concepts you’ve learned in this lesson—BrowserRouter, Routes, nested routes, NavLink, useParams, and 404 pages—to build the skeleton of a complete admin dashboard.

JSX
import { BrowserRouter, Routes, Route, NavLink, Outlet, useParams } from 'react-router-dom'
import './App.css'

// Layout Components
function AdminLayout() {
  return (
    <div className="admin-container">
      <header className="admin-header">
        <h1>Tom E-commerce Management Backend</h1>
      </header>
      <div className="admin-body">
        <nav className="admin-sidebar">
          <NavLink to="/" end>Dashboard</NavLink>
          <NavLink to="/products">Product Management</NavLink>
          <NavLink to="/orders">Order Management</NavLink>
          <NavLink to="/settings">System Settings</NavLink>
        </nav>
        <main className="admin-content">
          <Outlet />
        </main>
      </div>
    </div>
  )
}

// Page Components
function Dashboard() {
  return <h2>Welcome back,Tom!Number of Orders Today:42</h2>
}

function ProductsLayout() {
  return (
    <div>
      <h2>Product Management</h2>
      <nav>
        <NavLink to="list">Product List</NavLink>
        <NavLink to="add">Add Item</NavLink>
      </nav>
      <Outlet />
    </div>
  )
}

function ProductList() {
  return <p>The product list is displayed here...</p>
}

function AddProduct() {
  return <p>The form for adding products is displayed here....</p>
}

function Orders() {
  return <h2>Order Management</h2>
}

function Settings() {
  return <h2>System Settings</h2>
}

function NotFound() {
  return <h2>404 — Page Not Found</h2>
}

export default function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<AdminLayout />}>
          <Route index element={<Dashboard />} />
          <Route path="products" element={<ProductsLayout />}>
            <Route index element={<ProductList />} />
            <Route path="list" element={<ProductList />} />
            <Route path="add" element={<AddProduct />} />
          </Route>
          <Route path="orders" element={<Orders />} />
          <Route path="settings" element={<Settings />} />
          <Route path="*" element={<NotFound />} />
        </Route>
      </Routes>
    </BrowserRouter>
  )
}

Architectural Features: The entire application consists of only one <BrowserRouter> and one top-level <Routes>. AdminLayout serves as a global layout that embeds all page components via outlets. This "single route + nested layout" pattern is the standard architecture for medium-sized React applications.


📖 Summary


📝 Exercises

  1. Create a 4-page admin dashboard: Home, User List, User Details (retrieve the user ID using useParams), and About page. Use Link for navigation, and ensure that NavLink has an active style.
  2. Add nested routes to the user list page: /users displays the user list, and /users/:id displays user details; both share a layout component with a header (using an Outlet).
  3. Add a 404 page to the end of the routing table to display a user-friendly message when a user accesses a nonexistent path.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏