[ File Conventions ] Files

차차·2023년 6월 17일
1

Next Docs

목록 보기
34/34
post-thumbnail

error.js

error.js는 경로 세그먼트에 대한 오류 UI 경계를 정의한다.

// app/dashboard/error.tsx

'use client' // Error components must be Client Components
 
import { useEffect } from 'react'
 
export default function Error({
  error,
  reset,
}: {
  error: Error
  reset: () => void
}) {
  useEffect(() => {
    // Log the error to an error reporting service
    console.error(error)
  }, [error])
 
  return (
    <div>
      <h2>Something went wrong!</h2>
      <button
        onClick={
          // Attempt to recover by trying to re-render the segment
          () => reset()
        }
      >
        Try again
      </button>
    </div>
  )
}

Props

error

error 객체의 인스턴스다. 이 오류는 서버나 클라이언트에서 발생할 수 있다.

reset

응답을 반환하지 않는 error boundary를 재설정하는 함수다.

알아두면 좋은 사항

  • error.js boundary는 클라이언트 컴포넌트여야 한다.

  • error.js boundary는 같은 세그먼트의 layout.js 컴포넌트에서 발생한 오류를 처리하지 않는다. 이는 error boundary가 해당 레이아웃 컴포넌트 내에 중첩되어 있기 때문이다.

  • 특정 레이아웃의 오류를 처리하려면 error.js 파일을 레이아웃의 상위 세그먼트에 배치한다.

  • 루트 레이아웃이나 템플릿 내에서 오류를 처리하려면 error.js의 변형인 app/global-error.js를 사용한다.


global-error.js

root layout.js에서 발생하는 오류를 특별히 처리하려면, 루트 app 디렉토리에 위치한 error.js의 변형인 app/global-error.js를 사용한다.

// app/global-error.js

'use client'
 
export default function GlobalError({
  error,
  reset,
}: {
  error: Error
  reset: () => void
}) {
  return (
    <html>
      <body>
        <h2>Something went wrong!</h2>
        <button onClick={() => reset()}>Try again</button>
      </body>
    </html>
  )
}

알아두면 좋은 사항

  • global-error.js는 활성화되었을 때 루트 layout.js를 대체하므로 자체 <html><body> 태그를 정의해야 한다.
  • 오류 UI를 디자인하는 동안, React 개발자 도구를 사용하여 오류 경계(Error boundaries)를 수동으로 전환하는 것이 도움이 될 수 있다.


layout.js

레이아웃은 라우트 간에 공유되는 UI다.

// app/dashboard/layout.tsx

export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return <section>{children}</section>
}

루트 레이아웃은 루트 앱 디렉토리에서 가장 상위에 위치한 레이아웃이다. <html><body> 태그 및 다른 전역적으로 공유되는 UI를 정의하는 데 사용된다.

// app/layout.tsx

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

Props

children ( 필수적 )

레이아웃 컴포넌트는 children prop을 받아서 사용해야 한다. 렌더링 중에 children은 레이아웃이 감싸고 있는 라우트 세그먼트로 채워진다. 주로 자식 레이아웃 (있는 경우) 또는 페이지의 컴포넌트일 것이다. 그러나 해당되는 경우 Loading이나 Error와 같은 다른 특수 파일일 수도 있다.

params ( 선택적 )

루트 세그먼트에서 해당 레이아웃까지의 동적 라우트 매개변수 객체다.

ExampleURLparams
app/dashboard/[team]/layout.js/dashboard/1{ team: '1' }
app/shop/[tag]/[item]/layout.js/shop/1/2{ tag: '1', item: '2' }
app/blog/[...slug]/layout.js/blog/1/2{ slug: ['1', '2'] }
// app/shop/[tag]/[item]/layout.tsx

export default function ShopLayout({
  children,
  params,
}: {
  children: React.ReactNode
  params: {
    tag: string
    item: string
  }
}) {
  // URL -> /shop/shoes/nike-air-max-97
  // `params` -> { tag: 'shoes', item: 'nike-air-max-97' }
  return <section>{children}</section>
}

알아두면 좋은 점

레이아웃은 searchParams prop을 받지 않는다.

page와 달리 layout 컴포넌트는 searchParams prop을 받지 않는다. 이는 공유된 레이아웃이 탐색 중에 다시 렌더링되지 않기 때문에 navigation 사이에 오래된 searchParams가 발생할 수 있기 때문이다.

클라이언트 측 navigation을 사용할 때, Next.js는 두 라우트 사이에서 공통 레이아웃 아래에 있는 페이지의 일부만 자동으로 렌더링한다.


다음 디렉토리 구조의 예를 들어보면, dashboard/layout.tsx/dashboard/settings/dashboard/analytics의 공통 레이아웃이다.

app
└── dashboard
    ├── layout.tsx
    ├── settings
    │   └── page.tsx
    └── analytics
        └── page.js

/dashboard/settings에서 /dashboard/analytics로 이동할 때, /dashboard/analyticspage.tsx가 서버에서 렌더링된다. 왜냐하면 이것은 변경된 UI이기 때문이다.

반면, dashboard/layout.tsx는 두 라우트 사이에서 공통으로 사용되기 때문에 다시 렌더링되지 않는다.

이 성능 최적화는 레이아웃을 공유하는 페이지 간의 navigation을 빠르게 만들어준다. 왜냐하면 해당 페이지의 데이터 가져오기와 렌더링만 실행하면 되기 때문에 공유 레이아웃이 자체 데이터를 가져오는 라우트 전체를 실행할 필요가 없기 때문이다.

dashboard/layout.tsx가 다시 렌더링되지 않기 때문에, 레이아웃 서버 컴포넌트의 searchParams prop은 navigation 후에 오래된(stale) 상태가 될 수 있다.

대신에, Page의 searchParams prop이나 Client 컴포넌트에서 useSearchParams 훅을 사용하여 클라이언트에서 최신 searchParams와 함께 다시 렌더링되도록 한다.


RootLayout

  • app 디렉토리에는 루트 app/layout.js가 포함되어야 한다.

  • 루트 레이아웃은 <html><body> 태그를 정의해야 한다.

  • 루트 레이아웃에는 수동으로 <title><meta>와 같은 <head> 태그를 추가해서는 안된다. 대신에 <head> 요소의 스트리밍과 중복 제거와 같은 고급 요구 사항을 자동으로 처리하는 Metadata API를 사용해야 한다.

  • route groups를 사용하여 여러 개의 루트 레이아웃을 생성할 수 있다.

  • 여러 개의 루트 레이아웃을 통해 navigation할 경우 전체 페이지 로드가 발생합니다(클라이언트 측 navigation이 아닌 경우). 예를 들어, app/(shop)/layout.js를 사용하는 /cart에서 app/(marketing)/layout.js를 사용하는 /blog로 이동할 경우 전체 페이지 로드가 발생합니다. 이는 여러 개의 루트 레이아웃에만 해당됩니다.



loading.js

loading.jsSuspense를 기반으로 한 즉시 로딩 상태를 생성할 수 있다.

기본적으로 이 파일은 서버 컴포넌트다. 그러나 "use client" 지시문을 통해 클라이언트 컴포넌트로 사용할 수도 있다.

// app/feed/loading.js

export default function Loading() {
  // Or a custom loading skeleton component
  return <p>'Loading...'</p>
}


not-found.js

not-found.js은 라우트 세그먼트 내에서 notFound 함수가 throw될 때 UI를 렌더링하는 데 사용된다. 커스텀 UI를 제공하는 것 외에도, Next.js는 404 HTTP 상태 코드를 반환한다.

// app/blog/not-found.js

import Link from 'next/link'
 
export default function NotFound() {
  return (
    <div>
      <h2>Not Found</h2>
      <p>Could not find requested resource</p>
      <p>
        View <Link href="/blog">all posts</Link>
      </p>
    </div>
  )
}

알아두어야 할 점

notFound() 오류를 예상하여 catch하는 것 외에도, 루트 app/not-found.js 파일은 전체 애플리케이션에서 일치하지 않는 URL을 처리한다. 이는 사용자가 앱에서 처리되지 않은 URL을 방문할 경우 app/not-found.js 파일에서 내보낸 UI가 표시됨을 의미한다.



page.js

page는 라우트에 고유한 UI다.

params(선택적)

루트 세그먼트부터 해당 페이지까지의 동적 라우트 매개변수를 담고 있는 객체다. 예를 들면 다음과 같다.

ExampleURLparams
app/shop/[slug]/page.js/shop/1{ slug: '1' }
app/shop/[category]/[item]/page.js/shop/1/2{ category: '1', item: '2' }
app/shop/[...slug]/page.js/shop/1/2{ slug: ['1', '2'] }

searchParams(선택적)

현재 URL의 검색 매개변수를 포함하는 객체다. 예를 들면 다음과 같다.

URLsearchParams
/shop?a=1{ a: '1' }
/shop?a=1&b=2{ a: '1', b: '2' }
/shop?a=1&a=2{ a: ['1', '2'] }

알아두어야 할 점

  • searchParams는 값을 미리 알 수 없는 동적 API다. 이를 사용하면 페이지가 요청 시 동적 렌더링으로 선택된다.

  • searchParamsURLSearchParams 인스턴스가 아닌 일반 JavaScript 객체를 반환한다.



route.js

Route Handlers를 사용하면 Web 요청응답 API를 사용하여 특정 라우트에 대한 사용자 정의 요청 핸들러를 만들 수 있다.

HTTP Methods

