Learn React Router in an Existing Project

Learn React Router in an Existing Project

Adding React Router to an existing project feels a little like renovating a house while still living in it. The lights are on, the furniture is already in place, and your users are walking through the front door every day. You cannot just tear everything down and start over. You need to introduce routing carefully, keep the app stable, and make the transition feel natural.

That is exactly why this topic matters so much.

A lot of tutorials show React Router in a brand-new app. That is useful, but the real world is different. Most of the time, you are not starting from zero. You already have pages, components, API calls, layouts, forms, and maybe even a small mess of conditional rendering that has been growing for months. Then one day you realize the app needs real navigation: a dashboard, profile pages, product pages, order details, admin panels, settings, and maybe a public site on top of that.

At that point, React Router becomes more than a library. It becomes the structure that helps your app feel like an actual application instead of one giant screen.

This guide walks through how to add React Router to an existing React project in a clean, practical way. We will build step by step, using realistic examples, and we will talk about common mistakes along the way. The goal is not just to “make routes work.” The goal is to make the routing feel like it always belonged there.


Why React Router matters in an existing project

In a small app, you can sometimes fake navigation with state. A button changes a boolean, a new component appears, and that is enough for a while. But as the app grows, that approach starts to crack.

A route gives you something state alone cannot:

  • A shareable URL

  • Browser back and forward support

  • Direct access to nested pages

  • Better organization of large screens

  • Cleaner separation between sections of the app

If someone visits /dashboard/orders/42, they should land directly on that order. If they refresh the browser, the page should still load correctly. If they copy the URL and send it to a teammate, that teammate should arrive at the same place. Routing makes that possible.

For an existing project, this is often the moment where the app becomes easier to maintain too. Instead of one large component trying to manage everything, you get a route-based structure where each page has a clear responsibility.


Before you start: understand your current project

Before adding React Router, take a quiet look at your app as it already exists.

Ask yourself:

  • Does the app already have a layout component?

  • Is there a navbar or sidebar that should stay visible on every page?

  • Are there any pages that should be public and others that should be private?

  • Do you have components that are really “pages” but are not treated like pages yet?

  • Are you using React Router already in a limited way, or are you starting from scratch?

This matters because existing projects rarely need a full rewrite. They usually need a careful reorganization.

For example, if your app already has a top navigation bar and a left sidebar, those should probably become part of a shared layout route. The content inside the page should change, but the shell around it should stay consistent.

That shared shell is one of the biggest strengths of React Router.


Installing React Router

If your project does not already include it, install react-router-dom.

npm install react-router-dom

Or with Yarn:

yarn add react-router-dom

Or with pnpm:

pnpm add react-router-dom

If you are working in an existing codebase, this is usually the easiest part. The more important part comes next: placing the router in the right place in your app.


Wrap your app with BrowserRouter

React Router needs to know about the URL, and that means your application must be wrapped in a router provider.

In most React apps, this happens in main.jsx, main.tsx, index.jsx, or index.tsx.

Example with main.jsx

import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import App from './App'
import './index.css'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
)

That single change is the foundation. Without it, your routes and links will not work properly.

If your project already has providers for Redux, Context API, React Query, or theme handling, you may need to place BrowserRouter in the right position among them. Usually, it is fine to wrap App with it at the top level.


The simplest route setup

Now let us create a basic route structure.

App.jsx

import { Routes, Route } from 'react-router-dom'
import Home from './pages/Home'
import About from './pages/About'
import NotFound from './pages/NotFound'

export default function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
      <Route path="*" element={<NotFound />} />
    </Routes>
  )
}

This is the simplest working setup.

Now each URL renders a different component:

  • / renders Home

  • /about renders About

  • anything else renders NotFound

That is enough to get started, but in a real project, this is only the beginning.


Organize your project before it becomes messy

If your existing project has grown organically, you may already feel the pain of components living in the wrong places. Routing is a good excuse to clean up the structure.

A common structure looks like this:

src/
  components/
  layouts/
  pages/
  routes/
  hooks/
  context/
  assets/

A practical breakdown:

  • pages/ for route-level screens

  • layouts/ for shared page shells

  • components/ for reusable UI pieces

  • routes/ for route configuration if the app gets large

