[๐ŸŽ์ฝ”๋”ฉ์• ํ”Œ ๊ฐ•์˜์š”์•ฝ]navigate, nested routes, outlet ๐Ÿงญ

๐ŸŒˆ KJยท2025๋…„ 6์›” 3์ผ

codingapple

๋ชฉ๋ก ๋ณด๊ธฐ
19/23
post-thumbnail

์˜ค๋Š˜์˜ ์ˆ™์ œ ๐Ÿ“

/event/one ํŽ˜์ด์ง€์™€ /event/two ํŽ˜์ด์ง€๋ฅผ nested routes๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋งŒ๋“ค์–ด๋ด…์‹œ๋‹ค!

React ํ”„๋กœ์ ํŠธ ํด๋” ๊ตฌ์กฐ ๐Ÿ“

React๋Š” HTML์„ ์ด์˜๊ฒŒ ๋งŒ๋“ค์–ด์ฃผ๋Š” ์ž‘์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. ๋งŒ๋“ค ํŒŒ์ผ๋“ค์˜ 95%๊ฐ€ .js ํŒŒ์ผ์ด๋ฏ€๋กœ ๋น„์Šทํ•œ .js ํŒŒ์ผ๋ผ๋ฆฌ ๋ฌถ์–ด์„œ ํด๋”๋ฅผ ๊ตฌ์„ฑํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค!

๊ถŒ์žฅ ํด๋” ๊ตฌ์กฐ

src/
โ”œโ”€โ”€ components/     # ์ปดํฌ๋„ŒํŠธ ์—ญํ• ํ•˜๋Š” js ํŒŒ์ผ
โ”œโ”€โ”€ routes/         # ํŽ˜์ด์ง€ ์—ญํ• ํ•˜๋Š” js ํŒŒ์ผ (๋˜๋Š” pages/)
โ”œโ”€โ”€ utils/          # ์ž์ฃผ ์“ฐ๋Š” ํ•จ์ˆ˜๋“ค
โ””โ”€โ”€ App.jsx

ํ•„์š”ํ•  ๋•Œ๋งˆ๋‹ค ํด๋”๋ฅผ ์ถ”๊ฐ€๋กœ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•˜์„ธ์š”! ๐Ÿ”ง

์˜ค๋Š˜ ๋ฐฐ์šธ ๊ฒƒ๋“ค importํ•˜๊ธฐ ๐Ÿ“ฆ

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

1. useNavigate() - ํŽ˜์ด์ง€ ์ด๋™ ๊ธฐ๋Šฅ ๐Ÿš€

Link ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ชป์ƒ๊ฒผ๋‹ค๋ฉด useNavigate()๋ฅผ ์‚ฌ์šฉํ•ด๋ณด์„ธ์š”!

function App(){
  let navigate = useNavigate()
  
  return (
    // ์ƒ๋žต
    <button onClick={() => { navigate('/detail') }}>์ด๋™๋ฒ„ํŠผ</button>
  )
}

useNavigate() ์‚ฌ์šฉ๋ฒ•

  • useNavigate(): ํŽ˜์ด์ง€ ์ด๋™์‹œ์ผœ์ฃผ๋Š” ํ•จ์ˆ˜ ๋ฐ˜ํ™˜
  • navigate('/detail'): /detail ํŽ˜์ด์ง€๋กœ ์ด๋™
  • navigate(-1): ๋’ค๋กœ 1๋ฒˆ ๊ฐ€๊ธฐ
  • navigate(2): ์•ž์œผ๋กœ 2๋ฒˆ ๊ฐ€๊ธฐ

์ˆซ์ž๋ฅผ ๋„ฃ์œผ๋ฉด ๋ธŒ๋ผ์šฐ์ € ํžˆ์Šคํ† ๋ฆฌ ๊ธฐ๋ฐ˜ ์ด๋™์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค! โฎ๏ธโญ๏ธ

2. 404 ํŽ˜์ด์ง€ ๋งŒ๋“ค๊ธฐ โŒ

์œ ์ €๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฒฝ๋กœ๋กœ ์ ‘์†ํ–ˆ์„ ๋•Œ "์—†๋Š” ํŽ˜์ด์ง€์ž…๋‹ˆ๋‹ค" ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด์—ฌ์ฃผ๋ ค๋ฉด:

<Route path="*" element={ <div>์—†๋Š”ํŽ˜์ด์ง€์ž„</div> } />

์ž‘๋™ ์›๋ฆฌ

  • path="*": ๋ชจ๋“  ๊ฒฝ๋กœ๋ฅผ ์˜๋ฏธ
  • ์œ„์—์„œ ์ •์˜ํ•œ ๊ฒฝ๋กœ๋“ค(/detail ๋“ฑ)์ด ์•„๋‹Œ ์ด์ƒํ•œ ํŽ˜์ด์ง€ ์ ‘์† ์‹œ * ๊ฒฝ๋กœ๋กœ ์•ˆ๋‚ด
  • ๋งจ ๋ฐ‘์— ์ž‘์„ฑํ•ด์•ผ ํ•จ (๋‹ค๋ฅธ ๋ผ์šฐํŠธ๋“ค ๋‹ค์Œ์—)

