Search

중첩 라우트(Nested routes)

웹 개발에서 라우팅(Routing)이란 접속한 URL의 경로(Route)에 대해 빠르게 페이지에 도달하도록 길 안내 하는 것을 뜻하죠. 리믹스(Remix)의 라우팅 시스템은 내부적으로 react-router중첩 라우트(Nested routes)를 사용하고 있습니다. 중첩 라우트는 react-router나 next.js를 사용해 보신 분 들이라면 아마 익숙한 개념일텐데요. 이 중첩 라우트를 쉽게 말하자면 어떤 URL에서 중첩되는 경로에 있는 모든 레이아웃 요소를 해당 페이지에서 모두 중첩해서 사용하는 파일 기반의 라우팅 시스템이라고 할 수 있습니다.
리믹스에서는 중첩된 경로에 있는 모든 레이아웃 요소를 해당 라우트 페이지에서 중첩해서 사용합니다. 이를 통해 중복된 UI 개발 코드를 줄이고 페이지 로드 속도를 향상시킬 수 있습니다. 이미지 출저: 리믹스 홈페이지, https://remix.run/

라우트 V1과 V2의 차이점

리믹스 V1에서는 라우트 설정을 폴더 구조를 중첩해서 정의하도록 되어 있었는데요. V2에서는 flat routes 제안 내용을 받아들여 폴더 중첩 없이 파일 이름에서 .으로 중첩을 구분하여 라우트 구조를 정의하도록 변경되었습니다.

V1의 경우…

V1의 라우트는 아래처럼 app/routes/내에 중첩된 폴더 구조 기준으로 라우팅 경로가 정의되는 방식이었습니다.
app/ ├── routes/ │ ├── calendar │ │ ├── details.tsx <- /calendar/details │ │ └── index.tsx <- /calendar │ ├── projects/ │ │ ├── $projectId/ │ │ │ ├── details.tsx <- /projects/a1/details │ │ │ ├── edit.tsx <- /projects/a1/edit │ │ │ └── index.tsx <- /projects/a1 │ │ └── settings.tsx <- /projects/settings │ └── projects.tsx <- /projects └── root.tsx
Plain Text
복사
V1 방식은 페이지 경로를 폴더와 파일 구조를 통해 트리 형태로 확인할 수 있습니다.
중첩 폴더와 파일로 정의하는 V1의 방식은 페이지 숫자가 적은 소규모 프로젝트의 경우에는 라우트 구조를 폴더 구조를 통해 직관적으로 파악할 수 있다는 장점이 있지만 페이지 수가 많고 규모가 큰 프로젝트의 경우 전반적인 라우트 구조를 단번에 확인하는 것이 힘들어지고 복잡한 경로에 있는 폴더와 파일의 경우 찾는 것이 꽤 번거로운 작업이 되게 됩니다.

V2에서는

V2는 보다 평평한 형태의 flat routes를 도입함으로써 기존의 폴더 중첩이 사라지고 아래처럼 app/routes/에서 다른 폴더를 찾아가지 않고도 모든 라우트 구조를 단번에 확인할 수 있습니다. 폴더 구조 대신에 파일이름에서 .으로 나누어 중첩 구조를 설정합니다.
app/ ├── routes/ │ ├── calendar.details.tsx <- /calendar/details │ ├── calendar._index.tsx <- /calendar │ ├── projects.$projectId.details.tsx <- /projects/a1/details │ ├── projects.$projectId.edit.tsx <- /projects/a1/edit │ ├── projects.$projectId._index.tsx <- /projects/a1 │ ├── projects.settings.tsx <- /projects/settings │ └── projects.tsx <- /projects └── root.tsx
Plain Text
복사
V2 방식은 폴더 트리 대신 파일 네이밍으로 중첩을 구분하기 때문에 routes/ 아래에서 모든 페이지 경로를 확인할 수 있습니다.
라우트 설정이 중첩 경로에 따라 폴더 구조에 깊이가 있어 찾아가며 확인해야 했던 V1에 비해 V2는 routes/ 바로 아래에 모두 평평하게 존재하므로 전체적인 라우트 설정을 보다 빠르게 파악할 수 있습니다. 하지만 경로가 길어질수록 파일 이름도 같이 길어지는 점과 폴더 트리 구조에 비해 비교적 직관성이 떨어지는 점은 단점으로 볼 수 있겠네요.

V2의 파일 네이밍 라우트 사용 방법

리믹스 라우트는 크게 보면 두가지 구조가 되는데요. 모든 경로의 중첩 시작점이 되는 root 파일과 각 경로마다 파일 네이밍으로 중첩 라우트를 구성하는 routes/ 폴더가 있습니다.
app/ ├── routes/ └── root.tsx
Plain Text
복사

모든 중첩 라우트의 뿌리가 되는 root.tsx

root 파일은 모든 중첩의 밑바탕이 되는 파일로 모든 라우트의 랜더링에 필요한 것 들을 이 곳에서 설정합니다. create-remix로 생성한 프로젝트의 root.tsx 파일을 열어보면 초기 코드가 아래와 같이 되어 있는데요. 프로젝트에서 공통으로 사용하는 <meta> 태그 설정이나 전역 CSS 임포트와 같은 것들을 이 곳에서 처리하도록 하면 됩니다.
// ./app/root.tsx import { cssBundleHref } from "@remix-run/css-bundle"; import type { LinksFunction } from "@remix-run/node"; import { Links, LiveReload, Meta, Outlet, Scripts, ScrollRestoration, } from "@remix-run/react"; export const links: LinksFunction = () => [ ...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []), ]; export default function App() { return ( <html lang="en"> <head> <meta charSet="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <Meta /> <Links /> </head> <body> <Outlet /> {/* 하위 라우트가 랜더링 되는 위치 */} <ScrollRestoration /> <Scripts /> <LiveReload /> </body> </html> ); }
TypeScript
복사
root.tsx에서 여러 리믹스 컴포넌트들을 사용하고 있지만 본 포스트는 중첩 라우트에 대해 다루기 때문에 자식 라우트를 중첩해 위치시키는 Outlet 컴포넌트에 대해서만 설명합니다. 다른 리믹스 컴포넌트들은 별도 포스트에서 자세히 다루겠습니다.