This is not a strict rule. It is simply a way to make the project easier to reason about. In an existing app, even a small cleanup like moving page-like components into pages/ can reduce confusion later.

A good sign that a component belongs in pages/ is this: if it is tied to a URL, it is probably a page.


Add shared layouts the right way

One of the most important features in React Router is the ability to create layout routes.

A layout route lets you keep common UI around multiple pages. Think of a dashboard with a sidebar, top bar, and footer. Only the main content area changes.

DashboardLayout.jsx

import { Outlet, NavLink } from 'react-router-dom'

export default function DashboardLayout() {
  return (
    <div style={{ display: 'flex', minHeight: '100vh' }}>
      <aside style={{ width: 240, padding: 20, background: '#f4f4f4' }}>
        <h2>My App</h2>
        <nav style={{ display: 'grid', gap: 12, marginTop: 20 }}>
          <NavLink to="/dashboard" end>
            Dashboard
          </NavLink>
          <NavLink to="/dashboard/orders">Orders</NavLink>
          <NavLink to="/dashboard/settings">Settings</NavLink>
        </nav>
      </aside>

      <main style={{ flex: 1, padding: 24 }}>
        <Outlet />
      </main>
    </div>
  )
}

The important part is <Outlet />. That is where nested route content appears.

Route setup

import { Routes, Route } from 'react-router-dom'
import Home from './pages/Home'
import About from './pages/About'
import Dashboard from './pages/Dashboard'
import Orders from './pages/Orders'
import Settings from './pages/Settings'
import NotFound from './pages/NotFound'
import DashboardLayout from './layouts/DashboardLayout'

export default function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />

      <Route path="/dashboard" element={<DashboardLayout />}>
        <Route index element={<Dashboard />} />
        <Route path="orders" element={<Orders />} />
        <Route path="settings" element={<Settings />} />
      </Route>

      <Route path="*" element={<NotFound />} />
    </Routes>
  )
}

Now /dashboard, /dashboard/orders, and /dashboard/settings all share the same layout.

This is one of those changes that makes a project feel professional very quickly.


How to migrate an existing project without breaking everything

If your app already has a lot of components, do not try to convert everything at once. That is usually where people get stuck.

A safer migration path looks like this:

Step 1: Keep the current UI working

Do not remove your existing screen logic immediately. First, add BrowserRouter and a small route setup.

Step 2: Move one screen at a time into pages

Pick one part of the app, wrap it in a route, and make sure it behaves correctly.

Step 3: Extract layouts

If you already have a navbar or sidebar, turn it into a layout component.

Step 4: Replace manual view switching

If you have code like this:

{activeTab === 'profile' && <Profile />}
{activeTab === 'billing' && <Billing />}
{activeTab === 'security' && <Security />}

consider turning those into real routes instead:

<Route path="/settings/profile" element={<Profile />} />
<Route path="/settings/billing" element={<Billing />} />
<Route path="/settings/security" element={<Security />} />

That change is often more powerful than it looks. It gives each section its own URL, improves navigation, and makes the app easier to maintain.

Step 5: Clean up old state-based navigation

Once routes are reliable, remove the extra state logic that no longer needs to control page-level UI.

This is the kind of migration that rewards patience. The app stays usable while you improve it piece by piece.


Basic navigation with Link and NavLink

In a real app, you should avoid using plain <a href="/..."> for internal navigation unless you truly need a full page reload.

Use Link instead.

Example

import { Link } from 'react-router-dom'

export default function Header() {
  return (
    <header>
      <Link to="/">Home</Link>
      <Link to="/about">About</Link>
      <Link to="/dashboard">Dashboard</Link>
    </header>
  )
}

Link changes the URL without reloading the page.

For navigation items, NavLink is often better because it can show the active state.

import { NavLink } from 'react-router-dom'

export default function MainNav() {
  return (
    <nav>
      <NavLink
        to="/"
        className={({ isActive }) => (isActive ? 'active' : '')}
        end
      >
        Home
      </NavLink>

      <NavLink
        to="/about"
        className={({ isActive }) => (isActive ? 'active' : '')}
      >
        About
      </NavLink>
    </nav>
  )
}

That active state is very useful for sidebars, tabs, and menus.


Route parameters: building dynamic pages

Most real projects need dynamic routes. A blog, shop, CRM, or dashboard usually has pages like:

  • /products/12

  • /users/5

  • /orders/8391

  • /blog/react-router-guide

React Router handles this with route parameters.

Route definition

<Route path="/products/:productId" element={<ProductDetails />} />

Reading the parameter

import { useParams } from 'react-router-dom'

export default function ProductDetails() {
  const { productId } = useParams()

  return (
    <div>
      <h1>Product Details</h1>
      <p>Product ID: {productId}</p>
    </div>
  )
}

This is especially useful in an existing project where many screens may already be fetching data by ID. Routing makes that logic feel much more natural.

You can also use multiple params:

<Route path="/users/:userId/orders/:orderId" element={<OrderDetails />} />

And in the component:

import { useParams } from 'react-router-dom'

export default function OrderDetails() {
  const { userId, orderId } = useParams()

  return (
    <div>
      <h1>Order Details</h1>
      <p>User: {userId}</p>
      <p>Order: {orderId}</p>
    </div>
  )
}

Using search parameters for filters and sorting

Sometimes the path should stay the same, but the page state should change. That is where search parameters are useful.

For example:

  • /products?category=shoes

  • /users?sort=latest

  • /orders?status=pending

Reading search params

import { useSearchParams } from 'react-router-dom'

export default function ProductsPage() {
  const [searchParams, setSearchParams] = useSearchParams()

  const category = searchParams.get('category') || 'all'
  const sort = searchParams.get('sort') || 'popular'

  return (
    <div>
      <h1>Products</h1>
      <p>Category: {category}</p>
      <p>Sort: {sort}</p>

      <button onClick={() => setSearchParams({ category: 'shoes', sort: 'latest' })}>
        Show Shoes
      </button>
    </div>
  )
}

Search params are excellent when the page is already part of your existing UI and you want the URL to reflect what the user is seeing. That makes the page easier to share and return to later.


Protected routes for authenticated sections

Most existing projects eventually need private pages. Maybe the app has a dashboard, admin area, account page, or internal tools. Those should not be open to everyone.

A common pattern is a protected route wrapper.

Example authentication gate

import { Navigate, Outlet } from 'react-router-dom'

export default function ProtectedRoute({ isAuthenticated }) {
  if (!isAuthenticated) {
    return <Navigate to="/login" replace />
  }

  return <Outlet />
}

Route usage

import { Routes, Route } from 'react-router-dom'
import ProtectedRoute from './components/ProtectedRoute'
import Login from './pages/Login'
import DashboardLayout from './layouts/DashboardLayout'
import Dashboard from './pages/Dashboard'
import Settings from './pages/Settings'

export default function App() {
  const isAuthenticated = true

  return (
    <Routes>
      <Route path="/login" element={<Login />} />

      <Route element={<ProtectedRoute isAuthenticated={isAuthenticated} />}>
        <Route path="/dashboard" element={<DashboardLayout />}>
          <Route index element={<Dashboard />} />
          <Route path="settings" element={<Settings />} />
        </Route>
      </Route>
    </Routes>
  )
}

This keeps route protection centralized instead of scattering checks across multiple components.

In a real project, isAuthenticated would likely come from context, Redux, cookies, or a session hook.


Redirects with Navigate

Redirects are common in existing apps. Maybe you want to send old URLs to new ones, or maybe a user should be moved after login.

Example: redirecting an old route

import { Navigate } from 'react-router-dom'

<Route path="/home" element={<Navigate to="/" replace />} />

Example: redirect after form submission

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

export default function CreatePost() {
  const navigate = useNavigate()

  const handleSubmit = async (e) => {
    e.preventDefault()
    // save data here
    navigate('/posts')
  }

  return (
    <form onSubmit={handleSubmit}>
      <button type="submit">Save Post</button>
    </form>
  )
}

useNavigate is one of those small tools that ends up being used everywhere once routing becomes part of the project.


Handling nested pages inside an existing dashboard

Let us say your existing project already has a dashboard with a lot of internal sections. You might have been using a sidebar and switching content with local state.

