C
React/Routing/Lesson 14

React Router

30 min·theory
JavaScript

React Router

💡 Why Should You Learn This?

🎯 You can build an SPA that switches views instantly without a full page reload.
💼 Browser back-navigation, bookmarks, and other built-in browser features are supported automatically.
It is the most widely used standard routing library in large-scale projects.
🏢 실무에서는
Companies such as Toss, Karrot Market, and Baemin use React Router as a core technology. 'React Router experience' is a very common requirement in job postings, and without it, joining a real-world project is difficult.

Concept

React Router v6 is the modern standard for SPA navigation, with nested routing and layout management via Outlet, and data-fetching optimization via loader being its core strengths. It is an essential routing architecture skill for large-scale projects.

Why It Matters

When building admin panels or dashboards in real-world projects, nested layouts with a sidebar-header-content structure are indispensable. The loader pattern lets you pre-load data before entering a page, significantly improving UX. This is a must-know technology, especially for projects that choose React SPA over Next.js.

Core Concepts

React Router v6 is similar to an apartment building structure. Nested routing is a hierarchical structure like "building-unit-room," Outlet is the "empty slot" on each floor where child components are rendered, and loader is like preparing everything you need before entering a room.

Key Points

  • Nested routing aligns URL hierarchy with component structure
  • Parent-child layout management via the Outlet component
  • Pre-load data before component rendering using loader functions
💻 Bad Example — v5-style Routing Without Nesting
// App.tsx - Antipattern
import { BrowserRouter, Routes, Route } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/dashboard/users" element={<DashboardUsers />} />
        <Route path="/dashboard/products" element={<DashboardProducts />} />
        <Route path="/dashboard/settings" element={<DashboardSettings />} />
      </Routes>
    </BrowserRouter>
  );
}

// Duplicate layout code for each Dashboard component
function DashboardUsers() {
  return (
    <div className="dashboard-layout">
      <Sidebar />
      <Header />
      <div className="content">
        <h1>User Management</h1>
        {/* User list */}
      </div>
    </div>
  );
}
💻 Good Example — v6 Nested Routing with Outlet
// App.tsx - 2025 Recommended Pattern
import { createBrowserRouter, RouterProvider, Outlet } from 'react-router-dom';
import { DashboardLayout } from './components/DashboardLayout';
import { Users, Products, Settings } from './pages';

// Define nested routing
const router = createBrowserRouter([
  {
    path: '/',
    element: <Home />,
  },
  {
    path: '/dashboard',
    element: <DashboardLayout />, // Parent layout
    children: [
      {
        index: true, // /dashboard default path
        element: <DashboardHome />,
      },
      {
        path: 'users',
        element: <Users />,
        loader: usersLoader, // Pre-load data
      },
      {
        path: 'products',
        element: <Products />,
        loader: productsLoader,
      },
      {
        path: 'settings',
        element: <Settings />,
      },
    ],
  },
]);

function App() {
  return <RouterProvider router={router} />;
}

// DashboardLayout.tsx - Reusable layout
function DashboardLayout() {
  return (
    <div className="dashboard-layout">
      <Sidebar />
      <div className="main-content">
        <Header />
        <div className="content">
          <Outlet /> {/* Child components render here */}
        </div>
      </div>
    </div>
  );
}

// loader functions - Load data before component rendering
export async function usersLoader() {
  const users = await fetch('/api/users');
  return users.json();
}

// Users.tsx - Use loader data
import { useLoaderData } from 'react-router-dom';

function Users() {
  const users = useLoaderData() as User[];
  
  return (
    <div>
      <h1>User Management</h1>
      {users.map(user => (
        <UserCard key={user.id} user={user} />
      ))}
    </div>
  );
}
💻 Advanced Example — Error Boundary and Loading State Handling
// router.tsx - A complete solution considering error handling and loading states
import { createBrowserRouter, isRouteErrorResponse } from 'react-router-dom';

const router = createBrowserRouter([
  {
    path: '/dashboard',
    element: <DashboardLayout />,
    errorElement: <DashboardErrorBoundary />, // Error Boundary
    children: [
      {
        path: 'users',
        element: <Users />,
        loader: usersLoader,
        errorElement: <UsersError />, // Page-specific error handling
      },
    ],
  },
]);

// Error Boundary Component
function DashboardErrorBoundary() {
  const error = useRouteError();
  
  if (isRouteErrorResponse(error)) {
    return (
      <div className="error-page">
        <h1>{error.status} {error.statusText}</h1>
        <p>{error.data}</p>
      </div>
    );
  }
  
  return (
    <div className="error-page">
      <h1>An unexpected error has occurred</h1>
    </div>
  );
}

// Error handling in loader
export async function usersLoader() {
  try {
    const response = await fetch('/api/users');
    if (!response.ok) {
      throw new Response('Unable to load user data', {
        status: response.status,
        statusText: response.statusText,
      });
    }
    return response.json();
  } catch (error) {
    throw new Response('Network error', { status: 500 });
  }
}

// Utilizing Suspense for loading state display
function App() {
  return (
    <Suspense fallback={<GlobalLoadingSpinner />}>
      <RouterProvider router={router} />
    </Suspense>
  );
}

💡 ⚠️ Common Mistakes

  • Forgetting Outlet so nested routes never render — you must add to the parent component
  • Not handling errors in loader, causing the app to crash — errors must be thrown as Response objects inside a try-catch
  • Confusing index: true with path: '' — index is the default for the parent path, while path: '' matches all child paths

💡 🎯 Interview Prep

Q: Explain the difference between nested routing in React Router v6 and v5
Q: Describe the role and usage of the Outlet component
Q: What are the reasons and benefits of using the loader pattern?

Hint: For nested routing, lead with aligning URL structure with component hierarchy and improved layout reusability. For Outlet, mention specifying where child components render. For loader, highlight pre-loading data before component rendering to improve UX — build your answers around these key terms.

⚛️ React Pattern — React Router

Learn how to use React Router in React step by step with code examples.
1 🛣️ 1. Install Router
$ npm install react-router-dom
2 📋 2. Define Routes
Map URLs to components
<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/about" element={<About />} />
</Routes>
3 🔗 3. Navigate with Links
Use <Link> to navigate without a full page reload
<Link to="/about">About</Link>
4 🎯 4. Dynamic Parameters
Dynamic routing like /users/:id + useParams

🎮 React Router — Step-by-Step Understanding

Click each step to read the content and track your progress with the ✓ Got it button.
🖥️ Result — rendered React component
✏️ React 코드 수정하기 (클릭해서 열기)
⚛️ React 18 + Babel Standalone — see the result first, then edit the code yourself.

Check Quiz

Which component in React Router is used to declaratively navigate between pages?
💡 <Link to="/path"> renders as an anchor tag, but performs SPA navigation without a full page reload. For programmatic navigation, use useNavigate().
Read this first: useContext
Up next: Data Fetching
React Router - React