티스토리 뷰
이 글은 Frontend Development 101 시리즈의 다섯 번째 글입니다. 여기서는 SPA가 여러 화면을 어떻게 표현하는지 URL 중심으로 설명합니다. URL은 단순한 주소가 아니라 현재 화면을 설명하는 상태이며, 라우터는 그 상태를 읽어 어떤 컴포넌트를 그릴지 결정하는 계층입니다.
한 화면짜리 앱은 비교적 단순합니다. 하지만 제품이 커지면 홈, 상세, 설정, 검색 결과처럼 여러 화면이 생기고, 사용자는 뒤로 가기와 새로고침과 링크 공유가 모두 자연스럽게 되기를 기대합니다. 이 기대를 만족시키는 핵심이 라우팅입니다.

라우팅은 'URL과 컴포넌트 트리를 매핑하는 일'입니다 — SPA / MPA / SSR / SSG 차이는 '이 매핑을 누가 언제 수행하는가(브라우저인가, 서버인가, 빌드 타임인가)'라는 한 가지 축으로 정리됩니다.
먼저 던지는 질문
- 단일 페이지 앱이 여러 화면을 보여 주는 원리는 무엇일까요?
- 경로(path)는 컴포넌트와 어떤 식으로 매핑될까요?
- 중첩 라우트와 동적 파라미터는 왜 필요한가요?
왜 중요한가
라우팅은 새로고침해도 같은 화면이 다시 열리고, 링크를 복사해 다른 사람과 공유할 수 있으며, 뒤로 가기 버튼이 자연스럽게 동작하게 만듭니다. 이 기본기가 무너지면 사용자는 제품 전체를 불안정하게 느낍니다.
좋은 라우팅은 URL만 보고도 현재 화면을 어느 정도 짐작할 수 있게 만듭니다. 반대로 URL이 화면 상태를 설명하지 못하면 검색, 필터, 상세 페이지 같은 기능이 곧바로 불편해집니다.
개념 한눈에 보기
결국 라우팅은 URL 패턴을 해석해 컴포넌트 트리를 고르는 일입니다. 이 모델만 잡혀도 정적 경로, 동적 경로, 중첩 경로를 같은 방식으로 읽을 수 있습니다.
핵심 용어
| 용어 | 뜻 | 실무에서 왜 중요한가 |
|---|---|---|
| Route | URL 패턴과 컴포넌트의 매핑입니다. | 어떤 화면이 어떤 주소에서 열리는지 설명하는 기본 단위입니다. |
| 중첩 라우트 | 다른 라우트 안에 들어가는 하위 라우트입니다. | 레이아웃과 하위 화면을 함께 묶는 구조를 만들 수 있습니다. |
| 동적 세그먼트 | /users/:id처럼 값이 들어갈 자리를 포함한 경로 패턴입니다. |
상세 페이지나 편집 화면처럼 개체별 URL을 자연스럽게 표현합니다. |
| 쿼리 문자열 | ?q=react&page=2처럼 경로 밖에 붙는 추가 상태입니다. |
검색, 정렬, 필터처럼 공유 가능한 화면 상태를 보존합니다. |
| Lazy loading | 필요한 라우트 코드만 나중에 불러오는 방식입니다. | 초기 번들을 줄이고 첫 화면 응답 속도를 높이는 데 직접 연결됩니다. |
전체 새로고침에서 URL 기반 화면 전환으로
라우팅의 핵심 변화는 화면 이동을 서버 문서 전환에서 브라우저 애플리케이션 상태 전환으로 옮겼다는 점입니다. 그래서 링크, 뒤로 가기, 새로고침, 공유 주소가 모두 같은 설계 문제로 묶입니다.
| 방식 | 화면 이동 방식 | 실무 영향 |
|---|---|---|
| 서버 라우팅 + 전체 새로고침 | 클릭할 때마다 새 문서를 요청합니다. | 단순하지만 상호작용이 많은 앱에서는 전환 비용이 큽니다. |
| SPA 라우팅 | URL은 유지하면서 필요한 화면만 교체합니다. | 사용자 경험은 매끄럽지만, 새로고침과 배포 설정까지 같이 신경 써야 합니다. |
Before (서버 라우팅, 전체 새로고침)
<a href="/about">About</a>
After (SPA routing, smooth transition)
<Link to="/about">About</Link>
이 차이를 이해하면 왜 <Link> 하나가 단순한 문법 차이를 넘어 사용자 경험과 배포 설정까지 바꾸는지 자연스럽게 보입니다.
실습: 리액트 라우터를 5단계로 적용하기
1단계 — Install
npm install react-router-dom
2단계 — Define routes
import { createBrowserRouter, RouterProvider } from "react-router-dom";
const router = createBrowserRouter([
{ path: "/", element: <Home /> },
{ path: "/about", element: <About /> },
]);
<RouterProvider router={router} />
3단계 — Use Link
import { Link } from "react-router-dom";
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
4단계 — Dynamic parameters
{ path: "/users/:id", element: <UserDetail /> }
import { useParams } from "react-router-dom";
function UserDetail() {
const { id } = useParams();
return <p>user {id}</p>;
}
5단계 — Lazy loading
import { lazy } from "react";
const Settings = lazy(() => import("./Settings"));
{ path: "/settings", element: <Suspense><Settings /></Suspense> }
이 흐름을 보면 라우팅이 단순한 링크 모음이 아니라는 점이 드러납니다. 설치와 경로 정의, 링크 연결, 파라미터 읽기, 코드 분할까지 이어져야 실제 제품에서 쓸 수 있는 라우팅이 됩니다.
검증 포인트
/,/about,/users/42로 이동했을 때 새로고침 없이 화면이 바뀌고 파라미터가 올바르게 보이는지 확인합니다.- Network 탭에서 lazy loading한 라우트가 별도 청크로 내려오는지 확인합니다.
문제가 생기면 먼저 볼 것
- 상세 페이지가 비면
useParams()결과와 라우트 패턴/users/:id가 일치하는지 확인합니다. - 배포 후 새로고침 404가 나면 호스팅 환경에 SPA history fallback 설정이 필요한지 봅니다.
이 코드에서 주목할 점
<Link>는 전체 새로고침 없이 라우터 상태만 바꿉니다.useParams는 동적 세그먼트를 실제 값으로 꺼내 줍니다.- Lazy loading은 초기 번들 크기를 줄여 첫 화면 성능을 지켜 줍니다.
자주 하는 실수 5가지
<a>와<Link>를 섞어 씁니다. 전체 새로고침이 발생해 SPA의 장점이 사라집니다.- 인증이 필요한 경로를 보호하지 않습니다. URL을 직접 입력하면 우회 접근이 생길 수 있습니다.
- 라우트가 많은데 lazy loading을 건너뜁니다. 초기 로딩이 매우 무거워집니다.
- 쿼리 문자열과 화면 상태를 동기화하지 않습니다. 검색 결과나 필터 상태가 새로고침에서 사라집니다.
- 404 페이지가 없습니다. 잘못된 URL에서 사용자가 하얀 화면을 만나게 됩니다.
실무에서는 이렇게 보입니다
최근에는 Next.js, Remix, Nuxt처럼 파일 기반 라우팅을 제공하는 프레임워크가 널리 쓰입니다. pages/users/[id].tsx 같은 파일 구조가 곧 라우트 정의가 됩니다. 직접 배열로 경로를 하나씩 나열하는 방식은 점점 줄어드는 추세입니다.
하지만 형식이 무엇이든 핵심은 같습니다. URL이 상태를 설명해야 하고, 인증과 권한과 404 처리와 코드 분할까지 함께 설계해야 합니다.
시니어 엔지니어는 이렇게 생각합니다
- URL은 공유 가능한 상태입니다.
- 인증과 권한 경계는 라우팅 설계 초반부터 반영합니다.
- 라우트가 많아질수록 코드 스플리팅은 선택이 아니라 기본입니다.
- 검색과 필터 상태는 쿼리 문자열에 넣어 공유 가능하게 만듭니다.
- 404 화면도 친절해야 하며 돌아갈 길을 제공해야 합니다.
체크리스트
- 정적 라우트와 동적 라우트를 구분할 수 있습니다.
-
<Link>와<a>의 차이를 설명할 수 있습니다. -
useParams로 파라미터를 읽을 수 있습니다. - lazy loading을 한 번 설정해 봤습니다.
- 404 페이지를 만들 수 있습니다.
연습 문제
/,/about,/users/:id,/*(404) 네 개의 라우트를 만들어 보세요./users/:id화면에서useParams로 값을 표시해 보세요./settings라우트를 lazy loading으로 분리하고 Network 탭에서 별도 청크를 확인해 보세요.
정리 및 다음 단계
라우팅은 사용자가 무엇을 보는지 결정하는 URL 기반 상태 관리입니다. 이 흐름이 잡히면 이제 화면이 서버 데이터와 어떻게 연결되는지도 자연스럽게 이어집니다.
다음 글에서는 프론트엔드가 서버에서 데이터를 가져오는 비동기 흐름을 봅니다.
처음 질문으로 돌아가기
- 단일 페이지 앱이 여러 화면을 보여 주는 원리는 무엇일까요?
- SPA는 문서를 통째로 다시 받지 않고 현재 URL을 읽어 어떤 컴포넌트를 그릴지 바꾸는 방식으로 여러 화면을 보여 줍니다. 본문의
createBrowserRouter예제에서/는Home,/about는About을 선택하듯이, 화면 전환의 중심은 새 HTML 요청이 아니라 라우터 상태입니다.
- SPA는 문서를 통째로 다시 받지 않고 현재 URL을 읽어 어떤 컴포넌트를 그릴지 바꾸는 방식으로 여러 화면을 보여 줍니다. 본문의
- 경로(path)는 컴포넌트와 어떤 식으로 매핑될까요?
- 라우터 배열에서
path와element를 짝으로 두면 URL 패턴이 바로 컴포넌트 트리 선택 규칙이 됩니다.<Link to="/about">는 그 매핑을 이용해 전체 새로고침 없이 경로만 바꾸고, 라우터는 대응하는 화면 조각만 다시 렌더링합니다.
- 라우터 배열에서
- 중첩 라우트와 동적 파라미터는 왜 필요한가요?
/users/:id처럼 동적 세그먼트를 쓰면 상세 페이지 구조는 유지한 채42같은 실제 식별자만 바꿔 여러 자원을 한 패턴으로 표현할 수 있습니다. 여기에 중첩 라우트를 더하면 공통 레이아웃 아래에서 하위 화면을 자연스럽게 조합할 수 있어, 실제 제품의 설정 화면이나 사용자 영역처럼 계층적인 UI를 다루기 쉬워집니다.
시리즈 목차
- Frontend Development 101 (1/10): 프론트엔드 개발이란 무엇인가?
- Frontend Development 101 (2/10): HTML과 CSS 기본
- Frontend Development 101 (3/10): JavaScript 기본
- Frontend Development 101 (4/10): 컴포넌트와 상태
- 라우팅과 페이지 (현재 글)
- API 호출과 비동기 (예정)
- 폼과 유효성 검사 (예정)
- 스타일링과 디자인 시스템 (예정)
- 빌드 도구와 번들링 (예정)
- 작은 프론트엔드 앱 만들기 (예정)
참고 자료
공식 문서
확인용 자료
'Software Engineering' 카테고리의 다른 글
| Frontend Development 101 (7/10): 폼과 유효성 검사 (0) | 2026.05.27 |
|---|---|
| Frontend Development 101 (6/10): API 호출과 비동기 (0) | 2026.05.27 |
| Frontend Development 101 (4/10): 컴포넌트와 상태 (0) | 2026.05.27 |
| Frontend Development 101 (3/10): JavaScript 기본 (0) | 2026.05.27 |
| Frontend Development 101 (2/10): HTML과 CSS 기본 (0) | 2026.05.27 |
- Total
- Today
- Yesterday
- ai safety
- rag
- langchain
- docker
- APIDesign
- backend
- LLM
- ai agent
- Cloud
- Tool Use
- reliability
- Computer Science
- frontend
- DesignPatterns
- embeddings
- Python
- softwaredesign
- DevOps
- Architecture
- QUALITY
- http
- Production
- AZURE
- AI Evaluation
- Agent
- Kubernetes
- openAI
- webdevelopment
- testing
- vector search
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |

