[ 살펴보기 ] NextJS - Layout & Template

[ 살펴보기 ] NextJS - Layout & Template

·

4 min read

App route에서의 layout 구성은 Page route에서의 layout과는 상당히 다르다. 또한 app route에서는 layout와 비슷한 template 파일 역시 존재한다. 해당 포스트를 통해 layout과 template에 대해 살펴보자.

Layout

NextJS project 폴더 구조가 다음과 같다고 가정해보자.

/app
    layout.tsx
    /user
        page.tsx
    /order
        page.tsx
    /login
        page.tsx
    /home
        page.tsx

만약 폴더 구조와 위와 같을 때 user, order, login, home은 모두 같은 layout을 공유한다.

만약 layout.tsx의 내용이 다음과 같다면 user, order 등 각각의 페이지 내용은 layout.tsx의 children 부분이 들어가게 된다.

const RootLayout = ({ children }: Props) => {
  return (
    <html lang="en">
      <body>
        <div className="flex flex-col space-y-5">
          <LayoutHeader />
          {children} 
          <LayoutFooter />
        </div>
      </body>
    </html>
  );
};

export default RootLayout;

만약 폴더 구조가 다음과 같다면 어떻게 될까?

/app
    layout.tsx
    /user
        layout.tsx
        page.tsx
    /order
        page.tsx
    /login
        page.tsx
    /home
        page.tsx

위의 예제에선 /user 폴더에 새로운 layout.tsx을 추가해 주었다. layout을 특정 route에 추가할 때 주의할 점은 새로 추가된 layout이 상단의 layout을 대체하진 않는다는 점이다. 하위 폴더에 추가된 layout 역시 상위에 존재하는 layout의 children prop의 일부로서 함께 포함된다.

예를들어 /user에 추가된 layout이 다음과 같다면


const UserLayout = ({ children }: Props) => {
  return <section id="user-layout">{children}</section>;
};

export default UserLayout ;

user 페이지가 사용하는 layout의 최종 모습은 다음과 같다.

const RootLayout = ({ children }: Props) => {
  return (
    <html lang="en">
      <body>
        <div className="flex flex-col space-y-5">
          <LayoutHeader />
            <section id="user-layout">
              {children} // user page 내용
            </section>
          <LayoutFooter />
        </div>
      </body>
    </html>
  );
};

export default RootLayout;

이렇듯 하위 폴더에 layout을 추가해 nested layout 형태를 구성할 수 있지만 하위 layout이 상위 layout을 대체하진 않는다.

그렇다면 특정 카테고리로 구분되는 페이지 마다 다른 layout을 적용하고자 하려면 어떻게 해야할까? 이런 경우에 app route의 route group을 적용할 수 있다.

/app
    layout.tsx // root layout
    /(public)
        layout.tsx
        /login
            page.tsx
        /home
            page.tsx
    /(protected)
        layout.tsx
        /user
            page.tsx
        /order
            page.tsx

위의 예제에서는 (public)과 (protected)라는 두 개의 route group을 통해 route group 마다 서로 다른 layout을 사용하도록 구성했다. 폴더 이름을 위와 같이 괄호에 싸서 생성하면 app route에선 route group으로 인식되어 접근하는 page url에는 영향을 주지 않는다.

즉, login 페이지에 접근할 때는 /login, user 페이지에 접근할 때는 /user url로 별다른 변화 없이 접근할 수 있다.

위와 같이 root layout 역할을 하는 layout.tsx 파일이 있고 여러 route group을 구성해 route gorup 마다 layout을 추가할 때 주의할 점은 html와 body tag는 root layout에 이미 존재하기 때문에 route group에 추가되는 layout에는 새로 html, body tag가 추가되어선 안된다.

Route group을 구성하여 layout을 추가한다고 해도 해당 layout이 상단 layout에 포함되는 것은 변하지 않는다. 즉, 위의 예제의 목적은 서로 다른 layout을 필요로 하는 페이지를 route group으로 묶어 같은 root layout 아래서 다른 layout UI를 제공하는 것이다.

