RR7 vs REMIX2 라우팅 비교

share · 2026-4-2

← 리스트로

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)를 하지 않고 loaderaction만 export하면 자동으로 리소스 라우트로 동작합니다.

요약: 왜 RR7 방식으로 바뀌었나?

Remix의 파일 시스템 라우팅은 편리하지만, 프로젝트가 커지면 다음과 같은 문제가 생깁니다.

  1. 파일명 복잡도: __auth.products.$id.edit.tsx 처럼 파일 이름이 너무 길어집니다.
  2. 유연성 부족: 파일 위치를 옮기면 URL이 바뀌어 버립니다.
  3. 가독성: app/routes/ 폴더에 파일이 100개 있으면 어떤 게 부모고 자식인지 찾기 힘듭니다.

RR7 Framework 모드는 이를 해결하기 위해:

  • 파일 구조는 개발자가 원하는 대로(도메인별, 기능별) 자유롭게 가져가되,
  • 라우팅 규칙app/routes.ts 한 곳에서 관리하도록 설계되었습니다.

1. 앞 언더바 (_pathless) : 경로 제외 레이아웃

Remix에서 파일명 앞에 붙는 언더바는 “URL 경로에는 나타나지 않지만, 레이아웃(공통 디자인)은 적용하고 싶을 때” 사용합니다.

  • Remix 규칙 (_auth.login.tsx):
    • URL: /login (auth라는 단어가 없음)
    • 구조: login.tsx_auth.tsx 레이아웃 안에 들어감.
  • 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 상황:

    1. app/routes/dashboard.tsx (부모 레이아웃)
    2. app/routes/dashboard.settings.tsx (대시보드 안의 설정 페이지)
    3. app/routes/dashboard_.special.tsx (뒤 언더바!)
  • 결과:

    • URL은 /dashboard/special로 동일해 보이지만, dashboard_.special.tsxdashboard.tsx 레이아웃을 타지 않습니다. 완전히 새로운 전체 화면 페이지가 됩니다.
  • RR7 매핑:

    • RR7은 코드로 명시하기 때문에 “뒤 언더바” 같은 트릭이 필요 없습니다. 그냥 layout() 그룹 밖으로 빼내면 끝입니다.
// 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() 헬퍼로 기존 방식을 그대로 쓸 수도 있습니다.

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/signupURL은 평평하지만 동일한 디자인을 공유해야 할 때 사용합니다.
  • 구조:
    layout("auth-design.tsx", [ 
      route("login", "login.tsx") 
    ]) // 결과: /login (auth-design 적용됨)
    

5. 최종 핵심 요약

  1. 가독성 승리: 파일 이름에 $, ., _를 남발하던 방식에서 벗어나, routes.ts 코드 한 장으로 전체 서비스 구조를 파악할 수 있게 되었습니다.
  2. 유연성 증가: 파일의 물리적 위치를 옮겨도 routes.ts 설정만 바꾸면 URL을 유지할 수 있습니다.
  3. 학습 곡선: 기존 Remix 사용자라면 $:로 바꾸고, 파일명 접두사/접미사 규칙을 layout() 함수와 배열 구조로 옮겨온다고 생각하면 매우 쉽습니다.

Tip: RR7에서 부모 컴포넌트는 여전히 **<Outlet />**을 사용해 자식의 렌더링 위치를 지정해야 한다는 점, 잊지 마세요!