Next.js App Router - Routing(2)

Minkyu Shin·2023년 6월 4일
1

Next.js

목록 보기
2/9
post-thumbnail
post-custom-banner

Next.js

nextjs.org/docs 공식문서의 내용을 번역, 정리한 내용입니다.
사진 출처는 nextjs.org 공식문서입니다.

Routing

Layouts

레이아웃은 여러개의 페이지에서 공유되는 UI이다. 레이아웃은 페이지 이동 시 state를 유지시키고 재렌더링 되지 않으며 중첩되어 사용될 수 있다. 레이아웃은 기본적으로 서버 컴포넌트이지만, 클라이언트 컴포넌트로 설정해 줄 수 있다.

레이아웃은 layout.js 파일에서 리액트 컴포넌트를 default export 하면 사용이 가능하며, children prop을 받아 렌더링 시 하위 레이아웃 또는 페이지를 내부에 보여지게 한다.

export default LayoutExample({ children }) {
  return (
    <Navbar/>
    <main>
      {children}
    </main>
  );
}

가장 최상단에 존재하는 레이아웃, 즉 /app 경로에 위치한 layout.js 파일을 Root Layout 이라고 하며, 애플리케이션의 모든 페이지에서 공유된다. Root Layout은 반드시 htmlbody 태그를 포함해야 한다, next.js가 자동으로 생성해주지 않기 때문이다. Root Layout을 설정하면 서버에서부터 반환되는 최초의 HTML을 수정해줄 수 있다.
모든 route segment는 각자의 레이아웃을 정의할 수 있고, 해당 segment의 모든 페이지가 그 레이아웃을 공유하게 된다.

중요한 점은 만약 부모 레이아웃이 존재한다면, 자식 레이아웃을 감싸게 된다.

또한, 레이아웃은 데이터 fetching을 할 수 있다. 하지만 부모 레이아웃에서 자식 레이아웃으로 데이터를 전달하는 것은 불가능하다.

Templates

템플릿은 자식 레이아웃 또는 페이지를 감싸주는 레이아웃과 비슷한 점이 있다. 하지만, 페이지 이동 시에 자식 컴포넌트에 대한 새로운 인스턴스를 만든다. 즉, 사용자가 템플릿을 공유하는 routes 사이를 이동하게 되면 컴포넌트의 새로운 인스턴스가 mount되고, DOM 요소가 재생성되며 state가 유지되지 않고 effect들이 재 동기화 된다.
레이아웃 대신 템플릿을 사용할 수 있는 경우는 다음과 같을 것이다.

  • useEffectuseState 에 의존하는 기능들
  • CSS 또는 애니메이션 라이브러리들을 사용해 enter/exit 애니메이션을 사용할 경우
export default function Template({ children }) {
  return <div>{children}</div>
}

단, Next.js는 특별한 경우 아니고서는 레이아웃 사용을 권장한다고 말한다.

Modifying <head>

app 디렉토리에서 titlemeta 와 같은 <head> 의 HTML 요소를 수정할 수 있다. 이 때 내장된 SEO support를 활용하면 된다.
layout.js 또는 page.js 파일에서 metadata 라는 이름으로 객체를 export 하거나(static) generateMetadata 함수를 활용(dynamic)하면 된다.

// app/page.js
export const metadata = {
  title: 'Example Next.js',
};

// or
export async function generateMetadata({ params }) {
  return {
    title: 'Exmaple Next.js',
  };
}

export default function Page() {
  return <div>Home</div>;
}

metadata 객체와 generateMetadata 함수는 서버 컴포넌트에서만 지원이 되고, 두 방식을 동일한 route segment에서 동시에 사용할 수는 없다.

Root Layout에서 수동으로 <head> 태그를 추가해서는 안되고 위에 언급된 Metadata API를 활용하여 <head> 요소를 다루어야 한다.

Route Group

일반적으로 app 디렉토리 내의 중첩된 디렉토리 구조는 URL 경로에 반영이 된다. 하지만 간혹 URL 경로에 특정 디렉토리를 포함하고 싶지는 않지만 유관된 파일을 모아둘 디렉토리가 필요하기도 하다. 이 때 사용할 수 있는 것이 Route Group 이다.
Route Group은

  • route들을 그룹으로 묶어두고 싶을 때 (e.g. 사이트 섹션, 특정 의도, 팀)
  • 동일한 route segment 단계에서 중첩된 레이아웃을 사용하고 싶을 때
    - 같은 segment에서 여러개의 중첩 레이아웃을 만들거나
    - 공통 segment에서 일부 부분집합에 레이아웃을 추가하고 싶을 때

