본문 바로가기
C.W.K.
Stream
Lesson 08 of 10 · published

가로채기 라우트로 모달과 전체 화면 함께 쓰기

~22 min · intercepting routes, modal, overlay

Level 0호기심
0 XP0/68 lessons0/11 achievements
0/120 XP to next level120 XP to go0% complete

같은 주소를 두 맥락으로 보여 줄 수 있어

피드에서 사진을 누르면 현재 화면 위에 모달로 열리지만, 그 주소를 새로고침하거나 공유해서 들어오면 사진만 있는 전체 페이지가 열리는 경험을 떠올려 봐. URL은 같아도 사용자가 어디서 왔는지에 따라 표현이 달라. 가로채기 라우트는 이 동작을 폴더 규칙으로 표현해.

접두사는 어느 구간을 가로챌지 말해

접두사기준 위치
(.)같은 단계의 구간
(..)한 단계 위
(..)(..)두 단계 위
(...)앱 루트

병렬 슬롯과 짝을 이뤄

보통 @modal 같은 병렬 라우트 안에 가로채기 라우트를 둬. 앱 내부 이동에서는 대상 페이지를 현재 레이아웃의 모달 슬롯에 그리면서, 직접 방문할 때 사용할 원래의 전체 페이지도 그대로 보존하는 방식이야.

닫기의 의미까지 설계해

모달을 닫을 때는 단순히 숨기기보다 브라우저 기록을 되돌려 이전 맥락으로 복귀시켜야 해. 직접 방문한 전체 페이지에는 돌아갈 피드가 없을 수 있으니, 두 진입 경로를 각각 시험해야 완성된 라우팅이 돼.

가로채기 라우트는 같은 자원에 두 진입 맥락이 실제로 있을 때 써. 피드 클릭, 주소 직접 방문, 새로고침, 뒤로 가기, 새 탭 열기를 각각 시험하고 어느 경우에 모달과 전체 페이지가 보여야 하는지 표로 적어. 닫기 동작은 이전 기록이 없을 때 갈 주소도 정해야 해.

모달처럼 보인다는 이유만으로 라우트가 되어야 하는 건 아니야. 공유·새로고침·뒤로 가기에 의미가 없는 일시적 안내는 로컬 상태가 더 맞아. URL이 자원을 식별하고 사용자가 그 상태로 돌아올 가치가 있을 때만 라우팅 복잡성을 지불해. 직접 주소로 연 전체 화면과 목록 위 모달에서 닫기 동작을 따로 확인해. 모달을 닫은 뒤에는 이전 페이지의 스크롤과 포커스까지 돌아와야 같은 탐색 흐름이라고 할 수 있어.

피드에서 모달을 열고, 그 URL을 새 탭에 붙이고, 새로고침하고, 뒤로·앞으로 가기를 순서대로 실행해. 각 단계의 URL과 화면 표현을 기록하고 키보드 focus가 모달로 들어갔다가 닫힐 때 원래 항목으로 돌아오는지도 확인해. 모달의 배경 스크롤 잠금과 screen reader의 바깥 콘텐츠 차단도 확인해. URL 표현이 맞아도 focus와 문서 읽기 순서가 무너지면 가로채기 라우트는 내비게이션을 개선한 게 아니라 시각 효과만 추가한 거야.

Code

사진 모달의 폴더 구조·text
app/
├── layout.tsx
├── page.tsx                                  # /
├── @modal/
│   ├── default.tsx                           # 기본값으로 아무것도 없음
│   └── (.)photos/[id]/page.tsx               # /photos/:id 가로채기
├── photos/
│   └── [id]/page.tsx                         # 전체 페이지 보기
└── feed/page.tsx                             # /feed(/photos/:id로 연결)
가로챈 페이지를 모달로 렌더링하기·tsx
// app/@modal/(.)photos/[id]/page.tsx
import { Modal } from '@/components/Modal';

export default async function PhotoModal({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  const photo = await getPhoto(id);

  return (
    <Modal closeHref="/feed">
      <img src={photo.url} alt={photo.title} className="max-h-[70vh]" />
      <p className="text-sm mt-2">{photo.title}</p>
    </Modal>
  );
}
직접 방문하면 전체 페이지·tsx
// app/photos/[id]/page.tsx
export default async function PhotoPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  const photo = await getPhoto(id);
  return (
    <main className="p-8">
      <img src={photo.url} alt={photo.title} className="w-full max-h-[80vh] object-contain" />
      <h1 className="text-2xl mt-4">{photo.title}</h1>
      <p className="mt-2 text-gray-600">{photo.description}</p>
    </main>
  );
}

External links

Exercise

/photos/:id 상세 페이지와 피드를 만들어. 썸네일 클릭은 모달로, 같은 URL을 새 탭에 붙이면 전체 상세 화면으로 열리는지 확인해.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.