That works at first, but routing gives you a cleaner model.

Example dashboard structure

import { Routes, Route } from 'react-router-dom'
import DashboardLayout from './layouts/DashboardLayout'
import DashboardHome from './pages/dashboard/DashboardHome'
import UsersPage from './pages/dashboard/UsersPage'
import ReportsPage from './pages/dashboard/ReportsPage'
import UserDetails from './pages/dashboard/UserDetails'

export default function AppRoutes() {
  return (
    <Routes>
      <Route path="/dashboard" element={<DashboardLayout />}>
        <Route index element={<DashboardHome />} />
        <Route path="users" element={<UsersPage />} />
        <Route path="users/:userId" element={<UserDetails />} />
        <Route path="reports" element={<ReportsPage />} />
      </Route>
    </Routes>
  )
}

Sidebar with links

import { NavLink, Outlet } from 'react-router-dom'

export default function DashboardLayout() {
  return (
    <div className="dashboard">
      <aside className="sidebar">
        <NavLink to="/dashboard" end>Overview</NavLink>
        <NavLink to="/dashboard/users">Users</NavLink>
        <NavLink to="/dashboard/reports">Reports</NavLink>
      </aside>

      <section className="content">
        <Outlet />
      </section>
    </div>
  )
}

This approach gives the user a much smoother experience. They can refresh, bookmark, and navigate naturally.


404 pages and fallback routes

Every project needs a friendly fallback.

Without one, users may land on a blank screen or a broken path and feel lost.

Example NotFound.jsx

import { Link } from 'react-router-dom'

export default function NotFound() {
  return (
    <div style={{ padding: 40, textAlign: 'center' }}>
      <h1>404</h1>
      <p>Sorry, the page you are looking for does not exist.</p>
      <Link to="/">Go back home</Link>
    </div>
  )
}

Route usage

<Route path="*" element={<NotFound />} />

This small page protects the experience of the entire app. It is simple, but users notice when it is missing.


Lazy loading routes for better performance

As the existing project grows, routes can become heavy. If every page loads at once, the bundle size may get larger than necessary.

React lazy loading helps here.

Example

import { lazy, Suspense } from 'react'
import { Routes, Route } from 'react-router-dom'

const Home = lazy(() => import('./pages/Home'))
const About = lazy(() => import('./pages/About'))
const Dashboard = lazy(() => import('./pages/Dashboard'))

export default function App() {
  return (
    <Suspense fallback={<div>Loading page...</div>}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/dashboard" element={<Dashboard />} />
      </Routes>
    </Suspense>
  )
}

This is especially helpful in existing apps that have many routes, admin screens, or feature-heavy sections. The idea is simple: only load what the user needs right now.


Data loading inside route pages

Once routing is in place, pages usually fetch data based on the current URL. That is one of the nicest parts of route-driven design.

Example product page

import { useParams } from 'react-router-dom'
import { useEffect, useState } from 'react'

export default function ProductDetails() {
  const { productId } = useParams()
  const [product, setProduct] = useState(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    let isMounted = true

    async function fetchProduct() {
      try {
        setLoading(true)
        const response = await fetch(`/api/products/${productId}`)
        const data = await response.json()

        if (isMounted) {
          setProduct(data)
        }
      } finally {
        if (isMounted) {
          setLoading(false)
        }
      }
    }

    fetchProduct()

    return () => {
      isMounted = false
    }
  }, [productId])

  if (loading) return <p>Loading...</p>
  if (!product) return <p>Product not found.</p>

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
    </div>
  )
}

When you bring React Router into an existing project, many components become easier to understand because the URL now tells the app what to show.


Common mistakes when adding React Router to an existing project

A few problems show up again and again.

1. Forgetting to wrap with BrowserRouter

Without the router provider, links and hooks will fail.

2. Mixing old and new navigation patterns

If part of the app uses window.location, part uses state, and part uses router links, the experience becomes inconsistent.

3. Not planning layouts

Without layout routes, you may repeat the same header and sidebar across multiple pages.

4. Using a tags for internal links

This causes full page reloads and breaks the smooth SPA experience.

5. Putting too much logic in App.jsx

