[Next.js] 공통 layout 적용하기

하영·2022년 9월 22일
22

Next.js

목록 보기
2/2
post-thumbnail

22.07.09 노션에 작성한 문서를 옮긴 것입니다. (원본 삭제됨)

0. 들어가며

웹서비스나 앱서비스에는 대부분의 화면에 공통으로 헤더를 사용합니다. 이를 모든 페이지에서 일일이 생성해주기에는 정말 까다롭죠. 그래서 개발할 땐 공통 레이아웃을 주로 사용합니다.

제가 공통 레이아웃을 구성하는 방식은 불안정 했었습니다.

React.js 에서 사용한 방법은 가장 최상단 파일에서 HTML5 시멘틱 구조로 헤더, 바디, 푸터를 구성하고 바디에 Route를 구성하였습니다. React.js 에서 사용한 방법이 맞는 건지는 모르겠네요?

그리고 지금은 Next.js 를 사용하여 프로젝트를 진행 중 입니다. 이전 Next.js 프로젝트에서도 공통 레이아웃을 설정해야 했는데, 그저 생각나는대로 불안한 구조로 레이아웃을 구성하였었습니다.

그렇기에, 이번 프로젝트에서는 조금 더 개선된 방법으로 개발하고자 공식문서
를 참고하여 레이아웃을 구성하였습니다.

이번 프로젝트에서는 모바일 위주의 웹앱을 제작할 것이기 때문에, 카카오헤어샵의 레이아웃을 참고하였습니다.

어떻게 했는지 보실까요 ?

1. 이용할 레이아웃 구성하기

1.1 가장 겉 레이아웃 (AppLayout) 제작

PC화면에서도 모바일 환경과 동일하게 구성해주어야 하기에 아래와 같은 구성을 따랐습니다.
(사용하고자 하는 레이아웃을 자유롭게 구성하시면 됩니다)

  • 화면 가로 폭을 최대 500px로 고정한다.
  • 브라우저의 가로 폭이 500px 미만으로 내려가면 width를 100%로 지정한다.

위의 조건을 만족할 수 있게 AppLayout.tsx 파일을 만들어 작성해줍시다!


저는 components/layout 폴더에서 만든 layout들을 관리하려 합니다!

components/layout/AppLayout.tsx 작성

// 렌더링할 화면의 가로폭을 고정하고, 가운데로 위치시키는 레이아웃.

import styled from 'styled-components';

const AppLayout = (props: { children: React.ReactNode }) => {
  return (
    <Centering>
      <FixedWidth>{props.children}</FixedWidth>
    </Centering>
  );
};

const Centering = styled.div`
  display: flex;
  justify-content: center;
`;
const FixedWidth = styled.div`
  width: 500px;
  @media (max-width: 500px) { /* 화면 너비가 500px 이하가 되면 요소 너비를 100%로 고정*/
    width: 100%;
  }
`;
export default AppLayout;

props.childrenAppLayout으로 감싸지는 요소들을 지칭합니다.

pages/_app.tsx 작성 (이후 변경될 코드)

import '../styles/globals.css'
import type { AppProps } from 'next/app'
import {AppLayout} from "components/layout"

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <AppLayout>
      <Component {...pageProps} />
    </AppLayout>
  );
}

export default MyApp

_app.tsxComponent를 감싸주게되면

모바일처럼 화면 영역이 축소됩니다! 이제 이 상태에서 화면에 맞는 맞춤 레이아웃을 또 제작하여 부착하면 됩니다.

저는 이전 Next.js 프로젝트에서 이런 방식으로 공통 레이아웃을 적용하곤 했습니다.
하지만 페이지 마다 다른 레이아웃을 적용하는 방법도 몰랐으며, 공식문서와는 전혀 다른 방법이었습니다.

2. 중첩 레이아웃 (여러 레이아웃) 사용하기

이제 저 모바일 화면 안에서 레이아웃을 사로 구성하려고 합니다. 지금 구상중인 서비스에는 헤더가 필요합니다.

헤더 컴포넌트에는 서비스 로고와 현재 로그인한 사용자의 닉네임을 표시하고자 했습니다.
(현재는 서비스 이름과 프로필사진으로 변경되었습니다)

일단은 레이아웃을 신경쓰지 않고, 위에서 제작한 기본 레이아웃에 헤더를 부착하여 헤더를 볼 수 있도록 하였습니다.

화면 가로로 가득히 차버린 헤더 ...

이 헤더를 모바일 화면에 맞게 축소 되게 해야 하는데, 이것은 구조로 보았을 때 야매 방법이라 생각이 들었습니다. 그래서 다른 괜찮은 방법을 찾아보다가 공식문서를 찾았습니다.

우선, 헤더만 포함된 HeaderLayout 레이아웃을 제작하였습니다.

components/layout/HeaderLayout.tsx

// 헤더를 포함하는 레이아웃.

import { Header } from 'components/common';
const AppLayout = (props: { children: React.ReactNode }) => {
  return (
    <>
      <Header />
      {props.children}
    </>
  );
};

