Next.js 에서 HeaderComponent 관리

김동영·2025년 8월 6일
post-thumbnail

1. 문제 인식: 분산된 Layout 관리의 비효율성

처음 프로젝트를 진행할 때는 Next.js App Router를 활용하여
각 페이지 마다 공통된 HeaderComponent를 사용하는 경우 layout.tsx 로 분리하여 관리하려고 했습니다.
하지만 HeaderComponent의 개수도 많고
또한 각각의 페이지마다 헤더의 사용이 달라 layout.tsx 생기는 경우도 많아져서
파일들이 여러 곳에 흩어져 유지보수 하기 어렵고 전체 헤더 구조의 일관성을 파악하기 어려워졌습니다.

이러한 비효율성을 해결하기 위해 모든 헤더 관련 로직을 한곳에서 중앙 관리하는 시스템을 구축하기로 결정했습니다.

2. 초기 아이디어: HeaderController

모든 로직을 한 곳에 모으기 위해 HeaderController를 만들고
if (pathname === '/entry')처럼 경로 문자열을 직접 사용하는 대신 유지보수성을 높이고자 헤더 종류별로 경로 규칙을 하나의 상수 객체로 그룹화하고 if/else 문에서 이 객체를 확인하는 방식을 구상했습니다.

// 초기 해결책 구상 코드

// 1. 헤더 종류별로 경로 규칙을 상수 배열로 정의
export const HEADER_PATHS = {
  NO_HEADER: ["/entry", "/signup", "/landing"],
  BACK_HEADER: ["/login", "/alarm", "/profile"],
  LOGO_HEADER: ["/home", "/friend/list"],
  // ...
};

// 2. if/else 문에서 각 상수 배열을 확인하여 분기 처리
const HeaderController = () => {
  const pathname = usePathname();

  // .includes()를 사용하여 경로가 배열에 직접 포함되는지 확인
  if (HEADER_PATHS.NO_HEADER.includes(pathname)) {
    return null;
  } else if (HEADER_PATHS.BACK_HEADER.includes(pathname)) {
    return <BackHeader ... />;
  } else if (HEADER_PATHS.LOGO_HEADER.includes(pathname)) {
    return <LogoHeader ... />;
  }
  // ...
};

3. 문제 발생: 동적 경로의 우선순위 충돌

이 구조는 가독성을 높였지만
/home은 로고 헤더
/home/[id]는 뒤로가기 헤더를 가져야 한다는 것이었습니다.

그렇다고 해서 /home/[id]/home/ 로 해서 startsWith('/home/') 로 한다고하더라도 /home/[id]/calendar 처럼 뒤에 더있을 수도 있고
또한 다양하게 존재할 수 있었기 때문에 명확하게 보장할 수 있는 방법이 필요했습니다.


4. 해결책 탐색 및 최종 설계: 정적과 동적으로 분리

이 우선순위 충돌 문제를 해결하기 위해 경로 규칙의 성격을 '정적 경로''동적 경로' 두 가지로 나누어 관리하는 방법을 생각했습니다.

  • 정적 규칙: '/home', '/alarm'처럼 정확히 일치하는 경로는 객체로 관리하는 방법으로 구현
  • 동적 규칙: '/home/[id]'처럼 패턴 매칭이 필요한 경로는 배열로 관리하여 정규식을 적용

// 1. 정확히 일치하는 경로들만 모아둔 객체
export const EXACT_PATH_CONFIGS = {
  "/home": { component: "LogoHeader", withAlarm: true },
  "/alarm": { component: "BackHeader" },
  "/entry": { component: null },
  // ...
};

// 2. 패턴 매칭이 필요한 경로들을 모아둔 배열
export const PATTERN_PATH_CONFIGS = [
  {
    // /home/[id] 와 같은 동적 경로
    test: (p: string) => /^\/home\/.+/.test(p),
    config: { component: "BackHeader", withAlarm: true },
  },
  // ...
];

5. 완성된 HeaderController

  1. 정확한 정적 경로 조회
  2. 없으면 동적 경로를 조회

다음과 같은 방법으로 명확한 우선순위 로직을 적용하여 최종 HeaderController를 완성했습니다.

"use client";
import { usePathname, useRouter } from "next/navigation";
import { useAtom } from "jotai";
import { hasNewNotificationAtom } from "@/stores/notificatonAtom";
import { EXACT_PATH_CONFIGS, PATTERN_PATH_CONFIGS } from "@/constants/header.constants";
import BackHeader from "./BackHeader";
import LogoHeader from "./LogoHeader";

const HeaderController = () => {
  const router = useRouter();
  const pathname = usePathname();
  // ... (hooks 및 핸들러 함수)

  // 1. 정확한 경로 객체에서 먼저 조회
  let config = EXACT_PATH_CONFIGS[pathname];

  // 2. 정확한 경로에 대한 설정이 없을 경우, 패턴 경로 배열에서 조회
  if (!config) {
    const patternMatch = PATTERN_PATH_CONFIGS.find(c => c.test(pathname));
    if (patternMatch) {
      config = patternMatch.config;
    }
  }

  // 설정에 따라 최종 헤더 렌더링
  if (!config || !config.component) return null;

  switch (config.component) {
    case "LogoHeader":
      // ...
    case "BackHeader":
      // ...
    default:
      return null;
  }
};

export default HeaderController;

다음과 같은 방법으로 Header를 보다 효율적으로 관리할 수 있게 되었습니다!

profile
안녕하세요 프론트엔드개발자가 되고싶습니다

0개의 댓글