사용할 수 있다.

사용방법은 매우 간단하다. 폴더의 이름을 소괄호 () 로 감싸주면 된다.

위와 같이 Route Group을 사용한 디렉토리는 URL 경로에 반영되지 않는다.

동일한 URL 위계를 가지더라도 각 그룹에 서로 다른 레이아웃을 적용할 수 있게 된다.

일부 segment만 특정한 레이아웃을 적용하기 위해 Route Group을 사용하기도 하며,

최상단의 layout.js 파일을 없애고 Route Group을 생성한 뒤 각각에 layout.js 파일을 만들어준다면 여러개의 Root Layout을 적용할 수도 있다.

Route Group을 사용할 때 몇가지 주의해야 할 점도 있다.

  1. Route Group은 URL 경로에 포함되지 않으므로, Route Group을 포함한 경로는 다른 경로와 동일한 URL 경로를 가지고 있으면 안된다.
    예를 들어, (a)/about/page.js(b)/about/page.js/about 이라는 동일한 경로로 연결되므로 사용이 불가하다.
  2. 여러개의 Root Layout을 적용하기 위해 최상단의 layout.js 파일을 없애주었을 경우 Route Group 중 하나에는 page.js 파일이 정의되어야 한다.
  3. 다중 Root Layout 간에 이동하는 동작은 full page load를 발생시킨다.

Dynamic Routes

segment 이름을 페이지에 실제로 접근하기 전에 확실히 모르고 동적인 데이터를 기반으로 route를 생성하고 싶을 때 동적 라우팅을 활용하게 된다.
동적 라우팅을 사용하는 방식은 Page Router와 동일하다. 폴더의 이름을 대괄호 [] 로 감싸주면 dynamic segment가 생성된다. 이 때 dynamic segment는 request time에 채워지거나 build time에 프리렌더링 된다. dynamic segment는 layout , page , route , generateMetadata 함수에 params 라는 prop으로 전달된다.

예를 들어 app/blog/[slug]/page.js 라는 경로를 가진 블로그가 있다면 [slug] 부분이 dynamic segment가 된다. URL 상 /blog/a 라는 경로로 접근하게 되면 params에는 { slug: 'a' } 객체가 담기게 된다. 이는 request time에 요청이 들어오면 자동으로 생성되는데, build time에 경로를 정적으로 생성하는 방법도 있다.

generateStaticParams

generateStaticParams 함수는 build time에 정적으로 route를 생성하는 함수이다.

// app/blog/[slug]/page.js
export async function generateStaticParams() {
  const res = await fetch('https://.../posts');
  const posts = res.json();
  
  return posts.map((post) => ({
    slug: post.slug,
  }));
}

이 함수를 활용하면 데이터를 좀 더 효율적으로 다룰 수 있다. 여러 generateStaticParams, layout, page에서 동일한 arguments를 가진 fetch 요청이 발생할 경우 모두 합쳐져 한번의 요청으로 처리되게 되기 때문이다. 이 장점을 통해 build time을 감소시킬 수 있다.

Catch-all Segments

다음으로, 폴더 이름 앞에 ... 을 붙여 이후 이어지는 모든 segment에까지 dynamic segment를 연장할 수 있다.
예를 들어 이전에 본 예시를 app/blog/[...slug]/page.js 로 변경해 준다면 /blog/a/b/c 라는 URL도 app/blog/[...slug]/page.js 파일과 연결되는 효과를 가져온다. params에는 배열 형태의 데이터가 담기게 되는데, { slug: ['a', 'b', 'c'] } 가 담긴다.

Catch-all Segments를 선택적으로 활용할 수 있도록 하는 방법도 있다. [[...folderName]] 과 같이 대괄호 2개로 감싸주는 방식인데, app/blog/[[...slug]]/page.js 와 같이 사용한다면 /blog , /blog/a/b/c URL 모두 동일한 페이지와 연결된다.

profile
개발자를 지망하는 경영학도
post-custom-banner

0개의 댓글