export default AppLayout;

헤더가 포함된 레이아웃은 모든 페이지에 일괄로 적용되는 페이지는 아닙니다. 헤더가 필요한 화면에만 사용되는 레이아웃이죠.

그렇기에, 너비가 고정된 레이아웃(AppLayout) 내부에 헤더 레이아웃(HeaderLayout)을 중첩시키는 방식을 사용해야 합니다. 아래 사진과 같은 구조로.. 말이죠!

이럴 때 사용하는 방식이 per page layout 입니다. 페이지 당 레이아웃을 적용시키는 방식... 말 그대로 겠죠 !! 공식 문서에 따르면, 페이지 당 레이아웃이 필요할 땐 getLayout 을 적용한다고 합니다. 자세히 볼까요

이 단계에서, 레이아웃 컴포넌트를 import 해오는 곳은 _app.tsx 가 아닌 레이아웃이 필요한 각 페이지 컴포넌트에서 import 해오기로 구조를 변경하겠습니다!

typescript에서는 페이지당 레이아웃을 지정해주는 getLayout 함수를 미리 선언해주어야 합니다.

pages/_app.tsx를 아래와 같이 수정

import '../styles/globals.css';
import type { ReactElement, ReactNode } from 'react';
import type { AppProps } from 'next/app';
import type { NextPage } from 'next';

export type NextPageWithLayout = NextPage & {
  getLayout?: (page: ReactElement) => ReactNode;
};
  /*
    NextPageWithLayout으로 Page의 타입을 지정하면,
    getLayout 속성함수를 사용할 수 있게된다. (사용해도 되고 안해도 되고 )
  */
type AppPropsWithLayout = AppProps & {
  Component: NextPageWithLayout;
}; // 기존 AppProps타입에 Layout을 추가한 것.

export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
  // 페이지 단위에서 정의한 레이아웃이 있다면 해당 레이아웃을 적용한다.
  const getLayout = Component.getLayout ?? ((page) => page);

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

코드 설명은 주석을 참고해주세요!

pages/index.tsx 레이아웃을 사용하고자 하는 페이지 컴포넌트

import Head from 'next/head';
import styles from '../styles/Home.module.css';
import type { ReactElement } from 'react';
import { AppLayout,HeaderLayout } from 'components/layout';
import type {NextPageWithLayout} from "./_app"
const Home: NextPageWithLayout = () => {
  return (
    <div className={styles.container}>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <h1 className={styles.title}>
          안녕하세요 <a href="https://nextjs.org">Next.js!</a>
        </h1>
        <p className={styles.description}>
          중첩 레이아웃 적용 완료!
        </p>
      </main>
    </div>
  );
};
// (페이지 컴포넌트 이름).getLayout 으로 호출.
Home.getLayout = function getLayout(page: ReactElement) {
  return (
    <AppLayout> {/* 이 단계에서 레이아웃 중첩 가능 */}
      <HeaderLayout>{page}</HeaderLayout> {/* 중첩할 레이아웃 nested layout*/}
    </AppLayout>
  );
};

export default Home;

이렇게 코드를 작성해주면 페이지 마다 원하는 레이아웃을 설정할 수 있으며, 중첩 레이아웃 설정도 가능해집니다~

중첩 레이아웃 적용 완료된 모습 입니다 !! 고정된 너비 안에 헤더가 쏙 들어가있죠?

여러 레이아웃을 사용하고, 중첩 레이아웃을 사용 할 경우가 생긴다면 이렇게 구성해 보세요! 코드 관리가 한결 더 쉬워 진답니다 ㅎㅎ

3. 마치며

위에서 잠깐 언급했지만, 예전에 잠시 진행했던 Next.js 프로젝트에서는 Layout의 사용법을 잘 모른 상태에서 필요한 대로 레이아웃을 만든 후 각 페이지에 최상단 태그로 감싸서 사용했었습니다.

예를들어, 기존에 쓰던 레이아웃에 사이드바 컴포넌트가 추가된 레이아웃이 필요하다면,
기존에 쓰던 레이아웃 코드사이드바 컴포넌트 코드를 작성한 레이아웃 파일을 추가로 만들고 사용하는 방법이었죠. 코드 중복이 많이 일어나도 관리 하기 힘든 코드였습니다.

위의 과정을 거치며,

분명히 더 나은 방법이 있을텐데...

하였지만 따로 찾아보지 않고 프로젝트를 하차했습니다. 이번에 새로운 프로젝트를 진행하면서 검색을 통해 공식문서 방법을 찾게 되었고, 이번 구조 공부를 통해 항상 개선할 방법을 찾는 것이 성장의 중요한 키 인 것을 배웠습니다!

공부하며 작성한 글이기 때문에 부족한 부분이나 궁금한 점이 있으시다면 댓글 자유롭게 남겨주세요 😊 감사합니다!

profile
maker를 넘어 solver를 지향합니다.

0개의 댓글