Next.js 구조에 대해서 제대로 모르고 사용하다보니 쉽게 해결할 일도 어렵게 만드는 것을 가끔 겪는 것 같습니다.
오늘은 Next.js에서 제공해주는 프로젝트 구조에 대해서 간단하게 살펴볼 예정입니다. 공부하게 되면서 새롭게 알게 되는 것들을 적용할 수 있으니 한번 블로그를 적으면 알아보겠습니다.
Top-level folder
최상위 폴더에는 app
, pages
, public
, src
가 있습니다.
app
app
: App Router를 사용하기 위한 폴더입니다.
- Next.js에서 공유 레이아웃, 중첩 라우팅, 로딩 상태, 오류 처리 등을 지원하는 React Server Components를 기반으로 구축된 새로운 매커니즘입니다.
page
: Page Router를 사용하기 위한 폴더입니다.
- 페이지 개념을 기반으로 구축된 파일 시스템 기반 라우터이며, 파일이 디렉터리에 추가되면 pages 자동으로 경로로 사용할 수 있습니다.
public
: Static Assets 즉, 정적 에셋을 제공할 때, 사용하는 폴더입니다.
src
: application 소스 폴더이며, 필수는 아닙니다.
Top-level files
next.config.js
: Next.js를 구성하는 파일입니다.
- 서버 및 빌드 단계에서 사용되며 브라우저 빌드에는 포함되지 않습니다.
- ESM이 필요한 경우,
next.config.mjs
를 통해 사용할 수도 있습니다. - 12.1.0부터는 async를 통해 비동기로도 사용할 수 있습니다.
- Webpack, Babel 또는 TypeScript에서 파싱되지 않습니다.
package.json
: 프로젝트 종속성 및 스크립트를 관리하는 파일입니다.
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
}
}
next dev
: 개발 모드에서 Next.js를 시작하기next build
: 프로덕션용 애플리케이션을 빌드하기next start
: Next.js 프로덕션 서버를 시작하기next lint
: Next.js의 기본 제공 ESLint 구성을 설정하기
instrumentation.ts
: 코드를 사용하여 모니터링 및 로깅 도구를 애플리케이션에 통합하는 프로세스입니다.
import { registerOTel } from '@vercel/otel'
export function register() {
registerOTel('next-app')
}
- 아직 canary 단계이기에
next.config.js
에서 아래와 같이 설정이 필요합니다. module.exports = { experimental: { instrumentationHook: true, }, }
- 사용하기 위해선 프로젝트의
root
에 있어야 합니다.src
폴더 사용 시, 페이지 또는 앱과 함께 src 안에 파일을 배치해야 합니다.
middleware.ts
: Next.js에서 request 시 사용되는 미들웨어입니다.
- 요청이 완료되기 전에 코드를 실행할 수 있고, 그 요청에 따라 재작성이 가능합니다.
matcher
: 특정 경로에서만 실행되도록 미들웨어를 필터링 하는 기능입니다.- 단일 또는 배열을 이용해서 다중 경로 사용이 가능합니다.
- 전체 정규식을 사용할 수 있습니다.
missing
을 사용하여 미들웨어를 거칠 필요가 없는 prefetch를 무시할 수 있습니다.
export const config = {
matcher: [
'/about/:path*', '/dashboard/:path*', // 다중 경로
'/((?!api|_next/static|_next/image|favicon.ico).*)', // 정규식
{
source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
missing: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
],
}
request.cookies
를 통해 cookie를 사용할 수 있습니다.response.headers.set
을 통해 헤더를 설정할 수 있습니다.Response.json
메서드를 통해 응답을 만들 수 있습니다.
etc.
.env
: 환경 변수를 모아둔 파일입니다..env.local
: local 에서 사용하는 환경 변수를 모아둔 파일입니다..env.production
: Production 에서 사용하는 환경 변수를 모아둔 파일입니다..env.development
: Development에서 사용하는 환경 변수를 모아둔 파일입니다.
.eslintrc.json
: ESLint 구성을 설정하는 파일입니다..gitignore
: Git에서 무시할 파일/폴더를 설정합니다.next-env.d.ts
: Next.js를 위한 타입스크립트 declaration 파일입니다.tsconfig.json
: 타입스크립트 설정 파일입니다.jsconfig.json
: 자바스크립트 설정 파일입니다.
app
라우팅 컨벤션(규칙)
Routing Files
layout.{js,jsx,tsx}
: Layout
routes(해당 경로 이하)에 공유되는 UI입니다.
- root layout은 최상위 레이아웃으로또는태그와 기타 전역적으로 공유되는 UI를 정의하는데 사용됩니다.
- root layout은 반드시 app 디렉토리에 포함되어야 합니다.
- root layout은 반드시 html과 body 태그를 정의해야 합니다.
- root layout에서 title, meta 태그를 수동으로 추가해서는 안됩니다.
- Metadata API를 통해서 추가해야 합니다.
import { Metadata } from 'next'
- layout은 children 속성을 사용해야 합니다.
- layout에서는
searchParams
를 받지 못합니다.- 공유된
layout
은 다시 렌더링되지 않기 때문입니다.
- 공유된
page.{js,jsx,tsx}
: Page
경로에 유니크한 UI입니다.
params
: 루트 세그먼트에서 해당 페이지로 내려가는 동적 경로 매개변수가 포함된 객체입니다.searchParams
: 현재 URL의 검색 매개변수가 포함된 객체입니다.searchParams
는 값을 미리 알 수 없는 동적 API입니다. 이를 사용하면 요청 시 페이지가 동적 렌더링으로 선택됩니다.searchParams
는URLSearchParams
인스턴스가 아닌 일반 JavaScript 객체를 반환합니다.
loading.{js,jsx,tsx}
: Loading UI
Suspense
를 기반으로 즉각적인 로딩 상태를 생성할 수 있습니다.
- default는 Sever Component 이지만
"use client"
를 통해 Clinet Component로 사용할 수 있습니다.
not-found.{js.jsx.tsx}
: Not found UI
경로 세그먼트 내에서 notFound
함수가 던져질 때 UI를 렌더링하는 데 사용됩니다. 스트리밍되지 않은 응답의 경우 404를 반환합니다.
- 앱에서 처리하지 않는 URL을 방문하는 사용자에게는
app/not-found.js
파일에서 내보낸 UI가 표시됩니다. - async를 통해 데이터를 가져올 수 있습니다.
import Link from 'next/link'
import { headers } from 'next/headers'
export default async function NotFound() {
const headersList = headers()
const domain = headersList.get('host')
const data = await getSiteData(domain)
return (
<div>
<h2>Not Found: {data.name}</h2>
<p>Could not find requested resource</p>
<p>
View <Link href="/blog">all posts</Link>
</p>
</div>
)
}
error.{js,jsx,tsx}
: Error UI
경로 세그먼트의 오류 UI 경계를 정의합니다. Server Component와 Client Component에서 발생하는 예기치 않은 오류를 포착하고 대체 UI를 표시하는 데 유용합니다.
error.js
의 boundary들은 클라이언트 컴포넌트여야 합니다.- production 에서는 민감한 정보가 유출되지 않도록 세부 정보가 제거 됩니다.
error.js
의 boundary는layout.js
컴포넌트에 중첩되어 있기 때문에 동일한 위치에 있으면 layout에서 발생한 에러는 캐치하지 못합니다.
Props
error
: Client Component로 전달되는 에러 객체의 인스턴스입니다.error.message
- Client Component에서 전달된 에러의 경우, 에러 메시지가 됩니다.
- Server Component에서 전달된 에러의 경우, 일반 에러 메시지가 됩니다.
errors.digest
를 사용해서 서버 측 로그에서 해당 오류를 일치시킬 수 있습니다.
error.digest
: Server Component에서 발생한 에러의 자동 생성 해시입니다. 서버 측 로그에서 해당 오류와 일치하는 데 사용할 수 있습니다.
reset
: Error Boundary를 재설정하는 함수입니다. 이 함수가 실행되면 Error Boundary 내용을 다시 렌더링하려고 시도하며, 성공하면 fallback Error Component가 다시 렌더링한 결과를 대체합니다.- 사용자에게 오류 복구를 시도하라는 메시지를 표시하는 데 사용할 수 있습니다.
global-error.{js,jsx,tsx}
: Global error UI
root의 layout.js
의 오류를 처리하기 위해 필요한 파일입니다.
global-error.js
는 활성화되면 루트layout.js
를 대체하므로 자체<html>
및<body>
태그를 정의해야 합니다.
'use client'
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<html>
<body>
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</body>
</html>
)
}
route.{js,ts}
: API endpoint
라우트 핸들러를 사용하면 웹 Request 및 Response API를 사용하여 지정된 경로에 대한 사용자 지정 요청 핸들러를 만들 수 있습니다.
request
는 웹 요청 API의 확장인NextRequest
객체입니다.NextRequest
를 사용하면 쿠키 및 확장된 파싱된 URL 객체nextUrl
에 쉽게 액세스하는 등 들어오는 요청을 추가로 제어할 수 있습니다.
- 현재
context
의 유일한 값은 현재 경로에 대한 동적 경로 매개변수가 포함된 객체인params
뿐입니다. - 라우트 핸들러는
NextResponse
객체를 반환하여 웹 응답 API를 확장할 수 있습니다.- 이를 통해 쿠키, 헤더, 리디렉션 및 재작성을 쉽게 설정할 수 있습니다.
// route.ts
export async function GET(request: Request) {}
export async function HEAD(request: Request) {}
export async function POST(request: Request) {}
export async function PUT(request: Request) {}
export async function DELETE(request: Request) {}
export async function PATCH(request: Request) {}
// If `OPTIONS` is not defined, Next.js will automatically implement `OPTIONS` and set the appropriate Response `Allow` header depending on the other methods defined in the route handler.
export async function OPTIONS(request: Request) {}
template.**{js,jsx,tsx}**
: Re-rendered layout
각 하위 레이아웃 또는 페이지를 감싸고 있다는 점에서 레이아웃과 유사합니다. 경로 전체에 걸쳐 상태를 유지하는 레이아웃과 달리 템플릿은 탐색 시 각 하위 레이아웃에 대해 새 인스턴스를 생성합니다.
default.js.{js,jsx,tsx}
: Parallel route fallback page(병렬 경로 폴백 페이지)
해당 파일은 전체 페이지 로드 후 Next.js가 슬롯의 활성 상태를 복구할 수 없는 경우 병렬 경로 내에서 폴백을 렌더링하는데 사용됩니다.
Dynamic Routes
[folder]
(Dynamic route segment)
: 동적 세그먼트는 폴더의 이름을 대괄호로 묶어 만들 수 있습니다.
Route | Example URL | params |
---|---|---|
app/blog/[slug]/page.js | /blog/a | { slug: 'a' } |
app/blog/[slug]/page.js | /blog/b | { slug: 'b' } |
app/blog/[slug]/page.js | /blog/c | { slug: 'c' } |
[…folder]
(Catch-all route segment)
: 괄호 안의 [...폴더명] 안에 줄임표를 추가하여 동적 세그먼트를 모든 후속 세그먼트를 포함하는 세그먼트로 확장할 수 있습니다.
Route | Example URL | params |
---|---|---|
app/shop/[...slug]/page.js | /shop/a | { slug: ['a'] } |
app/shop/[...slug]/page.js | /shop/a/b | { slug: ['a', 'b'] } |
app/shop/[...slug]/page.js | /shop/a/b/c | { slug: ['a', 'b', 'c'] } |
[[...folderName]]
(Optional catch-all route segment)
: 이중 대괄호 안에 매개변수를 포함하여 포괄 세그먼트를 선택사항으로[[...folderName]]
만들 수 있습니다 .
- catch-all 과 Optional catch-all 세그먼트의 차이점은 Optional을 사용하면 매개변수가 없는 경로도 match가 가능합니다.
Route | Example URL | params |
---|---|---|
app/shop/[[...slug]]/page.js | /shop | {} |
app/shop/[[...slug]]/page.js | /shop/a | { slug: ['a'] } |
app/shop/[[...slug]]/page.js | /shop/a/b | { slug: ['a', 'b'] } |
app/shop/[[...slug]]/page.js | /shop/a/b/c | { slug: ['a', 'b', 'c'] } |
Route Groups and Private Folders
(folder)
(Route Group): 폴더 이름을 괄호로 묶어 URL 경로에 영향을 주지 않고 경로 그룹을 만들 수 있습니다- 레이아웃에 특정 세그먼트 선택:
(folder)
디렉터리 내에layout.js
을 만들면 폴더 내에 있는 세그먼트에만layout.js
가 적용됩니다. - 여러 루트 레이아웃 만들기: 최상위
layout.js
파일을 제거하고 각 경로 그룹 안에 layout.js 파일을 추가하여 여러 루트 레이아웃을 만들 수 있습니다.
- 레이아웃에 특정 세그먼트 선택:
_folder
(Private Folder): 폴더 및 모든 하위 세그먼트를 라우팅에서 제외됩니다.- 해당 폴더가 비공개 구현 세부 정보이며 라우팅 시스템에서 고려하지 않아야 함을 나타내므로 해당 폴더 및 모든 하위 폴더가 라우팅에서 제외됩니다.
- 다음과 같은 경우에 유용할 수 있습니다.
- UI 로직과 라우팅 로직을 분리하는 경우.
- 프로젝트와 Next.js 에코시스템 전반에서 내부 파일을 일관성 있게 정리.
- 코드 편집기에서 파일 정렬 및 그룹화.
- 향후 Next.js 파일 규칙과 충돌할 수 있는 잠재적 명명 충돌 방지.
Parallel and Intercepted Routes (병렬 및 인터셉트 경로)
@folder
(Named slot)
: 병렬 경로는 명명된 슬롯을 사용하여 만들어집니다. 슬롯은 @폴더 규칙으로 정의됩니다.
- Slot은 부모
layout.js
에props
으로 전달됩니다. - 슬롯은 경로 세그먼트가 아니며 URL 구조에 영향을 미치지 않습니다.
export default function Layout({
children,
team,
analytics,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
<>
{children}
{team}
{analytics}
</>
)
}
(.)folder
(Intercept same level)
(..)folder
(Intercept one level above)(..)(..)folder
(Intercept two levels above)(…)folder
(Intercept from root)
: 상대 경로 규칙 ../
과 유사하지만 세그먼트의 경우 (..)
규칙을 사용하여 경로를 가로채는 것을 정의할 수 있습니다. (같은 param를 사용할 수 있음)
(..) 규칙
은 파일 시스템이 아닌 경로 세그먼트를 기준으로 한다는 점에 유의하세요.
- Intercepting Routes를 병렬 경로와 함께 사용하여 모달을 만들 수 있습니다.
- URL을 통해 모달 콘텐츠를 공유할 수 있도록 만들기.
- 페이지를 새로 고칠 때 모달을 닫지 않고 컨텍스트 유지.
- 이전 경로로 이동하지 않고 뒤로 탐색할 때 모달을 닫습니다.
- 앞으로 탐색할 때 모달을 다시 열기.
정리
이번에 정리하면서 Next.js에서 제공하는 폴더 구조를 보며 어떻게 하면 더 프로젝트를 잘 정리하고 효율적으로 사용할 수 있는지 고민하게 되는 시간이었던 것 같습니다. 특히 병렬로 라우팅을 한다는걸 보고 멍해졌지만 문득 회원의 등급에 따라 보여줄 수 있는 컨텐츠를 선별해서 보여줄 수 있겠다 싶어서 고민을 해봐야겠습니다.
'Front-End' 카테고리의 다른 글
React State 관리: 10년간의 교훈 (0) | 2024.03.03 |
---|---|
React-Native에 대해서 알아보자! (0) | 2024.02.17 |
2024년 상태 관리의 종결 : zustand (0) | 2024.01.19 |
Virtualize List with react-window (2) | 2023.12.17 |
[FE RoadMap] 00.Internet 좀 봐봐 (0) | 2023.03.27 |