만약 위의 예제와는 달리 root layout 역할을 하는 /app/layout.tsx이 없다면 각 route group에 있는 layout.tsx 파일이 각자의 group에서 root layout이 되기에 각 layout.tsx 파일은 html, body tag를 포함해야 한다.

/app
    /(public)
        layout.tsx // html, body tag 필요
        /login
            page.tsx
        /home
            page.tsx
    /(protected)
        layout.tsx // html, body tag 필요
        /user
            page.tsx
        /order
            page.tsx

또 하나 기억해야 하는 사항은 같은 layout을 사용하는 route 사이에서 navigation이 발생하면 해당 layout은 다시 render되지 않는다.

다음 폴더 구조를 예제로 살펴보자.

/app
    layout.tsx // root layout
    /user
        layout.tsx // user layout
        page.tsx
        /history
            page.tsx
    /order
        page.tsx
    /login
        page.tsx
    /home
        page.tsx

만약 user, order, login, home page 사이에 navigation이 발생하면 root layout은 다시 render되지 않는다. 하지만 다른 페이지에서 user 페이지로 이동하면 user layout은 다시 render된다. ( user/history 제외 )

위의 예제에서 user 페이지와 user/history 페이지는 같은 layout을 공유한다. ( user layout ) 그렇기에 user페이지와 user/history 페이지 사이에서 navigation이 발생하면 user layout은 다시 render되지 않는다.

Template

Template와 layout과의 가장 큰 차이점은 template이 client component일 때 발생한다. layout은 client component일지라도 해당 layout을 공유하는 page사이에 navigation이 발생해도 re-render가 되지 않는 반면 template은 client component일 때 같은 template을 공유하는 page 사이에서 navigation이 발생했을 때 re-render가 발생한다.

/app
    template.tsx // root template
    layout.tsx // root layout
    /user
        page.tsx
    /order
        page.tsx
    /login
        page.tsx
    /home
        page.tsx

만약 route 구조가 위와 같고 template이 client component라면 template을 공유하는 user, order, login, home 사이에 navigation이 발생할 때 마다 template은 다시 render된다.

그리고 layout과 template이 같은 level에 존재한다면 layout children에 template이 포함된다. 즉 계층 구조는 다음과 같다.

<RootLayout>
  <RootTemplate>{children}</RootTemplate>
</RootLayout>

layout와 같이 template 역시 nested 구조를 가질 수 있다. 아래는 /order route에 template을 하나 더 추가한 예제이다.

/app
    template.tsx // root template
    layout.tsx // root layout
    /user
        page.tsx
    /order
        tempalte.tsx // order template
        page.tsx
    /login
        page.tsx
    /home
        page.tsx

만약 route folder 구조가 위와 같다면 order 페이지는 다음과 같은 구조를 가진다.

<RootLayout>
  <RootTemplate>
    <OrderTemplate>
      {children} // order page 내용
    </OrderTemplate>
  </RootTemplate>
</RootLayout>

만약 layout의 형태가 아래와 같다면

const RootLayout = ({ children }: Props) => {
  return (
    <html lang="en">
      <body className={inter.className}>
        <div className="flex flex-col space-y-5">
          {children}
          <div id="foot-layout" className="flex space-x-10">
            <footer>footer</footer>
          </div>
        </div>
      </body>
    </html>
  );
};

template이 적용되는 범위는 children이며 그 외의 부분은 template의 영향을 받지 않는다. 결과적으로 다음과 같이 template이 적용된다.

const RootLayout = ({ children }: Props) => {
  return (
    <html lang="en">
      <body className={inter.className}>
        <div className="flex flex-col space-y-5">
          <Template>{children}</Template> // template 범위
          <div id="foot-layout" className="flex space-x-10">
            <footer>footer</footer>
          </div>
        </div>
      </body>
    </html>
  );
};

또한 폴더 구조에 따라 template과 parallel route 함께 사용할 때 route slot 갯수 마다 tempalte이 적용될 수 있으니 주의가 필요하다. ( 참고 - [ 살펴보기 ] Parallel Route )