자식 중첩 라우트를 위치시키는 Outlet

root.tsx 파일 코드에서 <body> 태그 바로 아래에 보면 리믹스의 <Outlet /> 컴포넌트가 위치해 있는데요. 이 <Outlet /> 컴포넌트가 위치한 곳에 중첩된 하위 라우트의 화면 요소가 랜더링 됩니다. 리액트 노드 컴포넌트의 children 프로퍼티와 그 용도가 비슷하다고 볼 수 있겠네요.
<Outlet />root 뿐만 아니라 화면 중첩을 사용할 모든 라우트에서 사용할 수 있습니다. 리믹스에서 해더(Header), 푸터(Footer) 또는 메뉴 바와 같은 화면 공통 레이아웃은 <Outlet />과 라우트 중첩을 통해 구현합니다.

파일 네이밍 라우트 컨벤션

app/routes 폴더 아래에 있는 모든 파일은 이름을 리믹스가 구문 분석하여 라우터(Router)를 형성하게 됩니다. 가장 기본적이고 우선되는 규칙은 URL 경로가 파일 이름에서 .으로 구분되어 결정된다는 것입니다.
app/ ├── routes/ │ └── 1depth.2depth.tsx -> /1depth/2depth └── root.tsx
Plain Text
복사
중첩된 경로를 사용하면 부모 라우트를 레이아웃으로 사용하게 되고 자식 라우트는 부모 라우트의 <Outlet />이 위치한 곳에 랜더링 됩니다.
app/ ├── routes/ │ ├── my.tsx -> /my │ └── my.details.tsx -> /my/details, my.tsx를 레이아웃으로 사용 └── root.tsx
Plain Text
복사
이름 앞에 _(언더바)를 붙이면 해당 이름이 URL 경로로 사용되지 않습니다. _index.tsx로 사용하면 해당 경로의 인덱스 페이지가 됩니다.
app/ ├── routes/ │ ├── _index.tsx -> / │ ├── my.tsx -> /my │ └── my.details.tsx -> /my/details └── root.tsx
Plain Text
복사
_ 프리픽스를 사용해 URL 경로로 사용되지는 않지만 레이아웃으로만 사용되는 모듈을 구성할 수 있습니다. 아래처럼 _defualt.tsx라는 레이아웃 모듈이 있을 때 _defualt를 앞에 사용한 라우트는 _defualt.tsx 레이아웃을 사용하게 됩니다.
app/ ├── routes/ │ ├── _defualt.tsx URL 경로로 지정되지 않고 레이아웃 모듈로만 사용됨 │ ├── _defualt._index.tsx _defualt 레이아웃 사용 │ ├── my.tsx │ └── my.details.tsx my 레이아웃 사용 └── root.tsx
Plain Text
복사
_를 이름 끝에 붙이는 suffix로 사용하게 되면 경로는 사용하지만 레이아웃 중첩을 사용하지 않는다는 뜻이 됩니다. 사용 방법은 해당 네임 끝에 _를 붙이는 식입니다.
app/ ├── routes/ │ ├── _defualt.tsx │ ├── _defualt._index.tsx │ ├── my.tsx │ └── my_.details.tsx -> /my/details, my 레이아웃은 사용하지 않음 └── root.tsx
Plain Text
복사
$를 프리픽스로 사용하면 URL 세그먼트가 됩니다. 아래와 같이 reservation 경로 뒤에 $id로 세그먼트를 정의하면 /reservation/a123 경로로 접속했을 때 a123을 id 값으로 받아오게 되죠.
app/ ├── routes/ │ └── reservation.$id.tsx └── root.tsx
Plain Text
복사
세그먼트로 정의 한 값은 loader()에서 params로 사용할 수 있게 됩니다.
// app/routes/reservation.$id.tsx export const loader = async ({ params }: LoaderFunctionArgs) => { const reservation = await getReservation(params.id); return json({ reservation }); };
TypeScript
복사
라우트에 파일 이름 대신 폴더를 사용해 라우트를 조직화 할 수 있습니다. 파일 이름 대신 폴더 이름으로 대체하면 폴더 내부의 route.tsx 파일로 경로가 연결됩니다. 폴더 내의 다른 파일들은 경로에 영향을 주지 않습니다.
라우트를 폴더로 조직화하여 사용하는 이유는 단일 파일의 라우트가 사이즈가 크고 복잡해지는 경우 여러 파일로 나누어 관리하는 것이 훨씬 수월하기 때문입니다. 예를 들어 레이아웃으로 사용되는 라우트의 경우 아래 코드처럼 같은 폴더에 레이아웃 요소마다 각각의 파일로 나누어 두면 가까이에 있어 찾기도 쉽고 유지관리 하기가 훨씬 편해집니다.
app/ ├── routes/ │ ├── _default/ │ │ ├── header.tsx │ │ ├── footer.tsx │ │ ├── menu-bar.tsx │ │ └── route.tsx │ └── _default.contact/ │ ├── modal.tsx │ └── route.tsx -> /contact, ../default/route.tsx를 레이아웃으로 사용 └── root.tsx
Plain Text
복사

 Remix란?

Remix 목록