Learn ReactJS - Routing & Navigation
Episode 9 of 24

Learn ReactJS - Routing & Navigation

This episode teaches React Router: BrowserRouter, Routes, and Route for defining pages, nested routes with dynamic params, route guards for protection, and Link, NavLink, and redirect. You'll also learn route-based code splitting.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

An app with only one page quickly hits its limits. React Router is the de facto standard for navigation in React SPAs — the library that makes the URL the source of truth for the page being displayed.

Episode 9 builds from the basics: BrowserRouter, Routes, and Route, then moves on to nested routes, dynamic params, route guards, Link, NavLink, and redirect. Finally, we apply route-based code splitting with lazy and Suspense so each page loads separately.

React Router Basics

Installing and Setting Up BrowserRouter

Start by installing React Router and wrapping your app with BrowserRouter:

Install React Router
npm install react-router-dom
JSFirst route
import { BrowserRouter, Routes, Route, Link } from "react-router-dom"
 
function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">Beranda</Link>
        <Link to="/tentang">Tentang</Link>
      </nav>
      <Routes>
        <Route path="/" element={<h1>Beranda</h1>} />
        <Route path="/tentang" element={<h1>Tentang Kami</h1>} />
      </Routes>
    </BrowserRouter>
  )
}

<BrowserRouter>{:javascript}</BrowserRouter> provides the URL context to the entire app, and <Routes> holds the list of <Route> entries matched against paths. Link replaces <a> so navigation happens without a full reload.

Nested Routes, Dynamic Params, and Route Guards

Nested Routes with Outlet

For a shared layout (header, sidebar) across many pages, use a parent route with <Outlet>:

JSNested route with Outlet
import { Routes, Route, Outlet } from "react-router-dom"
 
function Layout() {
  return (
    <div>
      <header>Header Bersama</header>
      <Outlet />
    </div>
  )
}
 
function App() {
  return (
    <Routes>
      <Route element={<Layout />}>
        <Route path="/" element={<h1>Beranda</h1>} />
        <Route path="/blog" element={<h1>Blog</h1>} />
      </Route>
    </Routes>
  )
}

<Outlet /> is where child routes render. Layouts like headers and footers are written once, and every page appears inside them.

Dynamic Params

Paths can contain dynamic parameters with a colon prefix:

JSDynamic param useParams
import { useParams } from "react-router-dom"
 
function DetailProduk() {
  const { id } = useParams()
  return <h1>Produk {id}</h1>
}
 
// rute: <Route path="/produk/:id" element={<DetailProduk />} />

const { id } = useParams() reads the value from a URL like /produk/42. Dynamic parameters let one component serve many pages.

Route Guards

Route guards protect pages that require authentication. The simple pattern: check the condition, then render <Navigate> if the user isn't authorized:

JSSimple route guard
import { Navigate } from "react-router-dom"
 
function HalamanProteksi({ login, anak }) {
  if (!login) return <Navigate to="/login" replace />
  return anak
}

<Navigate to="/login" replace /> redirects users who aren't logged in to the login page. replace swaps the history entry so the back button doesn't return to the protected page. Episode 12 will build a full authentication pattern.

NavLink works like Link plus knows when it's active — useful for menus with an active style:

JSNavLink with active class
import { NavLink } from "react-router-dom"
 
<NavLink
  to="/dashboard"
  className={({ isActive }) => (isActive ? "menu aktif" : "menu")}
>
  Dashboard
</NavLink>

className={({ isActive }) => isActive ? "menu aktif" : "menu"{:javascript}} applies a different class when the route is active. The navigation menu immediately shows which page the user is on.

Route-Based Code Splitting and Lazy Loading

lazy and Suspense per Route

Large pages can be loaded separately so the initial bundle stays small. React provides lazy and Suspense:

JSCode splitting per route
import { lazy, Suspense } from "react"
 
const Dashboard = lazy(() => import("./Dashboard.jsx"))
 
function App() {
  return (
    <Suspense fallback={<p>Memuat halaman...</p>}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
      </Routes>
    </Suspense>
  )
}

lazy(() => import("./Dashboard.jsx")) makes Vite split Dashboard into a separate chunk that's only downloaded when that route is visited. The fallback is shown while the download is in progress.

See the code splitting result
npm run build

Notice the build output: the dist/assets folder contains several separate .js files, one for each route. This is the foundation of networking performance we'll optimize in episode 14.

Conclusion

Episode 9 gave your app many pages: the BrowserRouter, Routes, and Route basics, nested routes with Outlet, dynamic params, route guards, Link, NavLink, redirect, and per-route code splitting with lazy plus Suspense.

Key takeaways:

  • BrowserRouter wraps the app; Routes and Route define pages.
  • Outlet renders child routes inside a shared layout.
  • useParams reads dynamic params from the URL.
  • Route guards use <Navigate> to redirect unauthorized users.
  • NavLink marks the active page; useNavigate for programmatic navigation.
  • lazy + Suspense split the bundle per route.

In the next episode, episode 10, we'll cover modern state management — the Context API for shared state, useReducer for complex state logic, then Zustand and Redux Toolkit as modern state managers, plus best practices for global state versus local state. You'll stop passing long chains of props.