route.js을 사용하면 특정 라우트에 대한 사용자 정의 요청 핸들러를 생성할 수 있다. 다음 HTTP 메서드가 지원된다. GET, POST, PUT, PATCH, DELETE, HEADOPTIONS.

// route.js

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) {}

Route Handlers는 app 디렉토리 내에서만 사용할 수 있다. API Routes (pages)와 Route Handlers (app)를 함께 사용할 필요는 없으며, Route Handlers는 모든 사용 사례를 처리할 수 있어야 한다.


Parameters

request (선택적)

request 객체는 NextRequest 객체로, Web 요청 API의 확장이다. NextRequest를 사용하면 수신된 요청에 대한 추가적인 제어를 할 수 있다. 이를 통해 cookies에 쉽게 액세스하고 확장된 파싱된 URL 객체인 nextUrl에 쉽게 접근할 수 있다.

context (선택적)

// app/dashboard/[team]/route.js

export async function GET(request, context: { params }) {
  const team = params.team // '1'
}

현재 context의 유일한 값은 params이며, 이는 현재 라우트의 동적 라우트 매개변수를 포함하는 객체다.

NextResponse

Route Handlers는 NextResponse 객체를 반환함으로써 Web 응답 API를 확장할 수 있다. 이를 통해 쿠키 설정, 헤더 설정, 리다이렉트 및 리라이트를 쉽게 처리할 수 있다.



Route Segment Config

Route Segment 옵션은 다음 변수들을 직접 내보내는 방식으로 Page, Layout 또는 Route Handler의 동작을 구성할 수 있도록 한다.

OptionTypeDefault
https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic'auto''force-dynamic'
https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamicparamsbooleantrue
https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#revalidatefalse'force-cache'
https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#fetchcache'auto''default-cache'
https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#runtime'nodejs''edge'
https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#preferredregion'auto''global'
// layout.tsx / page.tsx / route.ts

export const dynamic = 'auto'
export const dynamicParams = true
export const revalidate = false
export const fetchCache = 'auto'
export const runtime = 'nodejs'
export const preferredRegion = 'auto'
 
export default function MyComponent() {}

현재 구성 옵션의 값은 정적으로 분석 가능해야 한다. 예를 들어, revalidate = 600은 유효하지만 revalidate = 60 * 10은 유효하지 않다.


Options

dynamic

레이아웃이나 페이지의 동적 동작을 완전히 정적으로 또는 완전히 동적으로 변경한다.

// layout.tsx / page.tsx / route.ts

export const dynamic = 'auto'
// 'auto' | 'force-dynamic' | 'error' | 'force-static'

app 디렉토리의 새로운 모델은 page 디렉토리의 getServerSidePropsgetStaticProps의 바이너리 all-of-nothing 모델보다는 fetch 요청 수준의 세분화된 캐싱 제어를 선호한다. dynamic 옵션은 이전 모델로의 편리한 복귀 방법이며 간단한 마이그레이션 경로를 제공한다.

  • auto (기본값)
    동적 동작으로 전환하지 않는 한 모든 컴포넌트가 동적 동작에 참여하지 않도록 최대한 캐싱한다.

  • force-dynamic
    모든 fetch 요청의 캐싱을 비활성화하고 항상 재검증하여 레이아웃이나 페이지의 동적 렌더링과 동적 데이터 가져오기를 강제한다. 이 옵션은 다음과 동일하다.

    - `pages` 디렉토리의 `getServerSideProps()`
    - 레이아웃이나 페이지의 모든 `fetch()` 요청의 옵션을 `{ cache: 'no-store', next: { revalidate: 0 } }`로 설정하기.
    - 세그먼트 구성을 `export const fetchCache = 'force-no-store'`로 설정하기.
  • error
    동적 함수나 동적 fetch 사용 시 오류가 발생하여 레이아웃이나 페이지의 정적 렌더링과 정적 데이터 가져오기를 강제한다. 이 옵션은 다음과 동일하다.

    - `pages` 디렉토리의 `getStaticProps()`
    - 레이아웃이나 페이지의 모든 `fetch()` 요청의 옵션을 `{ cache: 'force-cache' }`로 설정하기.
    - 세그먼트 구성을 `fetchCache = 'only-cache', dynamicParams = false`로 설정하기.
    - `dynamic = 'error'`는 `dynamicParams`의 기본값을 `true`에서 `false`로 변경한다. 
      `generateStaticParams`에 의해 생성되지 않은 동적 매개변수를 가진 동적으로 렌더링되는 페이지로 다시 돌아가려면 `dynamicParams = true`를 수동으로 설정해야 한다.
  • force-static
    cookies(), headers(), useSearchParams()를 비워진 값으로 반환하여 레이아웃이나 페이지의 정적 렌더링과 정적 데이터 가져오기를 강제한다.