3. Nested Routes - ์„œ๋ธŒ๊ฒฝ๋กœ ๋งŒ๋“ค๊ธฐ ๐Ÿ—‚๏ธ

์‚ฌ์šฉ ์‚ฌ๋ก€

  • /about/member: ํšŒ์‚ฌ ๋ฉค๋ฒ„ ์†Œ๊ฐœ ํŽ˜์ด์ง€
  • /about/location: ํšŒ์‚ฌ ์œ„์น˜ ์†Œ๊ฐœ ํŽ˜์ด์ง€

๊ธฐ๋ณธ ๋ฐฉ๋ฒ•

<Route path="/about/member" element={ <div>๋ฉค๋ฒ„๋“ค</div> } />
<Route path="/about/location" element={ <div>ํšŒ์‚ฌ์œ„์น˜</div> } />

Nested Routes ๋ฐฉ๋ฒ• (๊ถŒ์žฅ)

<Route path="/about" element={ <About/> }>  
  <Route path="member" element={ <div>๋ฉค๋ฒ„๋“ค</div> } />
  <Route path="location" element={ <div>ํšŒ์‚ฌ์œ„์น˜</div> } />
</Route>

์ž‘๋™ ์›๋ฆฌ

  • <Route> ์•ˆ์— <Route>๋ฅผ ๋„ฃ๋Š” ๊ฒƒ์ด Nested routes
  • /about/member ์ ‘์† ์‹œ: <About> + <div>๋ฉค๋ฒ„๋“ค</div> ํ‘œ์‹œ
  • /about/location ์ ‘์† ์‹œ: <About> + <div>ํšŒ์‚ฌ์œ„์น˜</div> ํ‘œ์‹œ

Outlet ์ปดํฌ๋„ŒํŠธ ์‚ฌ์šฉํ•˜๊ธฐ ๐Ÿ“

Nested routes์—์„œ ์ž์‹ element๋“ค์„ ์–ด๋””์— ๋ณด์—ฌ์ค„์ง€ ์ง€์ •ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ:

function About(){
  return (
    <div>
      <h4>aboutํŽ˜์ด์ง€์ž„</h4>
      <Outlet></Outlet>
    </div>
  )
}

Outlet์˜ ์—ญํ• 

  • <Outlet>: nested routes ์•ˆ์˜ element๋“ค์ด ํ‘œ์‹œ๋  ์œ„์น˜
  • /about/member ์ ‘์† ์‹œ <Outlet> ์ž๋ฆฌ์— <div>๋ฉค๋ฒ„๋“ค</div> ํ‘œ์‹œ
  • ๋ฐ˜๋“œ์‹œ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์— Outlet์„ ๋ฐฐ์น˜ํ•ด์•ผ ์ž์‹ ์š”์†Œ๊ฐ€ ๋ณด์ž„!

๋ผ์šฐํ„ฐ๋กœ ๋™์  UI ๋งŒ๋“ค๊ธฐ ๐ŸŽญ

URL์„ ๋ฐ”๊ฟ€ ๋•Œ๋งˆ๋‹ค ๋‹ค๋ฅธ UI๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ๋„ ๋™์  UI๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค!

๋ผ์šฐํ„ฐ ์‚ฌ์šฉ์˜ ์žฅ์ 

  • ๋’ค๋กœ๊ฐ€๊ธฐ ๋ฒ„ํŠผ ์‚ฌ์šฉ ๊ฐ€๋Šฅ โฌ…๏ธ
  • URL ๊ณต์œ  ๊ฐ€๋Šฅ ๐Ÿ”—
  • ๋ธŒ๋ผ์šฐ์ € ํžˆ์Šคํ† ๋ฆฌ ๊ด€๋ฆฌ ๐Ÿ“š

๋ณด์ถฉ ์„ค๋ช… ๐Ÿ’ก

Link ์ปดํฌ๋„ŒํŠธ:

<Link to="/detail">์ƒ์„ธํŽ˜์ด์ง€๋กœ</Link>
  • ๊ฐ„๋‹จํ•œ ๋งํฌ์— ์ ํ•ฉ
  • ์ ‘๊ทผ์„ฑ ์ข‹์Œ (์Šคํฌ๋ฆฐ ๋ฆฌ๋” ๋“ฑ)
  • SEO ์นœํ™”์ 

useNavigate ํ›…:

const navigate = useNavigate();

const handleSubmit = (data) => {
  // ํผ ์ œ์ถœ ํ›„ ํŽ˜์ด์ง€ ์ด๋™
  submitForm(data);
  navigate('/success');
};
  • ์กฐ๊ฑด๋ถ€ ๋„ค๋น„๊ฒŒ์ด์…˜์— ์ ํ•ฉ
  • ํ”„๋กœ๊ทธ๋ž˜๋ฐ์  ์ œ์–ด ๊ฐ€๋Šฅ
  • ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ์•ˆ์—์„œ ์‚ฌ์šฉ

