React 진입점과 라우팅 설정
main.jsx에서 앱을 시작하고 React Router로 페이지를 나누는 방법
코드 블록의 Try it Yourself 버튼으로 직접 실행할 수 있다.
구문
Tip: Gemini CLI로 구현하기 — 진입점 & 라우팅 설정
- 프롬프트:
gemini "React 19 + Vite 프로젝트에서 main.jsx와 App.jsx를 작성해줘. main.jsx에는 BrowserRouter로 앱을 감싸고, App.jsx에는 Routes를 구성해줘. 페이지 컴포넌트는 Home, MovieDetail, Category, ErrorPage이고, 경로는 각각 /, /movie/:id, /category/:type, * 야. [파일 경로: src/]"
- 사용 가이드:
[파일 경로]를 실제 src 폴더 경로로 바꾼다. 생성된 파일을src/폴더에 저장한다.
1. React 앱은 어디서 시작될까?
브라우저가 웹사이트를 열면, 가장 먼저 index.html 파일을 읽는다. 이 파일 안에 React가 연결되는 출발점이 있다.
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>frontend</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
| 줄 | 설명 |
|---|---|
| 10 | 핵심! React가 이 div 안에 모든 화면을 그린다. id="root"가 React의 시작점이다. |
| 11 | main.jsx 파일을 불러온다. 이 파일이 React 앱을 실제로 실행시킨다. |
Info: React는 빈
div하나에 JavaScript로 화면을 그리는 방식이다. 이것을 SPA(Single Page Application, 싱글 페이지 애플리케이션)라고 부른다.
2. main.jsx 작성 — React의 출발점
src/main.jsx 파일을 열고 아래 코드를 작성한다.
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App.jsx";
import { Home } from "./components/Home.jsx";
import { MovieDetail } from "./components/MovieDetail.jsx";
import { ErrorPage } from "./components/ErrorPage.jsx";
import { Category } from "./components/Category.jsx";
import { createBrowserRouter, RouterProvider } from "react-router";
const router = createBrowserRouter([
{
path: "/",
element: <App />,
errorElement: <ErrorPage />,
children: [
{ index: true, element: <Home /> },
{ path: "movie/:id", element: <MovieDetail /> },
{ path: "category/:type", element: <Category /> },
],
},
]);
createRoot(document.getElementById("root")).render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>,
);
코드가 세 부분으로 나뉜다. 탭을 눌러 각 역할을 확인해 보자.
① import 영역
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App.jsx";
import { Home } from "./components/Home.jsx";
import { MovieDetail } from "./components/MovieDetail.jsx";
import { ErrorPage } from "./components/ErrorPage.jsx";
import { Category } from "./components/Category.jsx";
import { createBrowserRouter, RouterProvider } from "react-router";
| 줄 | 설명 |
|---|---|
| 1 | StrictMode(스트릭트모드) — 개발 중 잠재 문제를 경고한다. |
| 2 | createRoot — React 앱을 DOM에 연결하는 함수이다. |
| 3 | Tailwind CSS + 폰트 설정 파일을 불러온다. |
| 5 | 전체 레이아웃 역할의 App 컴포넌트이다. |
| 6-10 | 각 페이지 컴포넌트를 가져온다. 아직 만들지 않았으므로 나중에 하나씩 만든다. |
| 11 | URL 경로와 컴포넌트를 연결하는 라우팅 도구이다. |
② 라우터 설정
const router = createBrowserRouter([
{
path: "/",
element: <App />,
errorElement: <ErrorPage />,
children: [
{ index: true, element: <Home /> },
{ path: "movie/:id", element: <MovieDetail /> },
{ path: "category/:type", element: <Category /> },
],
},
]);
| 줄 | 설명 |
|---|---|
| 3 | path: "/"는 메인 페이지(홈)의 경로이다. |
| 4 | 메인 경로에서 App 컴포넌트를 렌더링한다. |
| 5 | 존재하지 않는 URL에 접속하면 ErrorPage를 보여준다. |
| 7 | index: true — "/"에 접속하면 기본으로 Home을 표시한다. |
| 8 | movie/:id — :id는 동적 파라미터. movie/550처럼 숫자가 바뀐다. |
| 9 | category/:type — :type은 popular, now_playing, top_rated 등이 온다. |
③ 렌더링
createRoot(document.getElementById("root")).render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>,
);
| 줄 | 설명 |
|---|---|
| 1 | index.html의 div#root를 찾아 React 앱을 연결한다. |
| 2-4 | StrictMode로 앱 전체를 감싸고, RouterProvider에 라우터를 전달한다. |
3. 라우팅의 동작 원리
| URL | 표시되는 컴포넌트 |
|---|---|
http://localhost:5173/ | App 안에 Home 표시 |
http://localhost:5173/movie/550 | App 안에 MovieDetail 표시 |
http://localhost:5173/search?q=어벤져스 | App 안에 Search 표시 |
http://localhost:5173/category/popular | App 안에 Category 표시 |
| 존재하지 않는 URL | ErrorPage 표시 |
App 컴포넌트는 항상 화면에 있고(헤더+푸터), URL이 바뀌면 App 안쪽 내용만 교체된다. 이 구조를 중첩 라우팅(Nested Routing, 네스티드 라우팅)이라 한다.
Tip: 넷플릭스를 떠올려 보자. 상단의 로고와 메뉴는 항상 그대로인데, 메인 콘텐츠 영역만 바뀐다. 이것이 바로 중첩 라우팅이다.
4. 동적 라우트 파라미터 이해하기
이 프로젝트에는 두 종류의 동적 파라미터가 있다.
| 경로 | 파라미터 | 예시 URL | 값 |
|---|---|---|---|
movie/:id | :id | /movie/550 | "550" |
category/:type | :type | /category/popular | "popular" |
나중에 각 컴포넌트에서 useParams() 훅으로 이 값을 꺼내 API에 요청을 보낸다.
5. errorElement란?
errorElement: <ErrorPage />,
사용자가 존재하지 않는 URL(예: /abcdef)에 접속하면 React Router가 자동으로 ErrorPage 컴포넌트를 보여준다.
6. 아직 없는 컴포넌트 임시 생성
지금 main.jsx가 여러 컴포넌트를 import 하고 있지만, 아직 해당 파일들이 존재하지 않는다. 이 상태로 개발 서버를 실행하면 에러가 발생한다. src/components/ 폴더 안에 아래 4개 파일을 각각 생성하고, 코드를 한 줄씩 입력한다.
Home
export function Home() {
return <div>Home 페이지 (준비중)</div>;
}
MovieDetail
export function MovieDetail() {
return <div>MovieDetail 페이지 (준비중)</div>;
}
ErrorPage
export function ErrorPage() {
return <div>404 에러 페이지 (준비중)</div>;
}
Category
export function Category() {
return <div>Category 페이지 (준비중)</div>;
}
export default vs export function 차이
이 프로젝트에서는 App.jsx만 export default를 사용하고, 나머지 컴포넌트는 export function(named export)을 사용한다.
| 방식 | 코드 | 가져올 때 |
|---|---|---|
export default | export default function App() | import App from "./App" |
| named export | export function Home() | import { Home } from "./Home" |
중괄호 { } 유무에 주의한다.
7. App.jsx 임시 생성
src/App.jsx 파일을 열고 기존 내용을 모두 지운 뒤, 아래 코드로 교체한다. Outlet(아울렛)은 URL이 바뀔 때 하위 페이지가 표시되는 빈자리라고 생각하면 된다. 액자의 틀은 그대로인데 안에 들어가는 사진만 바뀌는 것과 같다.
import { Outlet } from "react-router";
export default function App() {
return (
<div>
<h1>GOFLEX</h1>
<Outlet />
</div>
);
}
8. 동작 확인
npm run dev
| 확인 항목 | 기대 결과 |
|---|---|
http://localhost:5173/ | "GOFLEX"와 "Home 페이지 (준비중)" 텍스트 표시 |
http://localhost:5173/movie/550 | "GOFLEX"와 "MovieDetail 페이지 (준비중)" 텍스트 표시 |
http://localhost:5173/abcdef | "404 에러 페이지 (준비중)" 텍스트 표시 |
9. 전체 코드
src/main.jsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App.jsx";
import { Home } from "./components/Home.jsx";
import { MovieDetail } from "./components/MovieDetail.jsx";
import { ErrorPage } from "./components/ErrorPage.jsx";
import { Category } from "./components/Category.jsx";
import { createBrowserRouter, RouterProvider } from "react-router";
const router = createBrowserRouter([
{
path: "/",
element: <App />,
errorElement: <ErrorPage />,
children: [
{ index: true, element: <Home /> },
{ path: "movie/:id", element: <MovieDetail /> },
{ path: "category/:type", element: <Category /> },
],
},
]);
createRoot(document.getElementById("root")).render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>,
);
src/App.jsx (임시)
import { Outlet } from "react-router";
export default function App() {
return (
<div>
<h1>GOFLEX</h1>
<Outlet />
</div>
);
}