Frontend is 실전

[Next] Next.js의 layout 적극 활용해 보기

킹경후 2024. 12. 3. 21:14

next.js를 사용하다 보면, 가끔씩 모든 페이지에서 보이는 컴포넌트가 필요할 때가 있다.

예를들면 헤더라던가...내비게이션바라던가...

또는 앱 라우팅을 사용할 때, 특정 라우터를 가지는 모든 컴포넌트들이 공통된 스타일을 가진다던가...

보통 React였다면 전체적인 틀을 가지는 공용 컴포넌트를 설정해 놓고, 라우팅 경로에 따라 각각 다른 데이터를 넣어주는 방식을 사용할테지만, Next.js에는 'Layout'이라는 아주 좋은 기능을 제공하고 있다.

그런 의미에서, layout을 어떻게 사용하고 또 적용하는지에 대해 실제 페이지 제작을 통해 알아보도록 하자.


1. layout의 적용 범위 

우선 레이아웃은 'Root layout'과 'Local layout'으로 나뉜다.

루트 레이아웃은 말 그대로 '앱 내 모든 페이지에 적용되는 레이아웃'을 말하고,

로컬 레이아웃은 특정 페이지, 또는 '페이지 그룹에만 적용되는 레이아웃'을 말한다.

 

루트 레이아웃은 app 폴더 내에 존재하는 layout.tsx 파일, 로컬 레이아웃은 각각의 라우팅 폴더 내에 존재하는 layout.tsx 파일이다.

쉽게 말하면, layout.tsx 파일이 있는 폴더 내에 영향을 끼친다고 보면 되겠다.

다만, 같은 스타일을 적용한다면 파일과 가장 가까운 layout의 영향을 받는다. 

위 그림에서, 루트 레이아웃에서는 빨간 배경 / 노랑 텍스트를 지정하고

로컬 레이아웃에서는 보라 텍스트를 지정했다고 하자.

 

로컬 레이아웃은 배경색을 지정하지 않았으므로, 루트 레이아웃의 배경색인 빨간색이 그대로 적용되지만

텍스트 색상은 설정 했으므로, 루트 레이아웃의 글자 색과는 상관 없이 로컬 레이아웃의 텍스트 색상이 적용된다.

 

기본적인 레이아웃의 동작 원리를 알았으니, 이제 실전에서 적용해보도록 하자.


2. 실제 사용해 보기

현재 만들고자 하는 프로젝트는 기존에 블로그에만 쓰던 알고리즘 문제풀이를 웹 페이지로 배포하려는 프로젝트이다.

사이트는 우선 백준, 프로그래머스 2개 사이트를 이용하고 있으며, 루트인 app 아래 programmers, baekjoon이 있고

각 사이트별 라우팅 경로 아래에는 난이도, 그리고 난이도 아래에는 각각의 문제들이 들어가 있는 형식이다.

아직 사이트의 기초적인 폴더 구조만 잡혀있으므로, 우선 상단 내비게이션 바를 모든 페이지에 적용하는 것을 먼저 해보자.

 

import Image from "next/image";
import img from "@/shared/img/hoo.png";

function TopNavbar() {
  return (
    <div className="flex items-center gap-3 w-full h-16 bg-gray-50 p-4">
      <Image src={img} alt="logo" width={30} height={30} />
      <span className="text-[#1d2a3d] text-2xl font-black">hoorogrammers</span>
    </div>
  );
}
export default TopNavbar;

 

내비게이션바의 코드이다. 

이 TopNavBar를 루트 레이아웃으로 불러와서, body 태그 안에 넣어주기만 하면 바로 적용이 된다.

 

import "@/shared/css/index.css";
import TopNavbar from "@/widgets/TopNavbar";

export const metadata = {
  title: "Next.js",
  description: "Generated by Next.js",
};

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

 

이제 이 TopNavBar가 루트 (/), 그리고 각 라우터에 제대로 적용이 되었다면 성공이다.

 

루트

 

백준
프로그래머스

 

