Next.js에서는 App 컴포넌트
를 사용하여 모든 페이지에 글로벌 레이아웃을 적용할 수 있다. 하지만 페이지에 따라 특정 요소(ex: 검색 바)만 표시하고 싶다면, 해당 요소가 필요한 페이지에만 레이아웃을 별도로 적용하는 것이 좋다. 이번 포스트에서는 페이지별로 레아웃을 분리하고 적용하는 방법과 타입 설정까지 살펴보도록 하자.
검색 바와 같이 일부 페이지에만 적용할 요소가 필요할 때는 해당 페이지 전용 레이아웃 컴포넌트를 만들어 사용할 수 있다.
예시를 들기 위해src/components/searchable-layout.tsx
파일을 만들고, 검색 바를 포함한 SearchableLayout
컴포넌트를 정의했다. 여기서는 간단히 “임시 서치바”를 출력하는 형태로 만들어보자.
// src/components/searchable-layout.tsx
import { ReactNode } from "react";
export default function SearchableLayout({ children }: { children: ReactNode }) {
return (
<div>
<div>임시 서치바</div>
{children}
</div>
);
}
SearchableLayout
컴포넌트는 children
을 받아 페이지의 메인 컴포넌트를 레이아웃 내부에 렌더링한다. (children의 타입
은 ReactNode
로 지정하여 모든 리액트 컴포넌트를 받을 수 있도록 하자.)글로벌 레이아웃은 App 컴포넌트에서 설정할 수 있지만, 특정 페이지에만 적용할 레이아웃이 필요할 때는 각 페이지 컴포넌트에서 직접 레이아웃을 설정해야 한다.
특정 페이지 컴포넌트에서 getLayout
메서드를 정의하고 레이아웃을 설정할 수 있다. 아래는 index
페이지에 SearchableLayout
을 적용하는 예시이다.
// src/pages/index.tsx
import SearchableLayout from "@/components/searchable-layout";
export default function Home() {
return (
<>
<h1>인덱스</h1>
<h2>H2</h2>
</>
);
}
// 페이지에 적용할 레이아웃 설정
Home.getLayout = (page) => {
return <SearchableLayout>{page}</SearchableLayout>
};
Home 컴포넌트
의 getLayout
메서드는 page
라는 매개변수로 현재 페이지 컴포넌트를 받아 SearchableLayout
에 감싸서 반환한다. 이렇게 하면 index 페이지에만 SearchableLayout
이 적용된다.
getLayout
메서드는 Next.js에서 지원하는 기본 문법이 아닌, 자바스크립트의 함수 객체 특성을 활용한 것이다. 함수는 객체이므로 getLayout
과 같은 메서드를 추가할 수 있다.
그리고 이때에 page 매개변수의 타입은, react컴포넌트이기 때문에 타입은 ReactNode
로 정의함.
이제 getLayout
메서드를 활용해 App 컴포넌트에서 페이지별로 레이아웃을 적용하는 방법을 알아보자.
// src/pages/_app.tsx
import GlobalLayout from "@/components/global-layout";
import {NextPage} from "next";
import type { AppProps } from "next/app";
import { ReactNode } from "react";
type NextPageWithLayout = NextPage & {
getLayout?: (page: ReactNode) => ReactNode;
};
export default function App({ Component, pageProps }: AppProps & { Component: NextPageWithLayout }) {
const getLayout = Component.getLayout ?? ((page: ReactNode) => page);
return (
<GlobalLayout>
{getLayout(<Component {...pageProps} />)}
</GlobalLayout>
);
}
Component.getLayout
을 통해 현재 페이지 컴포넌트에 설정된 getLayout 메서드를 호출한다. 만약 getLayout
메서드가 없다면 기본적으로 페이지를 그대로 렌더링하도록 설정했다.
??
연산자를 사용해 getLayout
이 undefined
인 경우에는 페이지를 그대로 반환하는 함수인 ((page) => page)
가 적용되도록 한다.
그럼 만약에 현재 접속한 페이지가 /book
페이지여서 getLayout
메서드가 설정되지 않았을 경우에는 이 조건식에 따라서 undefined
이 되기 때문에 getLayout
이라는 변수에 ((page: ReactNode) => page)
이 함수가 저장이 된다.
그리고 이 함수는 그냥 현재의 페이지를 그대로 return만 하는 함수이기 때문에 {getLayout(<Component {...pageProps} />)}
이 상태 그대로 렌더링 해줘도 이 페이지 컴포넌트가 화면에 그대로 렌더링 될 거다.
그렇기 때문에 브라우저에서 /book/123
페이지에서 새로고침을 눌러도 결론적으로 레이아웃도 적용되지 않고 그대로 페이지가 렌더링 되면서 오류도 발생하지 않는다.
Component.getLayout
에 NextComponentType<NextPageContext, any, any> 형식에 getLayout 속성이 없습니다. 라는 메세지로 타입에러가 발생하는데, NextComponentType
에 getLayout
속성이 없다 라는 뜻이다. 이 에러 메세지가 뜨는 이유는, getLayout
이 적용되지 않은 페이지에서는 getLayout
메서드가 undefined
될 수 있기 때문이다.
이 getLayout
메서드는 우리가 임의로 만든 것이기 때문에 기본적인 타입 정보에는 포함이 되어있지 않아서
getLayout
메서드를 호출하고 있는데 이건 모르는 메서드야...라고 말하고 있는 거지.
그래서 이 Component타입에 getLayout메서드가 존재할 거야 하고 타입 정보를 추가해 주면 되는데,
여기서는 NextPageWithLayout
이라는 타입을 새로 정의해 적용해보았다.
// src/pages/_app.tsx
type NextPageWithLayout = NextPage & {
getLayout?: (page: ReactNode) => ReactNode;
};
NextPage
라는 원래 Next에서 제공하는 페이지 컴포넌트의 기본 타입으로 설정을해 준 다음에 교집합으로 getLayout이라는 프로퍼티를 추가해준다.
그래서 getLayout
메서드의 타입 정보로 page라는 매개변수를 받고 타입으로는 ReactNode로 정의, 반환값으로도 똑같이 page를 return하고 타입도 똑같이 ReactNode로 정의 해주면 된다.
이렇게 만든 타입은 기존에 next에서 제공하는 NextPage
타입에다가 getLayout
이라는 (page:ReactNode) => ReactNode;
이러한 타입의 메서드가 추가된 타입으로 정의가 된 것이다.
그래서 이 타입을 App컴포넌트가 받는 props의 타입에 교집합으로 Component의 타입으로 정의를 해주면 된다. 그럼 이 App컴포넌트가 전달받는 props의 타입을 확장해 주게 된다.
그럼 App컴포넌트가 전달받는 Component
타입은 NextPageWithLayout
이라는 타입으로써 getLayout
을 포함하고 있는 NextPage가 되는 것이다.
그런데 참고로 이 getLayout
이라는 메서드가 없을 수도 있기 때문에 페이지에 ?
사용해서 optional한 타입으로 만들어주도록 한다.
이로써 App 컴포넌트가 getLayout 메서드를 포함한 페이지 컴포넌트를 안전하게 처리할 수 있게 됐다.
Next.js에서 페이지별로 다른 레이아웃을 적용하려면, 각 페이지 컴포넌트에 getLayout
메서드를 추가하여 원하는 레이아웃으로 감싸면 된다.
App 컴포넌트에서는 이 getLayout
메서드를 통해 각 페이지에 개별 레이아웃을 적용할 수 있다.
이렇게 하면 Next.js에서 페이지별로 다른 레이아웃을 설정하고 관리할 수 있어, 더 유연하고 깔끔한 레이아웃 구성이 가능하다.
사용자가 특정 페이지로 이동 요청을 보내면, App 컴포넌트가 실행되고 Component
props로 해당 페이지 컴포넌트가 전달된다.
App 컴포넌트는 페이지에 getLayout
메서드가 정의되어 있는지 확인하고, 있으면 getLayout
을 사용하여 레이아웃을 적용한 컴포넌트를 반환하고, 없으면 기본 반환 함수를 사용하여 페이지를 그대로 렌더링한다.