RR7 vs REMIX2 라우팅 비교
1. 기본 및 인덱스 라우트 (Basic & Index)
Remix는 파일의 위치와 이름이 곧 URL이었지만, RR7은 routes.ts에서 명시적으로 함수를 호출합니다.
| 구분 | Remix (app/routes/…) | RR7 (app/routes.ts) |
설명 |
|---|---|---|---|
| Root/Home | _index.tsx |
index("routes/home.tsx") |
/ 경로에 대응하는 페이지 |
| Simple Path | about.tsx |
route("about", "routes/about.tsx") |
/about 경로 매핑 |
| Nested Path | contact.support.tsx |
route("contact/support", "...") |
/contact/support. Remix는 .으로 구분하지만 RR7은 문자열 내 / 사용 |
2. 동적 세그먼트 (Dynamic Segments)
파라미터를 받는 라우팅입니다. Remix는 $ 기호를 파일 이름에 썼지만, RR7은 표준 URL 패턴(:)을 사용합니다.
| 구분 | Remix (app/routes/…) | RR7 (app/routes.ts) |
설명 |
|---|---|---|---|
| 단일 파라미터 | posts.$id.tsx |
route("posts/:id", "...") |
params.id로 접근 가능 |
| 다중 파라미터 | $lang.$category.tsx |
route(":lang/:category", "...") |
여러 개의 동적 값 처리 |
| 선택적(Optional) | (기능 없음 / 폴더 구조 활용) | route("posts/:id?", "...") |
:id? 처럼 뒤에 ?를 붙여 선택적 파라미터 구현 |
| Splat (전체) | $.tsx |
route("*", "...") |
어떤 경로와도 매치되는 Catch-all 라우트 |
3. 중첩 라우팅 및 레이아웃 (Nested Layouts)
이 부분이 가장 큰 차이점입니다. Remix는 파일 이름의 접두사로 부모-자식 관계를 결정하지만, RR7은 layout() 함수로 감싸는 구조를 가집니다.
Remix 방식 (Implicit)
dashboard.tsx(부모 레이아웃)dashboard._index.tsx(자식 1)dashboard.settings.tsx(자식 2)- 규칙: 파일명이
dashboard로 시작하면 자동으로dashboard.tsx내부의<Outlet />에 렌더링됨.
RR7 방식 (Explicit)
// app/routes.ts
layout("routes/dashboard-layout.tsx", [
index("routes/dashboard-home.tsx"),
route("settings", "routes/settings.tsx"),
])
- 장점: 파일 이름을 억지로 맞출 필요가 없습니다. 어떤 파일이든 레이아웃으로 지정하고 그 안에 자식들을 명시적으로 배치할 수 있어 구조 파악이 훨씬 쉽습니다.
4. 경로 제외 레이아웃 (Pathless/Group Routes)
URL에는 영향을 주지 않으면서 특정 페이지들을 하나의 레이아웃(공통 디자인)으로 묶고 싶을 때 사용합니다.
| 구분 | Remix | RR7 |
|---|---|---|
| 규칙 | 파일 이름 앞에 _를 붙임 |
layout() 함수에 경로(path)를 생략함 |
| 예시 | _auth.login.tsx, _auth.signup.tsx |
layout("auth-layout.tsx", [ route("login", "..."), route("signup", "...") ]) |
| 결과 | URL은 /login, /signup 이지만 _auth 레이아웃 공유 |
동일함. 하지만 코드로 그룹핑이 보여서 가독성이 좋음 |
5. 리소스 라우트 (Resource Routes)
UI(HTML)를 렌더링하지 않고 JSON이나 이미지 등을 반환하는 API 전용 라우트입니다.
- Remix:
app/routes/api.search.ts처럼.ts확장자로 파일을 만들면 자동으로 리소스 라우트가 됩니다. - RR7:
route()함수를 그대로 사용하되, 연결된 파일에서default export(React Component)를 하지 않고loader나action만 export하면 자동으로 리소스 라우트로 동작합니다.
요약: 왜 RR7 방식으로 바뀌었나?
Remix의 파일 시스템 라우팅은 편리하지만, 프로젝트가 커지면 다음과 같은 문제가 생깁니다.
- 파일명 복잡도:
__auth.products.$id.edit.tsx처럼 파일 이름이 너무 길어집니다. - 유연성 부족: 파일 위치를 옮기면 URL이 바뀌어 버립니다.
- 가독성:
app/routes/폴더에 파일이 100개 있으면 어떤 게 부모고 자식인지 찾기 힘듭니다.
RR7 Framework 모드는 이를 해결하기 위해:
- 파일 구조는 개발자가 원하는 대로(도메인별, 기능별) 자유롭게 가져가되,
- 라우팅 규칙만
app/routes.ts한 곳에서 관리하도록 설계되었습니다.
1. 앞 언더바 (_pathless) : 경로 제외 레이아웃
Remix에서 파일명 앞에 붙는 언더바는 “URL 경로에는 나타나지 않지만, 레이아웃(공통 디자인)은 적용하고 싶을 때” 사용합니다.
- Remix 규칙 (
_auth.login.tsx):- URL:
/login(auth라는 단어가 없음) - 구조:
login.tsx가_auth.tsx레이아웃 안에 들어감.
- URL:
- RR7 매핑:
layout()함수를 사용하되, 경로(path)를 명시하지 않거나 빈 문자열로 둡니다.
// RR7 (app/routes.ts)
layout("routes/auth-layout.tsx", [ // 경로 없음 -> Pathless
route("login", "routes/login.tsx"),
route("signup", "routes/signup.tsx"),
])
2. 뒤 언더바 (folder_) : 레이아웃 중첩 방지 (Opt-out)
이 부분이 Remix에서 가장 헷갈리는 부분 중 하나입니다. 뒤 언더바는 “URL 상으로는 자식 경로 같아 보이지만, 부모 레이아웃을 무시하고 독자적인 페이지를 만들고 싶을 때” 사용합니다.
-
Remix 상황:
app/routes/dashboard.tsx(부모 레이아웃)app/routes/dashboard.settings.tsx(대시보드 안의 설정 페이지)app/routes/dashboard_.special.tsx(뒤 언더바!)
-
결과:
- URL은
/dashboard/special로 동일해 보이지만,dashboard_.special.tsx는dashboard.tsx레이아웃을 타지 않습니다. 완전히 새로운 전체 화면 페이지가 됩니다.
- URL은
-
RR7 매핑:
- RR7은 코드로 명시하기 때문에 “뒤 언더바” 같은 트릭이 필요 없습니다. 그냥
layout()그룹 밖으로 빼내면 끝입니다.
- RR7은 코드로 명시하기 때문에 “뒤 언더바” 같은 트릭이 필요 없습니다. 그냥
// RR7 (app/routes.ts)
export default [
// 1. 대시보드 레이아웃 그룹
layout("routes/dashboard-layout.tsx", [
route("dashboard/settings", "routes/settings.tsx"),
]),
// 2. 레이아웃을 타지 않는 독자적인 페이지
// Remix의 dashboard_.special.tsx와 동일한 역할
route("dashboard/special", "routes/special-page.tsx"),
]
3. 상세 비교 요약표
| 규칙 (Remix v2) | 위치 | 의미 | RR7 방식 (routes.ts) |
|---|---|---|---|
_name.tsx |
앞 | Pathless Layout: URL엔 안 나오지만 레이아웃은 공유함 | layout("layout.tsx", [ ... ]) |
name_.tsx |
뒤 | Layout Opt-out: 부모 레이아웃의 <Outlet />에 들어가기 싫을 때 |
layout 그룹 밖에 route() 정의 |
_index.tsx |
앞 | Index Route: 부모 경로의 기본 페이지 | index("home.tsx") |
4. 왜 RR7에서는 언더바가 사라졌나?
Remix의 언더바 규칙은 **“파일 이름만으로 모든 라우팅 구조를 표현해야 한다”**는 제약 때문에 탄생한 '약속’입니다.
- 앞 언더바: “이 파일 이름은 URL에서 무시해줘!”
- 뒤 언더바: “나랑 이름 같은 부모 파일이 있어도 무시하고 나 혼자 렌더링해줘!”
하지만 RR7 Framework 모드는 routes.ts라는 설정 파일이 생기면서 이런 복잡한 기호가 필요 없어졌습니다.
- 레이아웃을 입히고 싶으면
layout()함수 안에 넣으면 되고, - 독자적으로 쓰고 싶으면
layout()함수 밖에 쓰면 됩니다.
결론적으로, Remix v2에서 언더바를 써서 해결했던 복잡한 레이아웃 상속 문제들이 RR7에서는 **“어떤 함수 안에 코드를 배치하느냐”**라는 훨씬 직관적인 물리적 위치의 문제로 바뀌었다고 보시면 됩니다.
React Router 7(RR7) Framework 모드는 Remix의 강력한 기능을 유지하면서도, 파일 이름에 의존하던 제약을 **app/routes.ts**라는 설정 파일로 옮겨와 유연성을 극대화했습니다.
지금까지 논의한 모든 내용을 한눈에 보기 쉽게 정리해 드립니다.
1. 패러다임의 변화: 파일 중심 → 코드 중심
- Remix: 파일 이름이 곧 라우팅 규칙 (Convention over Configuration).
- RR7:
routes.ts에서 명시적으로 정의 (Configuration over Convention).- 참고: Remix 스타일을 선호하면
flatRoutes()헬퍼로 기존 방식을 그대로 쓸 수도 있습니다.
- 참고: Remix 스타일을 선호하면
2. 라우팅 규칙 상세 매핑 테이블
| 기능 | Remix 파일명 규칙 | RR7 routes.ts 설정 방식 |
비고 |
|---|---|---|---|
| 인덱스 (Home) | _index.tsx |
index("routes/home.tsx") |
/ 경로 |
| 일반 경로 | about.tsx |
route("about", "routes/about.tsx") |
/about |
| 동적 파라미터 | posts.$id.tsx |
route("posts/:id", "...") |
$id → :id (표준 URL 패턴) |
| 전체 매칭 | $.tsx |
route("*", "...") |
Splat/Catch-all 라우트 |
| 중첩 레이아웃 | parent.tsx + parent.child.tsx |
route("p", "f1", [ route("c", "f2") ]) |
트리 구조로 직관적 정의 |
| 선택적 파라미터 | (구현 복잡) | route("posts/:id?", "...") |
:id? 사용 가능 |
3. 언더바(_) 규칙의 현대화
Remix에서 기호로 구분하던 복잡한 규칙들이 RR7에서는 함수의 위치와 종류로 깔끔하게 정리되었습니다.
① 앞 언더바 (_auth.tsx) → layout() 함수
- 의미: URL에는 영향을 주지 않고 UI 레이아웃만 공유 (Pathless Layout).
- RR7:
layout("파일경로", [ 자식_배열 ])을 사용합니다.- 예:
layout("auth-layout.tsx", [ route("login", "...") ])→ URL은/login.
- 예:
② 뒤 언더바 (dashboard_.tsx) → 계층 분리
- 의미: 부모와 URL은 공유하지만, 부모의 레이아웃(Outlet)을 무시하고 싶을 때 (Layout Opt-out).
- RR7: 해당
route()를 부모의 자식 배열([])에 넣지 않고, **배열 밖(독립적)**에 선언합니다.
4. 중첩 라우팅: route() vs layout()
가장 헷갈리기 쉬운 두 함수의 차이점은 **“URL 세그먼트를 생성하느냐”**입니다.
route(path, file, children)
- 특징: URL 계층을 만듭니다.
- 용도:
/shop아래에/shop/admin이 있는 것처럼 URL과 UI가 모두 계층적일 때 사용합니다. - 구조: ```typescript
route(“shop”, “shop.tsx”, [
route(“admin”, “admin.tsx”)
]) // 결과: /shop/admin
layout(file, children)
- 특징: URL에 아무런 흔적을 남기지 않습니다.
- 용도:
/login과/signup이 URL은 평평하지만 동일한 디자인을 공유해야 할 때 사용합니다. - 구조:
layout("auth-design.tsx", [ route("login", "login.tsx") ]) // 결과: /login (auth-design 적용됨)
5. 최종 핵심 요약
- 가독성 승리: 파일 이름에
$,.,_를 남발하던 방식에서 벗어나,routes.ts코드 한 장으로 전체 서비스 구조를 파악할 수 있게 되었습니다. - 유연성 증가: 파일의 물리적 위치를 옮겨도
routes.ts설정만 바꾸면 URL을 유지할 수 있습니다. - 학습 곡선: 기존 Remix 사용자라면
$를:로 바꾸고, 파일명 접두사/접미사 규칙을layout()함수와 배열 구조로 옮겨온다고 생각하면 매우 쉽습니다.
Tip: RR7에서 부모 컴포넌트는 여전히 **
<Outlet />**을 사용해 자식의 렌더링 위치를 지정해야 한다는 점, 잊지 마세요!