모든 라우터에 제대로 적용이 된 것을 볼 수 있다.


이번에는 루트에서는 사용되지 않지만, 각 라우터에서 사용되는 사이드 내비게이션 바를 만들어 보자.

import ProblemList from "./ProblemList";

function SideNavBar() {
  return (
    <div className="flex flex-col gap-8 w-80 h-full border border-r-gray-300 border-y-gray-50 p-4">
      <ProblemList>백준</ProblemList>
      <ProblemList>프로그래머스</ProblemList>
    </div>
  );
}
export default SideNavBar;

아주 간단한 사이드 내비게이션 바를 만들어준 뒤, 각 라우터의 레이아웃으로 이동해준다.

 

import "@/shared/css/index.css";
import SideNavBar from "@/widgets/SideNavBar";

export const metadata = {
  title: "프로그래머스",
};

export default function PLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="flex w-full h-[calc(100vh-64px)]">
      <nav>
        <SideNavBar />
      </nav>
      <main>{children}</main>
    </div>
  );
}

 

여기서 아주아주아주 중요한 것이 있다.

next 프로젝트를 처음 만들 때, 기본적으로 app 폴더 안의 layout.tsx 파일만 있을 것이다.

이후 우리가 따로 폴더를 만들어 라우팅을 해준다면, 해당 폴더에는 page와 layout이 자동으로 만들어지지 않기 때문에 직접 파일을 만들어야 한다.

이때 새로 만든 레이아웃 파일에 기존 app의 레이아웃을 그대로 복사 붙여넣기 하면 된다

....고 하면 큰일난다!

각 라우팅 경로 내의 페이지는 폴더 내의 레이아웃을 따라간다.

쉽게 말해

  • app
    • programmers
    • baekjoon

위와 같은 폴더 구조가 있다면, programmers는 programmers 폴더 안의 레이아웃을, baekjoon은 baekjoon 폴더 안의 레이아웃을 따라간다는 것이다.

 

app 폴더 안의 레이아웃은 html부터 body까지 모두 레이아웃 내에서 리턴하고 있다.

즉, 프로젝트 생성시부터 존재하는 app 폴더 내의 layout.tsx는 html 전체에 적용되는 레이아웃을 담고 있는 것이다.

그러므로 programers의 레이아웃에 app의 레이아웃을 복붙한다면, programmers로 라우팅했을때 html의 레이아웃 전체가 바뀌어 버리게 된다.

우리가 만든 상단 내비게이션 바는 루트의 html에 붙어있는데, programmers에서 html이 바뀌어 버리면 당연히 상단바는 사라질 것이다.

 

그렇기 때문에 라우팅 경로의 레이아웃은 그냥 필요한 태그만 리턴해주면 된다.

 

그러면 다시 코드로 돌아와서, 사이드바를 레이아웃에 붙여보자

import "@/shared/css/index.css";
import SideNavBar from "@/widgets/SideNavBar";

export const metadata = {
  title: "프로그래머스",
};

export default function PLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="flex w-full h-[calc(100vh-64px)]">
      <nav>
        <SideNavBar />
      </nav>
      <main>{children}</main>
    </div>
  );
}

 

레이아웃의 리턴에서 html과 body를 지우고, 필요한 HTML 태그만 리턴해 주었다.

 

만족스러운 결과가 나왔다.

 

참고로 루트 경로에서는 레이아웃에 사이드바가 없기 때문에 당연히 보이지 않는다.

레이아웃은 상위 라우터 -> 하위 라우터로는 전달되지만 그 반대는 불가능하기 때문에, 적절히 필요한 컴포넌트들을 잘 조합해서 쓰면 되겠다.


오늘은 루트 레이아웃과 로컬 레이아웃, 그리고 이를 적용하는 방법에 대해 알아 보았다.

글을 쓰면서 시행착오도 많이 겪고, 내가 잘못 안 부분도 있어서 많이 고치기도 했지만....

Next.js를 처음 배울 때 가장 헷갈렸던 layout을 정리 할 수 있는 계기가 되었던 것 같다.