๊ณ ๊ธ‰ useNavigate ์‚ฌ์šฉ๋ฒ• ๐Ÿš€

1. replace ์˜ต์…˜

// ํžˆ์Šคํ† ๋ฆฌ์— ์ƒˆ ํ•ญ๋ชฉ ์ถ”๊ฐ€ (๊ธฐ๋ณธ๊ฐ’)
navigate('/login');

// ํ˜„์žฌ ํžˆ์Šคํ† ๋ฆฌ ํ•ญ๋ชฉ ๊ต์ฒด (๋’ค๋กœ๊ฐ€๊ธฐ ์‹œ ์ด์ „ ํŽ˜์ด์ง€๋กœ ์•ˆ ๊ฐ)
navigate('/login', { replace: true });

2. state ์ „๋‹ฌ

navigate('/profile', { 
  state: { 
    from: location.pathname,
    userInfo: userData 
  } 
});

// ๋ชฉ์ ์ง€์—์„œ state ๋ฐ›๊ธฐ
const location = useLocation();
const { from, userInfo } = location.state || {};

Nested Routes ์‹ค์ „ ํ™œ์šฉ ๐ŸŽฏ

1. ๋Œ€์‹œ๋ณด๋“œ ๋ ˆ์ด์•„์›ƒ

<Route path="/dashboard" element={<DashboardLayout />}>
  <Route index element={<DashboardHome />} />
  <Route path="analytics" element={<Analytics />} />
  <Route path="settings" element={<Settings />} />
  <Route path="profile" element={<Profile />} />
</Route>

function DashboardLayout() {
  return (
    <div className="dashboard">
      <Sidebar />
      <main>
        <Outlet />
      </main>
    </div>
  );
}

2. index ๋ผ์šฐํŠธ ํ™œ์šฉ

<Route path="/products" element={<ProductsLayout />}>
  <Route index element={<ProductsList />} />  {/* /products */}
  <Route path="new" element={<NewProduct />} />  {/* /products/new */}
  <Route path=":id" element={<ProductDetail />} />  {/* /products/123 */}
</Route>

๋ผ์šฐํŠธ ๋ณดํ˜ธํ•˜๊ธฐ ๐Ÿ›ก๏ธ

1. ์ธ์ฆ์ด ํ•„์š”ํ•œ ๋ผ์šฐํŠธ

function ProtectedRoute({ children }) {
  const navigate = useNavigate();
  const { user } = useAuth();
  
  useEffect(() => {
    if (!user) {
      navigate('/login', { replace: true });
    }
  }, [user, navigate]);
  
  return user ? children : null;
}

<Route path="/admin" element={
  <ProtectedRoute>
    <AdminPanel />
  </ProtectedRoute>
} />

2. ๊ถŒํ•œ ๊ธฐ๋ฐ˜ ๋ผ์šฐํŒ…

function RoleBasedRoute({ children, requiredRole }) {
  const { user } = useAuth();
  
  if (!user || user.role !== requiredRole) {
    return <Navigate to="/unauthorized" replace />;
  }
  
  return children;
}

์—๋Ÿฌ ๋ฐ”์šด๋”๋ฆฌ์™€ 404 ์ฒ˜๋ฆฌ โŒ

1. ๋ผ์šฐํŠธ๋ณ„ ์—๋Ÿฌ ๋ฐ”์šด๋”๋ฆฌ

<Route path="/dashboard" element={
  <ErrorBoundary fallback={<DashboardError />}>
    <Dashboard />
  </ErrorBoundary>
} />

2. ๊ณ„์ธต์  404 ์ฒ˜๋ฆฌ

<Routes>
  <Route path="/" element={<Layout />}>
    <Route index element={<Home />} />
    <Route path="products" element={<Products />} />
    <Route path="*" element={<NotFound />} />
  </Route>
</Routes>

์„ฑ๋Šฅ ์ตœ์ ํ™” ํŒ โšก

1. ๋ผ์šฐํŠธ ๊ธฐ๋ฐ˜ ์ฝ”๋“œ ์Šคํ”Œ๋ฆฌํŒ…

const Dashboard = lazy(() => import('./pages/Dashboard'));
const Profile = lazy(() => import('./pages/Profile'));

<Route path="/dashboard" element={
  <Suspense fallback={<LoadingSpinner />}>
    <Dashboard />
  </Suspense>
} />

2. ํ”„๋ฆฌ๋กœ๋”ฉ

// ๋งˆ์šฐ์Šค ํ˜ธ๋ฒ„ ์‹œ ์ปดํฌ๋„ŒํŠธ ๋ฏธ๋ฆฌ ๋กœ๋“œ
const DashboardLink = () => {
  const handleMouseEnter = () => {
    import('./pages/Dashboard'); // ํ”„๋ฆฌ๋กœ๋“œ
  };
  
  return (
    <Link to="/dashboard" onMouseEnter={handleMouseEnter}>
      ๋Œ€์‹œ๋ณด๋“œ
    </Link>
  );
};

0๊ฐœ์˜ ๋Œ“๊ธ€