As the project grows, routing should become organized. Very large App.jsx files get hard to maintain.

6. Forgetting a catch-all route

Without a 404 page, invalid URLs become confusing.

7. Overcomplicating migration

You do not need to convert everything in one pass. That usually slows the team down.

The best way to avoid these issues is to move slowly and keep the app functional at every step.


A practical route setup for an existing project

Here is a realistic example of how you might organize a mature React app.

File structure

src/
  layouts/
    MainLayout.jsx
    DashboardLayout.jsx
  pages/
    Home.jsx
    About.jsx
    Login.jsx
    NotFound.jsx
    dashboard/
      DashboardHome.jsx
      UsersPage.jsx
      ReportsPage.jsx
      UserDetails.jsx
  components/
    Header.jsx
    Footer.jsx
    Sidebar.jsx
    ProtectedRoute.jsx
  App.jsx
  main.jsx

main.jsx

import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import App from './App'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
)

App.jsx

import { Routes, Route } from 'react-router-dom'
import MainLayout from './layouts/MainLayout'
import DashboardLayout from './layouts/DashboardLayout'
import ProtectedRoute from './components/ProtectedRoute'
import Home from './pages/Home'
import About from './pages/About'
import Login from './pages/Login'
import NotFound from './pages/NotFound'
import DashboardHome from './pages/dashboard/DashboardHome'
import UsersPage from './pages/dashboard/UsersPage'
import UserDetails from './pages/dashboard/UserDetails'
import ReportsPage from './pages/dashboard/ReportsPage'

export default function App() {
  const isAuthenticated = true

  return (
    <Routes>
      <Route element={<MainLayout />}>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/login" element={<Login />} />
      </Route>

      <Route element={<ProtectedRoute isAuthenticated={isAuthenticated} />}>
        <Route path="/dashboard" element={<DashboardLayout />}>
          <Route index element={<DashboardHome />} />
          <Route path="users" element={<UsersPage />} />
          <Route path="users/:userId" element={<UserDetails />} />
          <Route path="reports" element={<ReportsPage />} />
        </Route>
      </Route>

      <Route path="*" element={<NotFound />} />
    </Routes>
  )
}

MainLayout.jsx

import { Outlet, Link } from 'react-router-dom'

export default function MainLayout() {
  return (
    <div>
      <header>
        <nav>
          <Link to="/">Home</Link>
          <Link to="/about">About</Link>
          <Link to="/dashboard">Dashboard</Link>
          <Link to="/login">Login</Link>
        </nav>
      </header>

      <main>
        <Outlet />
      </main>
    </div>
  )
}

This is the kind of structure that scales well in a real project. It is clean, readable, and easy for another developer to understand later.


Styling routes in a real application

Routing is not only about page switching. It also changes how your app feels.

A route-based project often needs:

  • active sidebar states

  • page transitions

  • consistent content spacing

  • layout-specific styles

  • responsive navigation

For example, a dashboard sidebar may highlight the current route, while a mobile menu may collapse into a drawer. Route state helps drive those UI decisions.

Here is a simple active link style:

import { NavLink } from 'react-router-dom'

export default function SidebarLink({ to, children }) {
  return (
    <NavLink
      to={to}
      end
      className={({ isActive }) =>
        isActive ? 'sidebar-link active' : 'sidebar-link'
      }
    >
      {children}
    </NavLink>
  )
}

Once routes are part of the app, these details become much easier to manage.


Real-world example: converting tabbed sections into routes

This is one of the most practical upgrades you can make in an existing app.

Imagine a settings page with tabs:

  • Profile

  • Security

  • Notifications

  • Billing

Many apps start with local tab state:

const [tab, setTab] = useState('profile')

Then they render content based on that state. It works, but it has limits. The URL does not reflect the active section, and users cannot bookmark a specific tab.

A better approach is to turn the tabs into routes:

<Route path="/settings" element={<SettingsLayout />}>
  <Route index element={<ProfileSettings />} />
  <Route path="security" element={<SecuritySettings />} />
  <Route path="notifications" element={<NotificationSettings />} />
  <Route path="billing" element={<BillingSettings />} />
</Route>

Now each tab has a URL.

That simple change improves usability, makes the code easier to follow, and gives the app a more polished feel.


Example: a settings layout with nested routes

import { NavLink, Outlet } from 'react-router-dom'

export default function SettingsLayout() {
  return (
    <div style={{ display: 'grid', gridTemplateColumns: '220px 1fr', gap: 24 }}>
      <aside>
        <h2>Settings</h2>
        <nav style={{ display: 'grid', gap: 10, marginTop: 16 }}>
          <NavLink to="/settings" end>Profile</NavLink>
          <NavLink to="/settings/security">Security</NavLink>
          <NavLink to="/settings/notifications">Notifications</NavLink>
          <NavLink to="/settings/billing">Billing</NavLink>
        </nav>
      </aside>

      <section>
        <Outlet />
      </section>
    </div>
  )
}

This pattern is a great fit for existing projects because it creates order without forcing a redesign.


Testing route behavior

Once routing is part of the app, it is worth testing. Not every route needs a heavy test suite, but the critical paths should be reliable.

You may want to test:

  • public page rendering

  • protected route redirects

  • 404 behavior

  • dynamic route rendering

  • navigation links

A routing test often checks that a page appears after rendering at a given route.

Example using React Testing Library:

import { render, screen } from '@testing-library/react'
import { MemoryRouter } from 'react-router-dom'
import App from './App'

test('renders home page on root route', () => {
  render(
    <MemoryRouter initialEntries={['/']}>
      <App />
    </MemoryRouter>
  )

  expect(screen.getByText(/home/i)).toBeInTheDocument()
})

If your app is already mature, even a few route tests can prevent accidental regressions.


A thoughtful migration strategy for large projects

For bigger existing projects, the smartest move is often to migrate by section.

You might begin with:

  • public pages first

  • then authentication pages

  • then dashboard pages

  • then nested admin routes

  • then tabbed sections

  • finally, route cleanup and redirects

This phased approach keeps the team from feeling overwhelmed. It also reduces risk because each step can be reviewed and verified.

A good migration is less about speed and more about confidence.

If the app is already in production, that confidence matters a lot.


When not to overuse routing

Routing is powerful, but not every piece of UI deserves its own route.

For example, a modal, drawer, tooltip, dropdown, or simple accordion section usually should not become a route unless there is a real reason. Routes are best for meaningful destinations, not every state change.

Ask a simple question:

Does this deserve a URL?

If yes, route it. If not, keep it as component state.

That balance is one of the signs of a healthy React app.


A final example: routing in a content-driven project

Let us imagine an existing blog or content platform.

You may need routes like:

  • /

  • /blog

  • /blog/:slug

  • /categories/:categorySlug

  • /author/:authorId

  • /dashboard/posts

  • /dashboard/posts/:postId/edit

A good router setup makes the structure clear:

<Routes>
  <Route path="/" element={<MainLayout />}>
    <Route index element={<Home />} />
    <Route path="blog" element={<BlogList />} />
    <Route path="blog/:slug" element={<BlogPost />} />
    <Route path="categories/:categorySlug" element={<CategoryPage />} />
    <Route path="author/:authorId" element={<AuthorPage />} />
  </Route>

  <Route element={<ProtectedRoute isAuthenticated={isAuthenticated} />}>
    <Route path="/dashboard" element={<DashboardLayout />}>
      <Route path="posts" element={<PostsList />} />
      <Route path="posts/:postId/edit" element={<EditPost />} />
    </Route>
  </Route>

  <Route path="*" element={<NotFound />} />
</Routes>

That structure gives the whole project a clear map. Once the map exists, adding new pages becomes much easier.


Final thoughts

Learning React Router in an existing project is really about learning how to evolve an app without breaking its shape.

The first step is technical, but the bigger skill is architectural. You are deciding what belongs in a route, what belongs in a layout, what belongs in page state, and what should stay simple. That kind of thinking makes a codebase healthier over time.

The nice thing about React Router is that it scales with you. It can handle a tiny site with three pages just as well as a large product with dashboards, nested sections, protected pages, and dynamic content. In an existing project, that flexibility is exactly what you need.

Start small. Add one route. Add one layout. Convert one tab system. Then keep going.