
🌱 해당 포스트는 한 입 크기로 잘라먹는 Next.js(v15)을 수강하고, Next.js 공식 문서 - page router를 참고하여 정리한 글입니다.
next.js 앱의 모든 페이지에 다 일괄적으로 적용이 되는 글로벌 레이아웃을 설정할 수 있다. _app 컴포넌트에 공통적인 레이아웃을 만들어주면 된다.
하지만, 공통적인 레이아웃 로직이 많아지면, 코드 가독성이 떨어질 수 있으므로, 레이아웃 파트만 별도로 분리하여 컴포넌트를 만드는 것이 좋다.
src > components > global-layout.tsx라는 파일을 만들고, 이 파일 안에 글로벌 레이아웃을 만든다.
src/
├── components/
│ └── global-layout.tsx ← 글로벌 레이아웃 컴포넌트
├── pages/
│ └── _app.tsx ← 앱 전체에 글로벌 레이아웃 적용
// src/components/global-layout.tsx
import Link from "next/link";
import { ReactNode } from "react";
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}>제작 @grit03</footer>
</div>
);
}
// pages/_app.tsx
import GlobalLayout from '@/components/global-layout'
import type { AppProps } from 'next/app'
import '@/styles/globals.css' // 전역 스타일
export default function App({ Component, pageProps }: AppProps) {
return (
<GlobalLayout>
<Component {...pageProps} />
</GlobalLayout>
)
}
모든 페이지에 공통적으로 적용되는 레이아웃이 아닌, 특정 페이지에만 적용되는 레이아웃이 필요한 경우가 있다. 특정 페이지에만 적용할 수 있는 레이아웃은 아래와 같이 생성할 수 있다.
컴포넌트에 getLayout 함수를 추가해서, 페이지 별 레이아웃을 구현할 수 있다.
// src/components/searchable-layout.tsx
import { useRouter } from "next/router";
import { ReactNode, useEffect, useState } from "react";
import style from "@/components/searchable-layout.module.css";
export default function SearchableLayout({
children,
}: {
children: ReactNode;
}) {
/* 코드 생략... */
return (
<div>
<div className={style.searchbar_container}>
<input
value={search}
onChange={onSearchChange}
placeholder="검색어를 입력하세요..."
/>
<button onClick={onSubmit}>검색</button>
</div>
{children}
</div>
);
}
// src/pages/index.tsx
import SearchableLayout from "@/components/searchable-layout";
import { ReactElement, ReactNode } from "react";
export default function Home() {
return (
<div>
<h1>메인 화면</h>
</div>
);
}
Home.getLayout = (page: ReactElement) => {
return <SearchableLayout>{page}</SearchableLayout>;
};
❓ 어떻게 컴포넌트 함수에 속성을 추가할 수 있는 걸까?
자바스크립트의 모든 함수들은 사실 다 객체이다. 그렇기 때문에 함수에도 속성으로 또 다른 함수를 추가할 수 있다.
🔗 참고자료 : 객체 자료형 자세히 살펴보기
// src/pages/_app.tsx
import "@/styles/globals.css";
import GlobalLayout from "@/components/global-layout";
import type { AppProps } from "next/app";
import { ReactElement, ReactNode } from "react";
import { NextPage } from "next";
// 기본적인 컴포넌트에는 getLayout함수가 없으므로, 타입에 추가해야한다.
type NextPageWithLayout = NextPage & {
getLayout?: (page: ReactElement) => ReactNode;
};
// 컴포넌트를 getLayout이 옵셔널로 있는 타입으로 바꾼다.
type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout;
};
export default function App({ Component, pageProps }: AppPropsWithLayout) {
// 컴포넌트에 getLayout 함수가 있다면 사용하고, 없으면 그대로 page를 리턴
const getLayout = Component.getLayout ?? ((page: ReactElement) => page);
return <GlobalLayout>{getLayout(<Component {...pageProps} />)}</GlobalLayout>;
}
❓
ReactElement와ReactNode의 차이
ReactNode는 리액트에서 렌더링 가능한 모든 값 (React.createElement의 리턴 값 뿐만 아니라, string, number, null 등)을 말하며,ReactElement보다 포괄적인 의미이다.
❓ 타입 단언(type assertion)이란?
타입 단언은 개발자가 해당 타입에 대해 확신이 있을 때 사용하는 타입 지정 방식으로, 타입스크립트의 타입 추론에 기대하지 않고 개발자가 직접 타입을 명시하여 해당 타입으로 강제하는 것을 의한다.
페이지 간 이동 시, 입력 값이나 스크롤 위치 등 페이지 상태를 유지하고 싶다면, Single-Page Application(SPA) 경험을 제공하는 것이 좋다.
Layout 패턴을 사용하면, 공통된 부분은 React 컴포넌트 트리가 동일하기 때문에, Layout에 있는 값들이 페이지 전환 중에도 유지된다. 왜냐하면, React는 컴포넌트 트리가 유지되면, 상태(state) 값들을 보존해주기 때문이다.