Next.js에서 getLayout을 활용한 글로벌 및 개별 페이지 레이아웃 적용

2025. 2. 24. 14:36프론트엔드/Next JS

728x90

🔹 1. 기존 Next.js의 글로벌 레이아웃 적용 방식

Next.js에서는 _app.tsx 파일을 수정하여 전역(Global) 레이아웃을 적용할 수 있습니다.

📌 components/global-layout.tsx

import { ReactNode } from "react";
import Link from "next/link";
import style from "./global-layout.module.css";

export default function GlobalLayout({
  children,
}: {
  children: ReactNode;
}) {
  return (
    <div className={style.container}>
      <header className={style.header}>
        <Link href={"/"}>📚 ONEBITE BOOKS</Link>
      </header>
      <main className={style.main}>{children}</main>
      <footer className={style.footer}>제작 @winterlood</footer>
    </div>
  );
}

위의 GlobalLayout을 _app.tsx에서 적용하면 모든 페이지에서 일관된 레이아웃을 유지할 수 있습니다.


🔹 2. 특정 페이지에서 추가적인 레이아웃 적용 (getLayout 활용)

일반적으로, Next.js에서 글로벌 레이아웃을 적용하면 모든 페이지가 동일한 구조를 따르게 됩니다.
그러나 일부 페이지에서만 특정한 UI(예: 검색 바가 포함된 레이아웃)를 추가하고 싶을 수도 있습니다.
이럴 때는 getLayout 패턴을 활용하여 개별 페이지에 특정 레이아웃을 적용할 수 있습니다.

📌 getLayout을 사용하면

  • 페이지별로 다른 레이아웃을 설정할 수 있습니다.
  • 기본(Global) 레이아웃을 유지하면서 추가적인 요소(Search bar 등)를 포함할 수 있습니다.

 


🔹 1. getLayout이 필요한 이유

보통 Next.js 프로젝트에서는 _app.tsx에서 GlobalLayout을 적용하여 모든 페이지에서 동일한 레이아웃을 유지합니다.

return (
  <GlobalLayout>
    <Component {...pageProps} />
  </GlobalLayout>
);

위처럼 하면 모든 페이지에서 GlobalLayout이 적용되지만, 특정 페이지에서만 다른 레이아웃(예: 검색 기능이 있는 SearchableLayout)을 추가해야 한다면?
👉 기존 방식으로는 개별 페이지마다 조건문을 추가해야 하므로 유지보수가 어렵습니다.

따라서, getLayout을 활용하면 각 페이지별로 필요한 레이아웃을 적용할 수 있습니다.


🔹 2. getLayout을 적용한 페이지 예제 (pages/index.tsx)

예제에서는 홈 페이지(index.tsx)에만 SearchableLayout을 추가하고,
그 외의 페이지는 기본 GlobalLayout만 적용되도록 설정합니다.

📌 pages/index.tsx

import SearchableLayout from "@/components/searchable-layout";
import style from "./index.module.css";
import { ReactNode } from "react";

export default function Home() {
  return (
    <>
      <h1 className={style.h1}>인덱스</h1>
      <h2 className={style.h2}>H2</h2>
    </>
  );
}

// 특정 페이지에서만 적용될 레이아웃 정의
Home.getLayout = (page: ReactNode) => {
  return <SearchableLayout>{page}</SearchableLayout>;
};

✅ 코드 설명

  1. 기본 페이지(Home) 정의
    • <h1>과 <h2> 요소를 포함한 간단한 UI 구조입니다.
  2. 개별 레이아웃 적용 (getLayout 설정)
    • Home.getLayout = (page: ReactNode) => <SearchableLayout>{page}</SearchableLayout>;
    • getLayout을 사용하면 현재 페이지(Home)에서만 SearchableLayout을 적용할 수 있습니다.
    • SearchableLayout은 검색 창이 포함된 레이아웃이며, index.tsx에서만 사용됩니다.
  3. getLayout이 없는 페이지는 기본(GlobalLayout)만 적용
    • 예를 들어, about.tsx 같은 다른 페이지에는 getLayout이 설정되지 않았으므로 기본 GlobalLayout만 유지됩니다.

🔹 3. _app.tsx에서 getLayout을 적용하는 방식

이제 _app.tsx에서 getLayout을 활용하여 각 페이지에서 레이아웃을 개별적으로 적용할 수 있도록 수정해야 합니다.

📌 pages/_app.tsx

import GlobalLayout from "@/components/global-layout";
import "@/styles/globals.css";
import { NextPage } from "next";
import type { AppProps } from "next/app";
import { ReactNode } from "react";

// NextPageWithLayout 타입 정의
type NextPageWithLayout = NextPage & {
  getLayout?: (page: ReactNode) => ReactNode;
};

// App 컴포넌트 정의
export default function App({
  Component,
  pageProps,
}: AppProps & {
  Component: NextPageWithLayout;
}) {
  // getLayout 함수가 정의되지 않은 경우 기본적으로 페이지를 그대로 반환
  const getLayout =
    Component.getLayout ?? ((page: ReactNode) => page);

  return (
    <GlobalLayout>
      {getLayout(<Component {...pageProps} />)}
    </GlobalLayout>
  );
}

🔹 4. _app.tsx 코드 상세 설명

📌 1) NextPageWithLayout 타입 정의

type NextPageWithLayout = NextPage & {
  getLayout?: (page: ReactNode) => ReactNode;
};
  • NextPage 타입을 확장하여 getLayout 속성을 추가합니다.
  • getLayout은 페이지별로 특정한 레이아웃을 적용할 수 있도록 하는 함수입니다.
  • 이 설정을 통해 각 페이지(Component)에서 선택적으로 레이아웃을 적용할 수 있도록 합니다.