dynamicParams

generateStaticParams로 생성되지 않은 동적 세그먼트가 방문될 때 어떤 동작이 수행되는지를 제어한다.

// layout.tsx / page.tsx

export const dynamicParams = true // true | false,
  • true (기본값)
    generateStaticParams`에 포함되지 않은 동적 세그먼트는 필요할 때 생성된다.

  • false
    generateStaticParams에 포함되지 않은 동적 세그먼트는 404를 반환한다.

  • 이 옵션은 페이지 디렉토리의 getStaticPathsfallback: true | false | blocking 옵션을 대체한다.

  • dynamicParams = true인 경우 세그먼트는 스트리밍 서버 렌더링을 사용한다.

  • dynamic = 'error'dynamic = 'force-static'을 사용하면 dynamicParams의 기본값이 false로 변경된다.

revalidate

레이아웃이나 페이지의 기본 재유효화 시간을 설정한다. 이 옵션은 개별 fetch 요청에서 설정한 revalidate 값에 대체되지 않는다.

// layout.tsx / page.tsx / route.ts

export const revalidate = false
// false | 'force-cache' | 0 | number
  • false (기본값)
    'force-cache'로 캐시 옵션을 설정하거나 동적 함수가 사용되기 전에 발견된 모든 fetch 요청을 캐시하는 기본 heuristic. 사실상 revalidate: Infinity와 동일하며, 리소스를 무기한으로 캐시해야 함을 의미한다. 개별 fetch 요청에서 cache: 'no-store' 또는 revalidate: 0을 사용하여 캐시를 피하고 라우트를 동적으로 렌더링하는 것도 가능하다. 또는 revalidate을 라우트의 기본값보다 작은 양의 양수로 설정하여 라우트의 재유효화 빈도를 증가시킬 수 있다.

  • 0
    동적 함수나 동적 데이터 가져오기가 발견되지 않아도 레이아웃이나 페이지가 항상 동적으로 렌더링되도록 보장한다. 이 옵션은 캐시 옵션을 설정하지 않은 fetch 요청의 기본값을 'no-store'로 변경하지만, 'force-cache'로 옵트인하거나 양수 revalidate를 사용하는 fetch 요청은 변경하지 않는다.

  • number (초 단위)
    레이아웃이나 페이지의 기본 재유효화 빈도를 n초로 설정한다.


revalidate 빈도

  • 단일 라우트의 각 레이아웃과 페이지에서 가장 낮은 revalidate 값이 전체 라우트의 재유효화 빈도를 결정한다. 이를 통해 하위 페이지도 상위 레이아웃과 동일한 빈도로 재유효화된다.
  • 개별 fetch 요청은 라우트의 기본 revalidate보다 낮은 revalidate를 설정하여 전체 라우트의 재유효화 빈도를 증가시킬 수 있다. 이를 통해 특정 기준에 따라 특정 라우트에 대해 동적으로 더 빈번한 재유효화를 선택적으로 사용할 수 있다.

runtime

// layout.tsx / page.tsx / route.ts

export const runtime = 'nodejs'
// 'edge' | 'nodejs'

preferredRegion

// layout.tsx / page.tsx / route.ts

export const preferredRegion = 'auto'
// 'auto' | 'global' | 'home' | ['iad1', 'sfo1']

preferredRegion 및 지원되는 지역에 대한 지원은 배포 플랫폼에 따라 다르다.

preferredRegion이 지정되지 않은 경우, 가장 가까운 상위 레이아웃의 옵션을 상속한다.
루트 레이아웃은 모든 지역을 기본값으로 갖는다.


generateStaticParams

generateStaticParams 함수는 동적 라우트 세그먼트와 함께 사용하여, 요청 시간이 아닌 빌드 시간에 정적으로 생성될 경로 세그먼트 매개변수 목록을 정의하는 데 사용될 수 있다.


Metadata Files API Reference

파일 기반의 메타데이터는 라우트 세그먼트에 특수한 메타데이터 파일을 추가하여 정의할 수 있다.

각 파일 규칙은 정적 파일(예: opengraph-image.jpg) 또는 코드를 사용하여 파일을 생성하는 동적 변형(예: opengraph-image.js)을 사용하여 정의할 수 있다.

파일이 정의되면 Next.js는 자동으로 파일을 제공하며 (생산 환경에서는 캐싱을 위해 해시가 포함됨) 관련된 헤드 요소를 올바른 메타데이터로 업데이트한다. 이는 자산의 URL, 파일 유형 및 이미지 크기와 같은 정보를 포함한다.

File Conventions: Metadata Files


[출처]
https://nextjs.org/docs/app/api-reference/file-conventions

profile
나는야 프린이

1개의 댓글

comment-user-thumbnail
2024년 8월 23일

드디어 마지막 글~ 감사합니다!

답글 달기