[Next.js] RootLayout에서 레이아웃을 동적 렌더링하기

D uuu·2024년 9월 19일
0

Next.js

목록 보기
4/5

문제 정의

Next.js의 App Router 을 사용하면 경로마다 폴더를 생성하여 라우트를 정의하고, 각 폴더 내에 layout 파일을 생성함으로써 경로에 맞는 공통 레이아웃을 설정할 수 있습니다. 그리고 RootLayout 은 모든 경로에 적용되는 공통 레이아웃을 설정할 수 있습니다.

📝app
ㄴlogin
  ㄴpage.tsx
  ㄴlayout.tsx  -- login 페이지의 적용되는 레이아웃
ㄴtodo
  ㄴpage.tsx
  ㄴlayout.tsx  -- todo 페이지의 적용되는 레이아웃
ㄴlayout.tsx   -- RootLayout 으로 모든 경로에 적용되는 공통 레이아웃
ㄴpage.tsx

대부분의 웹 애플리케이션은 하단에 고정된 footer 를 포함하고 있습니다. 하지만 모든 페이지에서 footer가 필요한 건 아닙니다.
어떤 경우에는 footer 가 없는 레이아웃이 더 적합할 수 있습니다. (예를들어 로그인 페이지)

이처럼 일부 페이지에서만 footer 가 필요하다면 어떻게 해야 할까요? footer 가 필요한 경로별로 layout 파일에 footer 를 넣어주는 방법도 있겠지만, 어떻게 해야 반복적인 작업을 하지 않고 효율적으로 각 페이지에 맞는 레이아웃을 적용할 수 있을까 하는 고민이 들었습니다.

방법 1: 폴더별 레이아웃 적용

첫 번째로 고려할 수 있는 방법은 각 폴더에 별도의 레이아웃 파일을 적용하는 것입니다. 예를 들어, /home, /meals 등의 페이지는 footer를 포함한 레이아웃을 사용하고, /login 페이지는 footer 없이 별도의 레이아웃을 적용할 수 있습니다.

이 방법은 간단하지만, 유지보수가 어렵고 중복 코드가 발생할 가능성이 큽니다. 예를 들어, footer 가 필요한 경로가 5개에서 10개로 늘어날 때마다 각 경로에 중복된 레이아웃 코드를 작성해야 하는 번거로움이 있습니다.

방법 2: 동적 레이아웃 렌더링

보다 효율적인 방법으로, 경로에 따라 동적으로 레이아웃을 렌더링하는 방법을 사용할 수 있습니다. 이를 통해 각 경로마다 동일한 레이아웃 로직을 반복하지 않고도 적절한 레이아웃을 동적으로 적용할 수 있습니다.

이를 위해 Next.js에서 제공하는 usePathname 훅을 활용하여 현재 경로에 따라 레이아웃을 선택할 수 있습니다.
그럼 아래에서 경로에 맞는 레이아웃을 선택하는 논리를 구현하고, 경로별로 레이아웃을 지정하도록 구현해보겠습니다!

레이아웃 컴포넌트 정의

먼저, 사용할 레이아웃 컴포넌트를 상수로 정의하고, 각 레이아웃에 적용될 경로를 설정합니다.
일단 footer 의 유무에 따라 primaryLayout(기본 레이아웃) 과 footerLayout 으로 나눴습니다.

import PrimaryLayout from './PrimaryLayout';
import FooterLayout from './FooterLayout';

const layouts = {
    primaryLayout: PrimaryLayout,
    footerLayout: FooterLayout,
} as const;

const layoutConfig = {
    primaryLayout: ['/login', '/goals'],
    footerLayout: ['/home', '/meals', '/exercise', '/my', '/category'],
} as const;

현재 경로에 맞는 레이아웃 키를 반환하는 getLayoutKey 함수를 작성합니다. 이 함수는 usePathname 으로 받아온 pathname 을 매개변수로 받아서 현재 경로가 layoutConfig 의 어느 경로에 해당하는지 확인해 해당 Key 를 리턴해주는 함수입니다.


const getLayoutKey = (pathname: string): LayoutKeysType => {
    const configEntries = Object.entries(layoutConfig) as [LayoutKeysType, string[]][];

    for (const [layoutKey, paths] of configEntries) {
        if (paths.some((path) => pathname.startsWith(path))) {
            return layoutKey;
        }
    }

    return 'primaryLayout';
};

이제 ConditionalLayout 컴포넌트를 만들어 현재 경로에 맞는 레이아웃을 동적으로 렌더링합니다.
해당 레이아웃을 children 으로 감싸주면 클라이언트에서 현재 경로를 받아와 해당 레이아웃을 렌더링 해줍니다.

import { ReactNode } from 'react';
import { usePathname } from 'next/navigation';

const ConditionalLayout = ({ children }: { children: ReactNode }) => {
    const pathname = usePathname();
    const layoutKey = getLayoutKey(pathname) as LayoutKeysType;
    const LayoutComponent = layouts[layoutKey];

    return <LayoutComponent>{children}</LayoutComponent>;
};

export default ConditionalLayout;

전체 코드

최종적으로, ConditionalLayout을 통해 각 경로에 맞는 레이아웃을 동적으로 적용할 수 있습니다.
이제 이 컴포넌트를 RootLayout 에 적용시켜 주면 끝입니다!

'use client';

import { ReactNode } from 'react';
import PrimaryLayout from './PrimaryLayout';
import FooterLayout from './FooterLayout';
import { usePathname } from 'next/navigation';

const layouts = {
    primaryLayout: PrimaryLayout,
    footerLayout: FooterLayout,
} as const;

const layoutConfig = {
    primaryLayout: ['/login', '/goals'],
    footerLayout: ['/home', '/meals', '/exercise', '/my', '/category'],
} as const;

type LayoutKeysType = keyof typeof layoutConfig;

const getLayoutKey = (pathname: string) => {
    const configEntries = Object.entries(layoutConfig);

    for (const [layoutKey, paths] of configEntries) {
        if (paths.some((path) => pathname.startsWith(path))) {
            return layoutKey;
        }
    }

    return 'primaryLayout';
};

const ConditionalLayout = ({ children }: { children: ReactNode }) => {
    const pathname = usePathname();

    const layoutKey = getLayoutKey(pathname) as LayoutKeysType;
    const LayoutComponent = layouts[layoutKey];

    return <LayoutComponent>{children}</LayoutComponent>;
};

export default ConditionalLayout;

결론

이 방법을 사용하면 각 경로에 따라 레이아웃을 동적으로 적용할 수 있으며, 중복된 레이아웃 코드를 반복해서 작성할 필요가 없습니다. 하지만 경로가 추가될 때마다 layoutConfig 를 업데이트해야 한다는 번거로움은 있을 수 있습니다. 뭔가 중복 코드는 줄였지만 반대로 다른 부분에서 신경써야 하는게 생겨버렸지만.. 두마리 토끼를 다 잡으면 좋지만 상황에 따라 더 나은 쪽으로 구현하게 되는 것 같습니다. 물론 레이아웃 구조를 더 검토하는 것도 고려해 볼 필요가 있을 것 같긴 합니다🧐

어쨋든 전반적으로 이 접근 방식은 웹 애플리케이션에서 일부 페이지에만 다른 레이아웃이 필요할 때 매우 유용합니다. RootLayout에서 경로 기반으로 동적으로 레이아웃을 렌더링함으로써 유지보수성과 코드의 간결성을 동시에 확보할 수 있다는 점에서 좋은 방법이라고 생각됩니다.

profile
배우고 느낀 걸 기록하는 공간

0개의 댓글

관련 채용 정보