버전 13이 나오면서 APP Router가 등장했다!!
app
폴더가 page
폴더를 대체할 수 있게 됬다.next.js는 폴더가 경로를 정의하는 데 사용되는 파일 시스템 기반 라우터를 사용
경로의 각 폴더는 URL 주소를 구성하는 부분을 나타냅니다.
매 경로 세그먼트는 실제 URL 주소의 한 부분을 의미합니다.
간단히 말하면, Route Segments는 URL 주소를 슬래시('/')로 나눈 각 부분을 말합니다.
중첩 라우팅(Nested Routing)이란 라우팅 맵핑을 최상위 컴포넌트 뿐만 아니라 여러 개의 컴포넌트에 걸쳐서 단계별로 정의하는 라우팅 기법이다.
간단히 말하면, 페이지 안에 또 다른 페이지가 들어가는 것을 말한다.
일반적으로 웹 사이트는 여러 페이지로 이루어져 있는데, 중첩 라우팅은 한 페이지 안에 또 다른 작은 페이지를 넣는 것이다. 이 작은 페이지들은 그들만의 주소를 가지고 있고, 사용자는 이를 통해 메인 페이지에서도 서브 페이지로 바로 이동할 수 있다.
이를 통해 프로젝트를 조직화하고 유지 관리하기가 훨씬 쉬워진다.
layout
: 세그먼트 및 세그멘트 하위에 대한 공유 UIpage
: 경로의 고유한 UI 및 공개적으로 접근 가능한 경로 만들기loading
: 세그먼트와 해당 자식들이 로딩 중일때 보여지는 UInot-found
: 세그먼트와 해당 자식들을 찾지 못했을때 보여지는 UIerror
: 세그먼트와 해당 자식들이 오류가 났을때 보여지는 UI (error.js는 무조건 클라이언트 컴포넌트여야함)global-error
: 전역 오류 UIroute
: 서버측 API 끝점 (Server-side API endpoint)template
: 재 렌더링될때 보이는 UIdefault
: 병렬 라우트에 대한 대체 UI경로 세그먼트의 특수 파일에 정의된 React 구성 요소는 특정 계층 구조로 렌더링된다.
이는 말 그대로, 페이지 구성 요소들이 폴더 구조와 같은 계층 구조를 형성하여 렌더링됨을 의미한다.
중첩된 경로에서 세그먼트의 구성요소는 상위 세그먼트의 구성요소 내에 포함된다.
간단히 말해, 한 페이지 안에 다른 페이지가 들어갈 수 있는데, 이때 각 페이지의 구성 요소들은 계층적으로 조직되어 렌더링된다..
page.js
또는 route.js
공개적으로 주소를 지정할 수 있다.
위의 이미지를 보면, components
, styles
, tests
등의 폴더들이 모두 같은 app 폴더 내에 위치하고 있다.
앱 라우터는 더 복잡한 라우팅 구조를 만들기 위한 방법을 제공한다. 아래와 같다
이러한 패턴을 통해 더 풍부하고 복잡한 UI를 구축할 수 있고, 소규모 팀과 개별 개발자가 구현하기에는 어려웠던 기능을 더욱 쉽게 사용할 수 있게 됨.
예
병렬 경로를 사용하면team
및analytics
페이지를 동시에 랜더링할 수 있다.
이렇게 병렬 경로를 설정하면 각 경로에 대해 독립적인 에러 및 로드 상태를 정의할 수 있다.
@folder
컨벤션으로 정의되며, 같은 수준의 레이아웃으로 props로 전달됩니다.예 ) 다음 파일 구조는 @analytics 및 @team이라는 두 개의 명시적 슬롯을 정의한다.
기본적으로 next.js는 각 슬롯의 활성 상태(또는 하위 페이지)를 추적한다.
그러나 슬롯 내에서 랜더링되는 콘텐츠는 탐색 유형에 따라 달라진다.
탐색 시 Next.js는 현재 URL과 일치하지 않더라도 슬롯의 이전 활성 상태를 렌더링한다.
전체 페이지 재렌더링이 필요한 Hard Navigation에서 Next.js는 먼저 일치하지 않는 슬롯의 default.js 파일을 렌더링하려고 시도한다. 사용할 수 없는 경우 404가 렌더링된다.
@team
슬롯에는 settings
디렉토리가 있지만 @analytics
에는 없다.@dashboard
또는 @login
라우트를 렌더링할 수 있다./* app/layout.tsx */
import { getUser } from '@/lib/auth'
export default function Layout({ params, dashboard, login }) {
const isLoggedIn = getUser()
return isLoggedIn ? dashboard : login
}
라우트 가로채기는 (..) 구문을 사용하여 정의할 수 있다. 이는 상대 경로 표기법인 ../과 유사하지만 세그먼트에 대해 적용된다.
(.)
는 동일한 수준의 세그먼트와 일치합니다.(..)
는 한 수준 위의 세그먼트와 일치합니다.(..)(..)
는 두 수준 위의 세그먼트와 일치합니다.(...)
는 루트 앱 디렉토리부터의 세그먼트와 일치합니다.예를 들어,
(..)photo
디렉토리를 생성함으로써 피드 세그먼트 내부에서 사진 세그먼트를 가로챌 수 있다.
각 폴더는 URL의 한 세그먼트에 해당하는 route segment(경로 세그먼트)를 나타낸다. 중첩된 경로를 만들기 위해서는 폴더를 서로 중첩시킬 수 있다.
특별한 page.js
파일을 사용하여 경로 세그먼트를 공개적으로 엑세스할 수 있다.
위 예에서는 /dashboard/analytics
URL 경로에 해당하는 경로가 없으므로 공개적으로 액세스 할 수 없다.
이 폴더는 구성 요소, 스타일시트, 이미지 또는 다른 공동 위치 파일을 저장하는 데 사용할 수 있다.
각 경로 세그먼트에 대한 UI를 만들 때는 특정한 파일 규칙을 따른다. 가장 일반적인 방법은 페이지별로 UI를 표시하는 page.js
파일과 여러 경로에 걸쳐 공유되는 UI를 표시하는 layout.js
파일을 사용하는 것이다.
예를 들어, 첫 번째 페이지를 생성하려면 app 디렉토리 내부에 page.js 파일을 추가하고 그 안에 React 컴포넌트를 정의하여 내보낸다.
//app/page.tsx
export default function Page(){
return <h1>Hello, Next.js!</h1>
}
앱 라우터는 page.js, layout.js, 그리고 template.js 파일을 생성할 수 있도록 새로운 파일 규칙을 도입했습니다.
페이지 (pages)
페이지는 라우트에 고유한 UI를 정의합니다. 중첩된 폴더를 사용하여 라우트를 구성하고, 각 라우트에 대해 page.js 파일을 사용하여 해당 UI를 정의합니다.
레이아웃 (layouts)
레이아웃은 여러 페이지에서 공유되는 UI를 정의합니다. 페이지를 전환할 때 레이아웃은 상태를 유지하고 상호작용을 보존하여 다시 렌더링되지 않습니다. 레이아웃은 중첩이 가능합니다.
layout.js 파일에서는 React 컴포넌트를 default로 내보내어 레이아웃을 정의한다.
이 컴포넌트는 랜더링 중에 자식 레이아웃이나 페이지로 채워질 children prop를 받아야 한다.
export default function DashBoardLayout({
children,
}: {
return (
<section>
{/* 공유 UI를 여기에 포함(헤더 또는 사이드바)*/}
<nav></nav>
{children}
</section>
);
}
이렇게 구성된 파일 시스템을 통해 각 페이지와 레이아웃을 간편하게 정의하고 사용할 수 있습니다.
Root Layout은 app
디렉터리 최상위에서 정의되며 모든 라우트에 적용된다.
이 레이아웃을 사용하면 서버에서 반환된 초기 HTML을 수정할 수 있다.
app
디렉터리에는 반드시 루트 레이아웃이 포함되어야 한다.<html>
과 <body>
태그를 반드시 정의해야 한다. (next.js는 이를 자동으로 생성해주지 않음)export default function RootLayout({
children,
}: {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
이렇게 최상위 레이아웃을 설정하면 모든 페이지에 일관된 레이아웃을 적용할 수 있다.
특정 경로 세그먼트가 활성화될 때, 해당 세그먼트에 매칭되는 폴더 내에 정의된 레이아웃이 적용된다.
이를 중첩 레이아웃이라고 합니다.
예를 들어, app/dashboard/layout.js와 같이 정의된 레이아웃은 대시보드 페이지에서 사용된다.
파일 구조의 기본 동작에 따라, 레이아웃은 자식 레이아웃을 갖고 있으며 이를 children 속성으로 감싸게 된다.
위의 두 레이아웃을 결합하려면 루트 레이아웃 (app/layout.js
)이 대시보드 레이아웃 (app/dashboard/layout.js
)을 둘러싸고, 대시보드 레이아웃은 app/dashboard/*
내부의 경로 세그먼트를 둘러싸게 된다.
Route Groups를 사용하면 공유 레이아웃에서 특정 경로 세그먼트를 선택적으로 포함하거나 제외할 수 있습니다.
일반적으로 app 디렉토리의 중첩된 폴더는 URL 경로와 일치한다.
그러나 이러한 폴더를 라우트 그룹으로 표시하여 해당 폴더가 URL 경로에 포함되지 않도록 할 수 있다.
이는 라우트 세그먼트와 프로젝트 파일을 논리적으로 구성하는 데 유용한다.
폴더 이름을 괄호로 묶음으로써 생성할 수 있다.
URL 경로에 영향을 주지 않고 라우트를 구성한다.
각 그룹의 폴더는 URL에서 생략됩니다. (예 : (marketing) 또는 (shop))
(marketing) 및 (shop) 내부의 경로가 동일한 URL 계층 구조를 공유하더라도 폴더 내에 layout.js 파일을 추가하여 각 그룹에 대해 다른 레이아웃을 생성할 수 있다.
특정 라우트를 레이아웃으로 선택하려면 새 라우트 그룹(예 : (shop))을 만들고 동일한 레이아웃을 공유하는 라우트를 그룹(예 : account and cart)으로 이동한다. 그룹 외부의 라우트는 레이아웃을 공유하지 않는다. (예 : checkout)
여러 루트 레이아웃을 만드려면 최상위 layout.js 파일을 제거하고 각 경로 그룹 내에 layout.js파일을 추가한다.
이것은 완전히 다른 UI/UX를 가진 섹션으로 애플리케이션을 분할하는데 유용하다.
templates는 각 자식 레이아웃 또는 페이지를 둘러싸는 레이아웃과 유사하다.
레이아웃은 경로 간에 지속되고 상태를 유지하는 반면, 템플릿은 탐색 시마다 자식의 새로운 인스턴스를 생성한다.
이는 템플릿을 공유하는 라우트 간에 이동할 때, 컴포넌트의 새 인스턴스가 마운트되고 DOM 요소가 다시 생성되며 상태가 유지되지 않으며 효과가 동기화된다는 것을 의미한다.
특정 동작이 필요한 경우 레이아웃보다 템플릿이 더 적합한 옵션일 수 있다.
예
- CSS 또는 애니메이션 라이브러리를 사용한 진입/이탈 애니메이션
useEffect
를 사용하는 기능 및useState
를 사용하는 기능- 기본 프레임워크 동작 변경. 예를 들어, 레이아웃 내의 Suspense 경계는 페이지 전환 시에만 대체 콘텐츠를 보여주지만 템플릿의 경우 매번 탐색할 때마다 대체 콘텐츠가 표시된다.
특별한 이유가 없는 경우 한 레이아웃을 사용하는 것이 좋다.
동적 라우트는 정확한 세그먼트 이름을 미리 알 수 없는 경우에 사용된다.
이는 주로 동적 데이터를 다룰 때 유용하다.
동적 라우트는 요청 시간에 채워지거나 빌드 시간에 미리 렌더링되는 세그먼트를 가진다.
params
prop으로 layout
, page
, route
및 generateMetadata
함수에 전달된다.예
블로그를 만든다고 가정했을때 다음과 같은 라우트app/blog/[slug]/page.js
를 포함할 수 있으며,
여기서[slug]
는 블로그 게시물의 동적 세그먼트이다.// app/blog/[slug]/page.tsx export default function Page({ params }: { params: { slug: string } }) { return <div>My Post: {params.slug}</div>; }
generateStaticParams
함수를 사용하여 생성할 수 있습니다. 이를 통해 빌드 시간에 정적으로 라우트를 생성할 수 있다.loading.js
를 사용하면 React Suspense와 함께 의미 있는 로딩 UI를 생성할 수 있다. 이는 서버에서 라우트 세그먼트의 내용이 로드되는 동안 대기하는 동안 사용자에게 대체 UI를 제공하는 데 도움이 된다.loading.js
파일을 추가하여 생성한다.React와 Next.js에서 스트리밍이 작동하는 방식을 이해하기 위해서는 서버 측 렌더링(SSR)과 그 제한 사항을 이해하는 것이 도움이 된다.
Suspense를 사용하면 얻을 수 있는 이점
- 스트리밍 서버 랜더링 - 서버에서 클라이언트로 HTML을 점진적으로 랜더링한다.
- 선택적 Hydration - React는 사용자 상호작용을 기반으로 어떤 컴포넌트를 먼저 상호작용 가능하게 할지 우선순위를 정한다.
redirect : 사용자가 처음 요청한 URL이 아닌, 다른 URL로 보내는 것을 뜻한다.
API | 목적 | 어디 | 상태 코드 |
---|---|---|---|
redirect | 돌연변이 또는 이벤트 후 사용자 리디렉션 | 서버 구성 요소, 서버 작업, 경로 처리기 | 307 (Temporary) or 303 (Server Action) |
permanentRedirect | 돌연변이 또는 이벤트 후 사용자 리디렉션 | 서버 구성 요소, 서버 작업, 경로 처리기 | 308 (Permanent) |
useRouter | 클라이언트 측 탐색 수행 | 클라이언트 구성 요소의 이벤트 처리기 | 해당 없음 |
redirects in next.config.js | 경로를 기반으로 들어오는 요청 리디렉션 | next.config.js 파일 | 307(임시) 또는 308(영구) |
NextResponse.redirect | 조건에 따라 수신 요청 리디렉션 | 라 수신 요청 리디렉션 미들웨어 | Any |
redirect
기능redirect
돌연변이나 이벤트 후에 자주 사용됩니다. 'use server'
import { redirect } from 'next/navigation'
import { revalidatePath } from 'next/cache'
export async function createPost(id: string) {
try {
// Call database
} catch (error) {
// Handle errors
}
revalidatePath('/posts') // Update cached posts
redirect(`/post/${id}`) // Navigate to the new post page
}
permanentRedirect
기능'use server'
import { permanentRedirect } from 'next/navigation'
import { revalidateTag } from 'next/cache'
export async function updateUsername(username: string, formData: FormData) {
try {
// Call database
} catch (error) {
// Handle errors
}
revalidateTag('username') // Update all references to the username
permanentRedirect(`/profile/${username}`) // Navigate to the new user profile
}
useRouter는 Next.js에서 제공하는 Hook 중 하나로, 현재 페이지의 라우터 정보를 가져올 수 있게 해주는 기능이다. 이 Hook을 사용하면 현재 페이지의 URL, 쿼리 파라미터, 라우트 매개변수 등과 같은 라우터 정보에 접근할 수 있다.
루트 핸들러는 웹의 요청/응답 API들을 이용하여 특정 루트에 커스텀 리퀘스트 핸들러를 생성할 수 있도록 도와준다.
알아두면 좋은 것: 루트 핸들러는 app디렉토리 내에서만 사용할 수 있습니다. 루트 핸들러들은 pages 디렉토리 내의 API Routes 와 동일하며, 이는 API Routes와 루트 핸들러를 함께 사용하지 않는다는 것을 의미합니다.
루트 핸들러는 app디렉토리 내의 route.js | ts파일에 정의한다:
export const dynamic = 'force-dynamic' // defaults to auto
export async function GET(request: Request) {}
루트 핸들러는 page.js나 layout.js와 비슷하게 app 디렉토리 내에 중첩될 수 있다. 하지만 page.js와 같은 루트 세그먼트 레벨에는 route.js파일이 올 수 없다.
다음과 같은 HTTP 메서드들이 사용 가능하다: GET
, POST
, PATCH
, DELETE
, HEAD
, 그리고 OPTIONS
. 지원되지 않는 메서드가 호출되면, Next.js는 405 Method Not Allowed
응답을 리턴합니다.
네이티브 Request와 Response를 지원하는 것에 더불어, Next.js는 이 둘을 NextRequest, 그리고 NextResponse와 함께 확장하여 더 향상된 케이스들을 위해 편리한 도구들을 제공합니다
GET
메서드를 Response
객체와 함께 이용할 경우, 루트 핸들러들은 기본적으로 정적으로 여겨진다.
// app/items/route.ts
import { NextResponse } from 'next/server';
export async function GET() {
const res = await fetch('https://data.mangodb-api/com/...', {
headers: {
'Content-Type': 'application/json',
'API-Key': process.env.DATA_API_KEY,
}
});
const data = await res.json();
return NextResponse.json({ data })
}
타입스크립트 경고: Response.json()은 유효하지만, 네이티브 타입스크립트의 타입들은 현재 에러를 보여준다. 타이핑된 응답에 대해서 NextResponse.json()을 대신 사용할 수 있다.
정적 페칭은 robots.txt, rss.xml, sitemap.xml 등의 루트들을 위한 커스텀 루트 핸들러를 생성하는 데에 유용할 수 있다.
루트 핸들러는 다음과 같은 경우에 동적이라고 여겨집니다:
Request
객체를 GET
메서드와 함께 사용하는 경우cookies
나 headers
와 같은 동적 함수들을 사용하는 경우route
를 가장 낮은 라우팅 원시 레벨로 고려할 수 있다.
page
와 같은 클라이언트 사이드 네비게이션이나 레이아웃에 관여하지 않는다.route.js
파일은 page.js
와 같은 루트에 위치할 수 없다.