7.7 KiB
Next.js App Router의 라우트 그룹(Route Groups) 구현 분석
개요
Next.js의 App Router에서는 괄호로 묶인 디렉토리((with-base-layout), (without-base-layout))를 라우트 그룹으로 처리하여 URL 경로에 영향을 주지 않으면서 서로 다른 레이아웃을 적용할 수 있게 합니다. 이 문서에서는 프로젝트에서 라우트 그룹이 어떻게 구현되고 사용되는지 분석합니다.
라우트 그룹 구조 분석
1. 디렉토리 구조
프로젝트에서는 두 가지 주요 라우트 그룹이 사용되고 있습니다:
/apps/cs/app/
├── (with-base-layout)/
│ ├── banner/
│ ├── bbs/
│ ├── code/
│ ├── contents/
│ ├── dashboard/
│ ├── ...
│ ├── layout.tsx
│ └── loading.tsx
├── (without-base-layout)/
│ ├── user-survey-article/
│ └── user-survey-result/
└── layout.tsx
2. 라우트 그룹별 레이아웃 구현
루트 레이아웃 (/apps/cs/app/layout.tsx)
import type { Metadata } from "next";
import { App } from "@/app";
export const metadata: Metadata = {
title: "유역하수도 통합 정보플랫폼",
};
export default App;
App 컴포넌트 (/apps/cs/src/app/index.tsx)
import "./styles/base.css";
import "./styles/components.css";
import "@fontsource-variable/noto-sans-kr";
import { AppProvider } from "./providers";
import { cookies } from "next/headers";
const App = async ({ children }: Readonly<{ children: React.ReactNode }>) => {
const theme = (await cookies()).get("site-theme-key");
return (
<html lang="ko" data-theme={theme?.value}>
<body>
<AppProvider>{children}</AppProvider>
</body>
</html>
);
};
export { App };
(with-base-layout) 레이아웃 (/apps/cs/app/(with-base-layout)/layout.tsx)
"use client";
import { usePathname, useRouter } from "next/navigation";
import { useQueryClient } from "@tanstack/react-query";
import { BaseLayout } from "@common/components";
import type { MenuItem } from "@/entities/menu";
import { useMenuStore } from "@/shared/lib";
import { PageLayout } from "@/shared/ui/page-layout";
export default function WithBaseLayout({
children,
}: {
children: React.ReactNode;
}) {
const router = useRouter();
const pathname = usePathname();
const queryClient = useQueryClient();
const setCurrentMenuInfo = useMenuStore((state) => state.setCurrentMenuInfo);
const handleMenuClick = (item: MenuItem) => {
setCurrentMenuInfo(item);
router.push(item.urlPath);
};
return (
<BaseLayout
queryClient={queryClient}
currentPath={pathname ?? ""}
handleMenuClick={handleMenuClick}
>
<PageLayout>{children}</PageLayout>
</BaseLayout>
);
}
PageLayout 컴포넌트 (/apps/cs/src/shared/ui/page-layout.tsx)
import { PageTitle } from "@common/components";
import { useMenuStore } from "../lib/use-menu-store";
function PageLayout({ children }: { children: React.ReactNode }) {
const currentMenuInfo = useMenuStore((state) => state.currentMenuInfo);
const menuPaths = currentMenuInfo?.menuPath
?.split(">")
.map((path) => path.trim());
return (
<div className="h-auto min-h-full bg-container pb-2">
<PageTitle
title={currentMenuInfo?.menuKornNm ?? ""}
menuPaths={menuPaths ?? []}
/>
{children}
</div>
);
}
라우트 그룹의 작동 원리
1. URL 경로에 영향을 주지 않는 그룹화
Next.js App Router에서 괄호로 묶인 디렉토리는 URL 경로에 포함되지 않습니다. 예를 들어:
/bbs/register경로는(with-base-layout)/bbs/register디렉토리에 매핑됩니다./user-survey-article/1경로는(without-base-layout)/user-survey-article/[srvySn]디렉토리에 매핑됩니다.
2. 레이아웃 분리 및 재사용
라우트 그룹을 사용하면 서로 다른 레이아웃을 가진 페이지들을 효율적으로 구성할 수 있습니다:
(with-base-layout)그룹: 메뉴, 헤더, 사이드바 등을 포함하는BaseLayout과PageLayout을 사용합니다.(without-base-layout)그룹: 기본 레이아웃 없이 페이지 컴포넌트만 렌더링합니다.
3. 중첩 라우트 그룹
프로젝트에서는 중첩된 라우트 그룹도 사용되고 있습니다:
(with-base-layout)/bbs/(post)/bbsnormal/
여기서 (post)는 게시물 관련 페이지들을 그룹화하는 중첩된 라우트 그룹입니다.
라우트 그룹 사용 사례
1. 관리자 인터페이스 (with-base-layout)
관리자 인터페이스는 일관된 레이아웃과 네비게이션이 필요합니다:
// (with-base-layout)/layout.tsx
return (
<BaseLayout
queryClient={queryClient}
currentPath={pathname ?? ""}
handleMenuClick={handleMenuClick}
>
<PageLayout>{children}</PageLayout>
</BaseLayout>
);
이 레이아웃은 다음과 같은 기능을 제공합니다:
- 메뉴 시스템을 통한 페이지 간 이동
- 현재 페이지 경로 표시
- 일관된 페이지 타이틀 및 구조
2. 독립적인 페이지 (without-base-layout)
설문 기사나 결과 페이지와 같은 독립적인 페이지는 기본 레이아웃 없이 콘텐츠에 집중합니다:
// (without-base-layout)/user-survey-article/[srvySn]/page.tsx
export default async function UserSurveyArticlePage({
params,
}: {
params: Promise<{ srvySn: string }>;
}) {
const { srvySn } = await params;
return <UserSurveyArticle srvySn={srvySn} />;
}
이러한 페이지는:
- 외부 링크나 팝업으로 열리는 경우가 많음
- 독립적인 기능을 제공(예: 설문 제출 후 창 닫기)
- 메인 애플리케이션 UI와 분리된 경험 제공
두 라우트 그룹의 주요 차이점
(with-base-layout)
BaseLayout컴포넌트를 포함하는 레이아웃을 사용합니다.- 이 레이아웃은 메뉴, 헤더, 사이드바 등 기본 UI 요소를 포함합니다.
PageLayout컴포넌트로 감싸져 있어 일관된 페이지 구조를 제공합니다.- 주로 관리자 페이지나 로그인 후 사용하는 내부 페이지에 사용됩니다.
- 다양한 관리 기능을 포함하는 여러 하위 디렉토리가 있습니다.
(without-base-layout)
- 기본 레이아웃 없이 페이지 컴포넌트만 렌더링합니다.
- 메뉴, 헤더, 사이드바 등의 UI 요소가 없는 독립적인 페이지입니다.
- 주로 외부 사용자용 페이지나 팝업, 임베드 페이지에 사용됩니다.
- 현재 두 가지 특수 목적 페이지만 포함하고 있습니다.
기술적 이점
- 코드 구성 개선: 관련 페이지와 레이아웃을 논리적으로 그룹화할 수 있습니다.
- 레이아웃 재사용: 공통 레이아웃을 여러 페이지에서 재사용할 수 있습니다.
- URL 구조 단순화: 그룹 이름이 URL에 포함되지 않아 깔끔한 URL 구조를 유지할 수 있습니다.
- 유지보수성 향상: 관련 기능을 함께 그룹화하여 코드 탐색과 유지보수가 용이합니다.
결론
Next.js App Router의 라우트 그룹 기능은 프로젝트에서 다양한 레이아웃 요구사항을 효율적으로 관리하는 데 중요한 역할을 합니다. (with-base-layout)과 (without-base-layout) 그룹을 통해 관리자 인터페이스와 독립적인 페이지를 명확하게 분리하면서도 코드 재사용성과 유지보수성을 높일 수 있습니다.
이 구조는 확장성이 뛰어나 새로운 페이지나 기능을 추가할 때 기존 패턴을 따르기 쉽고, 애플리케이션이 성장함에 따라 필요한 경우 추가 라우트 그룹을 도입할 수 있는 유연성을 제공합니다.