본문 바로가기

Next.js

Next.js 8. Layout

  React 모델을 사용하면 페이지를 일련의 컴포넌트로 분해할 수 있다. 이러한 컴포넌트 중 다수는 종종 페이지간에 재사용된다. 예를 들어 모든 페이지에 동일한 탐색 모음과 바닥글이 있을 수 있다.

// components/layout.js

import Navbar from './navbar'
import Footer from './footer'

export default function Layout({ children }) {
  return (
    <>
      <Navbar />
      <main>{children}</main>
      <Footer />
    </>
  )
}

 

 

  • 커스텀 앱의 단일 공유 레이아웃

  전체 앱에 대해 하나의 레이아웃만 있는 경우 사용자 지정 앱을 생성하고 해당 레이아웃으로 앱을 래핑 할 수 있다. 페이지를 변경할 때 <Layout /> 컴포넌트가 재사용되기 때문에 해당 컴포넌트의 상태(예: 입력 값)가 보존된다.

// pages/_app.js

import Layout from '../components/layout'

export default function MyApp({ Component, pageProps }) {
  return (
    <Layout>
      <Component {...pageProps} />
    </Layout>
  )
}

 

 

  • 페이지 별 레이아웃

  여러 레이아웃이 필요한 경우 페이지에 getLayout 속성을 추가하여 레이아웃에 대한 React 컴포넌트를 반환할 수 있다. 이렇게 하면 페이지별로 레이아웃을 정의할 수 있다. 함수를 반환하기 때문에 원하는 경우 복잡한 중첩 레이아웃을 가질 수도 있다.

// pages/index.js

import Layout from '../components/layout'
import NestedLayout from '../components/nested-layout'

export default function Page() {
  return (
    // ...content
  )
}

Page.getLayout = function getLayout(page) {
  return (
    <Layout>
      <NestedLayout>{page}</NestedLayout>
    </Layout>
  )
}
// pages/_app.js

export default function MyApp({ Component, pageProps }) {
  // 가능한 경우 페이지 레벨에서 정의된 레이아웃을 사용한다.
  const getLayout = component.getLayout || ((page) => page)
  
  return getLayout(<Component {...pageProps} />)
}

  페이지 사이를 탐색할 때 SPA(Single-Page Application) 경험을 위해 페이지 상태(입력 값, 스크롤 위치 등)를 유지하려고 한다. 이 레이아웃 패턴은 React 컴포넌트 트리가 페이지 전환 간에 유지되기 때문에 상태 지속성을 가능하게 한다. 컴포넌트 트리를 통해 React는 상태를 유지하기 위해 어떤 요소가 변경되었는지 이해할 수 있다.

 

 

  • TypeScript 사용

  TypeScript를 사용할 때 먼저 getLayout 함수를 포함하는 페이지에 대한 새 type을 만들어야 한다. 그런 다음 이전에 만든 type을 사용하도록 Component 속성을 재정의하여 AppProps를 위한 새 type을 만들어야만 한다.

// pages/index.tsx

import type { ReactElement } from 'react';
import Layout from '../components/layout'
import NestedLayout from '../components/nested-layout'
import type { NextPageWithLayout } from './_app'

const Page: NextPageWithLayout = () => {
  return <p>hello world</p>
}

Page.getLayout = function getLayout(page: ReactElement) {
  return (
    <Layout>
      <NestedLayout>{page}</NestedLayout>
    </Layout>
  )
}

export default Page
// pages/_app.tsx

import type { ReactElement, ReactNode } from 'react'
import type { NextPage } from 'next'
import type { AppProps } from 'next/app'

export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
  getLayout?: (page: ReactElement) => ReactNode
}

type AppPropsWithLayout = AppProps & {
  Component: NextPageWithLayout
}

export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
  const getLayout = Component.getLayout ?? ((page) => page)
  
  return getLayout(<component {...pageProps} />)
}

 

 

  • 데이터 가져오기

  레이아웃 내에서 useEffect 또는 SWR과 같은 라이브러리를 사용하여 클라이언트 측에서 데이터를 가져올 수 있다. 물론 이 파일은 Page가 아니기 때문에 getStaticProps나 getServerSideProps를 지금까지는 사용할 수 없다.

// components/layout.js

import useSWR from 'swr'
import Navbar from './navbar'
import Footer from './footer'

export default function Layout({ children }) {
  const { data, error } = useSWR(`/api/navigation`, fetcher)
  
  if (error) return <div>Failed to load</div>
  if (!data) return <div>Loading...</div>
  
  return (
    <>
      <Navbar links={data.links} />
      <main>{children}</main>
      <Footer />
    </>
  )
}