📌 2) getLayout을 적용하는 방식

const getLayout =
  Component.getLayout ?? ((page: ReactNode) => page);
  • getLayout이 정의된 페이지라면 Component.getLayout을 사용하고,
  • 정의되지 않은 경우 기본적으로 페이지를 그대로 반환합니다.

즉, getLayout이 정의된 페이지에서는 getLayout을 사용하여 특정 레이아웃을 적용하고,
getLayout이 없는 페이지에서는 일반적인 레이아웃(GlobalLayout)만 적용됩니다.


📌 3) GlobalLayout 내부에서 getLayout 적용

return (
  <GlobalLayout>
    {getLayout(<Component {...pageProps} />)}
  </GlobalLayout>
);
  • 모든 페이지는 GlobalLayout 내부에서 렌더링됩니다.
  • 하지만 getLayout이 정의된 페이지라면 추가적인 레이아웃(SearchableLayout 등)이 적용됩니다.

🔹 5. getLayout 적용 흐름 정리

페이지 파일 적용된 레이아웃

index.tsx GlobalLayout > SearchableLayout > index.tsx
about.tsx GlobalLayout > about.tsx
contact.tsx GlobalLayout > contact.tsx
  • index.tsx에서는 getLayout을 사용하여 기본 GlobalLayout 위에 SearchableLayout을 추가.
  • about.tsx 같은 페이지는 getLayout을 정의하지 않았으므로 기본 GlobalLayout만 적용.

🔹 6. getLayout을 적용하면 얻을 수 있는 장점

✅ 1) 특정 페이지에서만 추가적인 레이아웃 적용 가능

  • Home 페이지에는 SearchableLayout을 적용하지만, About 페이지에는 적용되지 않도록 설정할 수 있습니다.

✅ 2) 유지보수 및 확장성 증가

  • 기존 방식(조건문을 사용하여 특정 페이지에서만 다른 레이아웃 적용)보다 더 깔끔하고 확장성이 높음.
  • 페이지별로 다른 레이아웃이 필요할 때 새로운 레이아웃을 쉽게 추가 가능.

✅ 3) 전역 레이아웃과 페이지별 레이아웃을 조합하여 사용 가능

  • GlobalLayout을 유지하면서 특정 페이지에만 추가적인 UI(SearchableLayout 등)를 적용할 수 있음.

🔹 7. 마무리

📌 요약

  1. getLayout을 사용하여 특정 페이지에서만 추가적인 레이아웃을 적용.
  2. _app.tsx에서 Component.getLayout이 있는지 확인하고, 페이지별 레이아웃을 개별적으로 적용.
  3. 기본 GlobalLayout은 유지하면서, 필요에 따라 개별적인 레이아웃(SearchableLayout)을 추가.
  4. 유지보수 및 확장성이 뛰어난 모듈화된 레이아웃 시스템을 구축 가능.

이제 프로젝트에서 유연한 레이아웃 시스템을 적용하여 더욱 체계적인 개발이 가능합니다! 🚀


🔹 5. 검색 기능이 포함된 SearchableLayout 구현

📌 components/searchable-layout.tsx

import { useRouter } from "next/router";
import { ReactNode, useEffect, useState } from "react";
import style from "./searchable-layout.module.css";

export default function SearchableLayout({
  children,
}: {
  children: ReactNode;
}) {
  const router = useRouter();
  const [search, setSearch] = useState("");

  const q = router.query.q as string;

  useEffect(() => {
    setSearch(q || "");
  }, [q]);

  const onChangeSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
    setSearch(e.target.value);
  };

  const onSubmit = () => {
    if (!search || q === search) return; // 페이지 이동 최소화
    router.push(`/search?q=${search}`);
  };

  const onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
    if (e.key === "Enter") {
      onSubmit();
    }
  };

  return (
    <div>
      <div className={style.searchbar_container}>
        <input
          value={search}
          onKeyDown={onKeyDown}
          onChange={onChangeSearch}
          placeholder="검색어를 입력하세요 ..."
        />
        <button onClick={onSubmit}>검색</button>
      </div>
      {children}
    </div>
  );
}

✅ 코드 설명

  • useRouter를 사용하여 현재 URL의 쿼리 파라미터(q)를 가져옴
  • useEffect를 이용하여 검색어 입력창이 URL의 q 값과 동기화되도록 설정
  • onChangeSearch: 검색어 입력 시 search 상태 업데이트
  • onSubmit: 검색어 입력 후 버튼 클릭 시 /search?q=검색어로 이동
  • onKeyDown: Enter 키를 누르면 검색 실행

🔹 6. SearchableLayout을 적용한 결과

이제 Home 페이지(/)에서는 검색 바가 포함된 레이아웃이 적용되지만,
다른 페이지에서는 GlobalLayout만 유지됩니다.

✅ Home.getLayout을 적용한 결과:

--------------------------------------
| 📚 ONEBITE BOOKS (헤더)           |
--------------------------------------
| 🔍 검색 바                         |
--------------------------------------
| 인덱스 (메인 콘텐츠)               |
--------------------------------------
| 제작 @winterlood (푸터)           |
--------------------------------------

✅ 다른 페이지 (/about 등):

--------------------------------------
| 📚 ONEBITE BOOKS (헤더)           |
--------------------------------------
| About 페이지 (메인 콘텐츠)        |
--------------------------------------
| 제작 @winterlood (푸터)           |
--------------------------------------

🔹 7. 정리

기능 적용 방식

Global Layout _app.tsx에서 GlobalLayout을 적용하여 모든 페이지에서 유지
개별 페이지 Layout getLayout을 활용하여 특정 페이지에서만 추가적인 레이아웃 적용
검색 바 포함 Layout SearchableLayout을 만들어 Home 페이지에서만 적용

 

728x90