๐ป ์ค์ ํ๋ก๊ทธ๋๋ฐ | React ๋ผ์ฐํ (Routing) ์๋ฒฝ ์ ๋ฆฌ ๋ฐ ํ์ฉ๋ฒ ๊ฐ์ด๋
๐ ๋ค์ด๊ฐ๋ฉฐ - ๋ผ์ฐํ (Routing)์ด๋ ๋ฌด์์ธ๊ฐ?
์น ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ฌ์ฉ์๊ฐ ํน์ URL์ ์ ๋ ฅํ๊ฑฐ๋ ํด๋ฆญํ ๋, ํด๋น URL์ ๋ถ์ํ๊ณ ์ฌ๋ฐ๋ฅธ ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉ์์๊ฒ ๋ณด์ฌ์ฃผ๋ ๊ณผ์ ์ ๋ผ์ฐํ (Routing)์ด๋ผ๊ณ ํฉ๋๋ค. ํนํ ๋ฆฌ์กํธ(React)๋ฅผ ์ฌ์ฉํ๋ ์ต์ ์น ์ฑ์ Single Page Application(SPA) ๋ฐฉ์์ ์ฑํํ๊ธฐ ๋๋ฌธ์ ๋ผ์ฐํ ์ ์ค์์ฑ์ด ๋งค์ฐ ํฝ๋๋ค.
๐ React ๋ผ์ฐํ ์ ํต์ฌ ํน์ง ์ ๋ฆฌ
- ๋์ ํ์ด์ง ์ ํ: ํ์ด์ง ์ ์ฒด๋ฅผ ์๋ก ๋ถ๋ฌ์ค์ง ์๊ณ ๋ ๋น ๋ฅด๊ฒ ํ๋ฉด ์ ํ ๊ฐ๋ฅ
- URL ํ๋ผ๋ฏธํฐ๋ ์ฟผ๋ฆฌ ์คํธ๋ง: ํ์ํ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ ๋ URL ํ๋ผ๋ฏธํฐ์ ์ฟผ๋ฆฌ ์คํธ๋ง์ ํ์ฉ
- ์ค์ฒฉ๋ ๋ผ์ฐํ (Nested Routing): ๋ณต์กํ UI๋ฅผ ๊ฐ๊ฒฐํ๊ฒ ์ ์งํ๊ณ ๊ตฌ์กฐํ ๊ฐ๋ฅ
- ๋ธ๋ผ์ฐ์ ํ์คํ ๋ฆฌ ์ ์ด: ๋ธ๋ผ์ฐ์ ๋ค๋ก ๊ฐ๊ธฐ/์์ผ๋ก ๊ฐ๊ธฐ ๋ฑ ๊ธฐ๋ณธ์ ์ธ ์น ํ๊ฒฝ๊ณผ์ ํธํ์ฑ ์ ์ง
๐ง ๋ฆฌ์กํธ ๋ผ์ฐํฐ(react-router)์ ์ฅ๋จ์ ๋ถ์
- ์ฅ์ :
- SPA ๊ฐ๋ฐ์์ ๋น ๋ฅด๊ณ ํจ์จ์ ์ธ ํ์ด์ง ์ ํ
- ๋ค์ํ Hook ๋ฐ ์ปดํฌ๋ํธ ์ ๊ณต์ผ๋ก ๋์ฑ ์ฌ์ด ์ฌ์ฉ๋ฒ
- ๊ธฐ์กด ์น ํ์ค ๋ธ๋ผ์ฐ์ API์์ ๋ฐ์ด๋ ํธํ์ฑ
- ๋จ์ :
- ์ด๊ธฐ ์ค์ ๊ณผ ์ดํด ํ์์ฑ ๋์
- ๋๊ท๋ชจ ์ ํ๋ฆฌ์ผ์ด์ ์์๋ ๊ผผ๊ผผํ ์ค๊ณ๊ฐ ์ค์
๐ ๏ธ ์ค์ ์ ์ฉ ์์ ์ฝ๋ ๋ฐ ํ์ฉ๋ฒ
๊ธฐ๋ณธ ์ค์น ๋ฐ ์ค์ ํ๊ธฐ
// ๋ผ์ฐํฐ ๊ด๋ จ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น
npm install react-router-dom @types/react-router-dom
// index.tsx์ BrowserRouter ์ ์ฉ
import { BrowserRouter } from 'react-router-dom';
ReactDOM.createRoot(document.getElementById('root')!).render(
<BrowserRouter>
<App />
</BrowserRouter>
);
Route๋ก ์ปดํฌ๋ํธ ์ฐ๋ํ๊ธฐ
// App.tsx
import { Routes, Route } from 'react-router-dom';
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/profiles/:username" element={<Profile />} />
</Routes>
);
}
์ค์ฒฉ ๋ผ์ฐํ ์์
// ์ค์ฒฉ ๋ผ์ฐํ
์ค์ ํ๊ธฐ
<Routes>
<Route path="articles" element={<Articles />}>
<Route path=":id" element={<Article />} />
</Route>
</Routes>
๐ Link์ NavLink ์ฌ์ฉํ์ฌ ํ์ด์ง ์ ํํ๊ธฐ
// Link ํ์ฉ ์์
<Link to="/about">์๊ฐ ํ์ด์ง</Link>
// NavLink๋ก ํ์ฑํ๋ ๋งํฌ ์คํ์ผ ์ ์ฉํ๊ธฐ
<NavLink
to="/articles/1"
style={({ isActive }) => (isActive ? {color:'green', fontWeight:'bold'} : undefined)}
>
๊ฒ์๊ธ 1
</NavLink>
๐ก URL ํ๋ผ๋ฏธํฐ์ ์ฟผ๋ฆฌ ์คํธ๋ง ํธ๋ฆฌํ๊ฒ ๋ค๋ฃจ๊ธฐ
// ํ๋ผ๋ฏธํฐ ์ ๊ทผ
import { useParams } from 'react-router-dom';
const { username } = useParams();
// ์ฟผ๋ฆฌ ์คํธ๋ง ์ ๊ทผ
import { useSearchParams } from 'react-router-dom';
const [searchParams, setSearchParams] = useSearchParams();
let detail = searchParams.get("detail");
// ์ฟผ๋ฆฌ ๋ณ๊ฒฝ ์์
setSearchParams({ detail: "true", page: "2" });
๐ฆ NotFound ํ์ด์ง ์ค์ ํ์ฌ ๊ณ ๊ฐ ๊ฒฝํ ๊ฐ์ ํ๊ธฐ
// NotFound ๋ผ์ฐํ
์ถ๊ฐ
<Route path="*" element={<NotFound />} />
๐ฎ ํธ๋ ๋ ๋ฐ ์ ๋ฌธ๊ฐ ์๊ฒฌ๊ณผ ๋ฏธ๋ ์ ๋ง
์ต๊ทผ Next.js์ ๊ฐ์ ํ๋ ์์ํฌ๋ ํ์ผ ๊ธฐ๋ฐ์ ๋ผ์ฐํ ์์คํ ์ ์ฌ์ฉํ๋ฉฐ, ์๋ฒ ์ฌ์ด๋ ๋ ๋๋ง(SSR) ๋ฐ ์ ์ ์ฌ์ดํธ ์์ฑ(SSG) ๊ธฐ๋ฅ์ ์ ๊ณตํ๊ณ ์์ต๋๋ค. ๋ฆฌ์กํธ ๋ผ์ฐํฐ์ ๊ฐ์ ํด๋ผ์ด์ธํธ ๋ผ์ฐํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์ฌ์ ํ SPA ๊ตฌ์ถ์์ ์ ์ฉํ๊ณ ํญ๋๊ฒ ํ์ฉ๋๊ณ ์์ง๋ง, SEO๋ ํผํฌ๋จผ์ค ์ต์ ํ๋ฅผ ๊ณ ๋ คํ SSR/SSG ํ๊ฒฝ์์๋ Next.js์ ๊ฐ์ ๋๊ตฌ๋ก ์ ์ ๋ ์ ํ๋ ๊ฐ๋ฅ์ฑ๋ ๋์ต๋๋ค.
์ ๋ฌธ๊ฐ๋ค์ ์๊ฒฌ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- React Router๋ ์ฌ์ฉ์ฑ์ด ๋ฐ์ด๋ ๋น ๋ฅด๊ฒ ์ธํฐ๋ํฐ๋ธํ SPA ๊ตฌ์ถ ํ๊ฒฝ์ ์ฌ์ ํ ๋์ ํ์ฉ์ฑ์ ๋ณด์ด๊ณ ์๋ค.
- ์ ์ ๋ ๋ง์ ๊ธฐ์ ๋ค์ด SEO์ ์ด๊ธฐ ๋ก๋ฉ ์๋ ์ต์ ํ๋ฅผ ์ํด Next.js๋ Remix์ ๊ฐ์ ์๋ฒ ๊ธฐ๋ฐ ํ๋ ์์ํฌ๋ฅผ ์ ํํ๋ ๊ฒฝํฅ์ด ๋๋ ทํด์ง๊ณ ์๋ค.
โ ์์ฝ ๋ฐ ๋ง๋ฌด๋ฆฌ
๋ฆฌ์กํธ ๋ผ์ฐํ ์ ๋น ๋ฅด๊ณ ํจ์จ์ ์ธ SPA ์น์ฌ์ดํธ ๊ฐ๋ฐ์ ํ์ ๊ธฐ์ ์ ๋๋ค. ๊ธฐ๋ณธ ๊ฐ๋ ๊ณผ ์ค์ต ์์ ๋ฅผ ์ถฉ๋ถํ ์ดํดํ๊ณ ํ์ฉํ๋ฉด ๊ฐ๋ฐ์๋ ๋์ฑ ์ฌ์ฉ์ฑ ์ข๊ณ ๋งค๋๋ฌ์ด ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๊ณตํ๋ ์น ์ฑ์ ๊ตฌ์ถํ ์ ์์ต๋๋ค. ์์ผ๋ก์ ์จ๋ผ์ธ ํ๊ฒฝ์ด ์ ์ ๋ SPA ๋ฐฉ์๊ณผ ์๋ฒ ๋ ๋๋ง์ ํผํฉ ํํ๋ก ๋ฐ์ ํ๋ ๋งํผ ์ต์ ์น ๊ธฐ์ ์ ํ๋ฆ์ ์ ํ์ ํ๊ณ ์ ์ฉํ๋ ๊ฒ์ด ์ค์ํ๊ฒ ์ต๋๋ค.
๋๊ธ
๋๊ธ ์ฐ๊ธฐ