NextJS 라우팅
TIL 23. 12. 21
인프런 Next + React Query로 SNS 서비스 만들기 강의와 NextJS 공식문서를 함께 보며 작성
라우트 정의 & 페이지와 레이아웃 학습
Next.js 버전 13에서 리액트 서버 컴포넌트를 기반으로 하는 앱 라우터가 도입되었다. 앱 라우터는 app
이라는 디렉토리에서 동작한다.
Next.js는 폴더를 사용하여 경로를 정의하는 파일 시스템 기반 라우터를 사용하고 있다.
- 폴더: URL 세그먼트에 매핑되는 경로 세그먼트
- 파일: 경로 세그먼트에 표시되는 UI를 만드는 데 사용
✅ 용어 정리
- URL 세그먼트(URL Segment): 슬래시로 구분된 URL 경로의 일부
- 세그먼트: 부분, 구간, 분할
- URL 경로(URL Path): 도메인 뒤에 오는 URL의 일부(세그먼트로 구성)
각 폴더는 경로 세그먼트를 나타내며 각 경로 세그먼트는 URL 경로의 해당 세그먼트에 매핑된다. 즉 폴더명이 URL 경로가 된다. 중첩된 경로를 만드려면 폴더를 중첩해서 생성하면 된다.
해당 URL에 접근했을 때 보여지는 페이지는 page.tsx
파일에 정의한다. 페이지는 경로의 ‘고유한 UI’를 만들고 해당 URL에 접근할 수 있도록 한다. page
파일이 없다면 해당 페이지는 보여질 화면이 없으므로 “404 Not Found”를 마주할 것이다.
page
파일은.js
,.jsx
, 또는.tsx
파일 확장자를 사용할 수 있다.
레이아웃은 여러 페이지에서 공유되는 UI이다. 최상위 레이아웃을 루트 레이아웃이라고 하며 애플리케이션 모든 페이지에서 공유된다.
모든 경로 세그먼트는 자체 레이아웃을 정의할 수 있고 하위 세그먼트의 모든 페이지에서 공유된다. 레이아웃은 기본적으로 중첩이 되고, 각 상위 레이아웃은 children
prop을 사용해 페이지나 하위 레이아웃을 포함한다.
layout.tsx
파일에 정의한다. 레이아웃과 페이지 파일은 동일한 폴더에 정의할 수 있다.
/home
경로에 접근했다고 가정하고 예시를 들어 살펴보자.
📂app
┣ 📂home
┃ ┣ 📜layout.tsx
┃ ┗ 📜page.tsx
┣ 📜layout.tsx
┗ 📜page.tsx
// 📜 app/page.tsx
export default function Root() {
return <main>메인 페이지</main>;
}
// 📜 app/layout.tsx
(...)
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>
<div>메인 레이아웃 시작</div>
{children}
<div>메인 레이아웃 끝</div>
</body>
</html>
);
}
// app/home/page.tsx
export default function Home() {
return <div>홈 페이지</div>;
}
// app/home/layout.tsx
export default function HomeLayout({ children }) {
return (
<>
<div>홈 레이아웃 시작</div>
{children}
<div>홈 레이아웃 끝</div>
</>
);
}
컴포넌트 이름은 중요하지 않다. 그러나 export default는 반드시 붙여줘야 한다.
루트 레이아웃이 하위 레이아웃에서도 공유되고 있고 홈 레이아웃은 같은 폴더 내의 pages.tsx
를 children으로 전달받고 있다.
RootLayout ➡ HomeLayout ➡ HomePage
본격적으로 “X” 라우팅하기
먼저 “X”가 어떻게 URL을 구성하고 있는지 살펴보자. 구현할 기능 외에는 생략했다.
home
피드explore
탐색messages
쪽지username
프로필status
id
게시글
i
flow
login
로그인signup
회원가입
NextJS 라우터는 폴더명에 따라서 경로를 설정하고 page.tsx
파일이 있으면 해당 경로에 UI를 보여준다고 했다. 이를 기반으로 폴더와 파일을 생성한다.
📂app
┣ 📂compose
┃ ┗ 📜page.tsx
┣ 📂explore
┃ ┗ 📜page.tsx
┣ 📂home
┃ ┗ 📜page.tsx
┣ 📂search
┃ ┗ 📜page.tsx
┣ 📂username
┃ ┣ 📂status
┃ ┃ ┗ 📂id
┃ ┃ ┃ ┗ 📜page.tsx
┃ ┗ 📜page.tsx
┣ 📂i
┃ ┗ 📂flow
┃ ┃ ┣ 📂login
┃ ┃ ┃ ┗ 📜page.tsx
┃ ┃ ┗ 📂signup
┃ ┃ ┃ ┗ 📜page.tsx
┣ 📜layout.tsx
┣ 📜not-found.tsx
┗ 📜page.tsx
그런데 문제가 있다. username과 id는 고정된 값이 아니라 사용자와 게시글에 따라 다르게 라우팅되어야 한다.
동적 라우트 매칭
“X (twitter)”에서 피드의 게시글을 클릭하면 (...)/[username]/status/[id]
형태의 URL로 이동한다. 계정의 정보를 보여 주는 URL은 (...)/[username]
이다.
username과 id에 해당하는 값은 계정마다, 게시글마다 다르므로 동적으로 경로를 생성해야 한다. 이때 폴더 이름을 대괄호로 묶어서 생성하면 경로의 현재 상태에 따라 동적으로 겅로를 변경할 수 있다.
📂app
┣ 📂[username]
┃ ┣ 📂status
┃ ┃ ┗ 📂[id]
┃ ┃ ┃ ┗ 📜page.tsx
┃ ┗ 📜page.tsx
┗ 📜layout.tsx
만약 /NewJeans_ADOR
경로로 접근했다면 계정 정보가 표시될 것이고 게시글을 눌러서 들어가면 /NewJeans_ADOR/status/1737044701461500294
경로에서 게시글을 보여준다. 해당 계정과 게시글에 따라 관련된 내용을 제공할 수 있게 되었다.
그런데 만약 [username]이 URL 경로로 사용하고 있는 값이라면?
현재 URL 경로로 home을 사용하고 있다. 그런데 사용자가 username을 home이라고 지었다. 그리고 /home
경로로 이동하였다. 이 경우는 어떻게 될까?
결과는 URL 경로로 사용 중인 home의 경로로 이동한다. 미리 정의된 고정된 경로가 동적 경로보다 우선순위를 갖는다.
동적 라우팅으로 정의되는 값은 항상 최후 순위가 된다. 따라서, username이 home인 사용자의 계정 페이지를 볼 수 없게 된다. 사용하고 있는 URL 경로는 username으로 사용할 수 없도록 해야 한다.
라우트 그룹, 로그인 여부에 따라 다른 레이아웃 보여주기
애플리케이션에서 로그인한 사용자가 볼 수 있는 페이지와 로그인하지 않은 사용자의 동작을 구분하려고 한다. 로그인하지 않은 사용자는 로그인 혹은 회원가입 페이지만 볼 수 있게 해야 한다.
로그인한 사용자는 홈, 탐색하기와 같은 네비게이션과 사이드바를 볼 수 있지만 로그인하지 않은 사용자는 그렇지 않다. 로그인 여부에 따라 화면 구성이 다르다.
로그인 전 | 로그인 후 |
---|---|
라우트 그룹이란 URL 경로에 영향을 주지 않고 그룹을 만들 수 있는 기능이다. 폴더 이름을 괄호로 묶어 생성한다.
라우트 그룹을 이용하면 URL 경로와 관계 없이 layout.tsx
파일을 추가하여 그룹별로 다른 레이아웃을 만들 수 있다. 라우트 그룹으로 생성한 폴더는 URL에서 생략된다.
로그인한 그룹과 로그인하지 않은 그룹으로 (afterLogin)
과 (beforeLogin)
이라는 폴더를 생성한다. 각각 layout.tsx
파일을 생성하여 로그인 여부에 따라 다른 레이아웃을 보여준다.
📂app
┣ 📂(afterLogin)
┃ ┣ 📂compose
┃ ┣ 📂explore
┃ ┣ 📂home
┃ ┣ 📂search
┃ ┣ 📂[username]
┃ ┃ ┣ 📂status
┃ ┃ ┃ ┗ 📂[id]
┃ ┗ 📜layout.tsx
┣ 📂(beforeLogin)
┃ ┣ 📂i
┃ ┃ ┗ 📂flow
┃ ┃ ┃ ┣ 📂login
┃ ┃ ┃ ┗ 📂signup
┗ ┗ 📜layout.tsx
(+) “X”에서 페이지를 이동할 때 레이아웃은 변경되지 않는다. 레이아웃은 그대로 두고 페이지만 리렌더링이 된다.
페이지를 이동할 때마다 레이아웃 역시 새로 생성되게 하려면 템플릿를 사용한다.
페이지를 이동하려면 Link
페이지 이동은 <a>
태그 대신에 NextJS에서 제공하는 Link
를 사용한다. 그 이유는 <a>
태그는 페이지가 새로고침 되어 깜빡임이 생기고 상태가 초기화되기 때문이다.
애플리케이션의 첫 소개 화면을 구현하는데 계정 만들기와 로그인 버튼을 누르면 각 경로로 이동하도록 Link
에 경로를 지정한다.
import Image from 'next/image';
import Link from 'next/link';
import styles from './page.module.css';
import xLogo from '../../public/xLogo.png';
export default function Main() {
return (
<div className={styles.container}>
<div className={styles.left}>
<Image src={xLogo} alt="logo" />
</div>
<div className={styles.right}>
<h1>지금 일어나고 있는 일</h1>
<h2>지금 가입하세요.</h2>
<Link href="/i/flow/signup" className={styles.signup}>
계정 만들기
</Link>
<h3>이미 X에 가입하셨나요?</h3>
<Link href="/login" className={styles.login}>
로그인
</Link>
</div>
</div>
);
}
의도치 않은 Redirect
“X”에서 login 페이지로 이동하면 주소창에서 잠깐 https://twitter.com/login
으로 이동했다가 https://twitter.com/i/flow/login
으로 리다이렉션 되는 것을 알 수 있다. 기괴한 구조이지만 이것까지 지켜서 구현해본다.
// app/(beforeLogin)/login/page.tsx
import { redirect } from 'next/navigation';
export default function Login() {
redirect('/i/flow/login');
}
/login
에 해당하는 경로 매칭을 위해 login
폴더를 생성하고 이어서 page.tsx
파일을 생성한다. redirect
를 사용하여 사용자를 다른 URL로 리다이렉션할 수 있다.
/login
경로로 접근하면 /i/flow/login
경로로 리다이렉션 된다.
(+) Next에서 Image 사용법
이미지는 public 폴더에 보관하고 이미지를 넣으려면 Next에서 제공하는 Image
컴포넌트를 사용한다. Image
컴포넌트는 자동으로 이미지를 최적화하고 레이아웃 시프트 방지 기능이 포함되어 있다.
이미지 경로를 넣기 위해서는 이미지 파일을 import 해서 사용하고 Image
컴포넌트의 src에 import한 이미지 경로를 넣는다.
import Image from 'next/image';
import styles from './page.module.css';
import xLogo from '../../public/xLogo.png';
export default function Main() {
return (
<div className={styles.container}>
<div className={styles.left}>
<Image src={xLogo} alt="logo" />
</div>
</div>
);
}
댓글남기기