익명고민함 - 프로젝트🚀 Next.js의 레이아웃 구조

조준형·2025년 10월 18일

익명 고민함

목록 보기
1/6

최근에 익명고민함이라는 Next.js 프로젝트를 새로 시작했다.

이 프로젝트는 “익명으로 고민을 나누는 커뮤니티”를 만들고 싶어서 시작한 팀 프로젝트인데,

Next.js는 아직 익숙하지 않아 하나씩 공부하면서 구조를 잡고 있다.

이번 글에서는 프로젝트를 세팅하면서 받았던 피드백 중

가장 도움이 되었던 부분인 “루트에서 바로 페이지를 만들지 말고, 한 뎁스를 더 파서 관리하라”

이 부분을 실제로 어떻게 적용했는지를 정리해봤다.


🧩 처음 구조 — 전부 루트에 몰아넣은 상태

처음엔 단순하게 이렇게 시작했다.

app/
├── layout.tsx
├── page.tsx
└── globals.css

app/page.tsx에서 바로 메인 페이지를 구성하고,

Header나 SearchFilter, TagBadge 같은 컴포넌트를 전부 여기에 넣었다.

// app/page.tsx
import Header from '@/components/common/Header'
import SearchFilter from '@/components/common/SearchFilter'
import TagBadge, { DEFAULT_TAGS } from '@/components/common/TagBadge'

export default function HomePage() {
  return (
    <><Header ... />
      <SearchFilter ... />
      <div className="flex flex-wrap gap-2 mt-4">
        {DEFAULT_TAGS.map(tag => (
          <TagBadge key={tag.value}>{tag.label}</TagBadge>
        ))}
      </div>
    </>
  )
}

그런데 코드가 점점 길어지고,

이 Header를 다른 페이지에서도 쓸 거면 layout에 두는 게 낫지 않을까?

root layout이랑 page 역할이 좀 섞여 있는 것 같다

라는 피드백을 받았다.


💡 피드백 내용 — “뎁스를 하나 더 파서 (main)으로 분리해라”

Next.js의 App Router는 app/layout.tsxapp/page.tsx 구조를 기본으로 쓰는데,

layout은 고정된 외피, page는 실제 콘텐츠 역할을 한다.

문제는 루트에 전부 넣으면

  • 전역 layout과 페이지 layout의 역할이 섞이고
  • Header 같은 공통 UI를 재사용하기 어렵고
  • 섹션별로 다른 레이아웃을 만들기도 힘들다는 점이었다.

그래서 구조를 아래처럼 바꿨다 👇


🏗️ 리팩터링 후 구조

app/
├── layout.tsx          ← 전역 (html/body/provider)
├── (main)/             ← 메인 섹션 전용
│   ├── layout.tsx      ← Header 포함
│   └── page.tsx        ← 메인 페이지 내용
└── globals.css

🧱 app/layout.tsx

전역 레이아웃은 html, body, Provider 등 최소한만 유지했다.

// app/layout.tsx
import './globals.css'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ko">
      <body className="min-h-screen bg-background text-foreground antialiased">
        {children}
      </body>
    </html>
  )
}

🧭 app/(main)/layout.tsx

메인 전용 레이아웃.

Header를 이쪽으로 옮겨서 /, /post/... 같은 페이지 전환 시에도

Header가 유지되도록 했다.

// app/(main)/layout.tsx
import Header from '@/components/common/Header'

export default function MainLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="min-h-screen bg-background text-foreground">
      <HeaderclassName="glass-card border-b border-border/50"
        containerClassName="flex items-center justify-between"
      >
        <div className="flex items-center gap-2">
          <h1 className="text-xl sm:text-2xl font-extrabold gradient-text">익명고민함</h1>
        </div>
      </Header>

      <main className="mx-auto max-w-4xl px-4 sm:px-6 py-6">{children}</main>
    </div>
  )
}

🏠 app/(main)/page.tsx

이제 메인 페이지는 콘텐츠만 담당하게 깔끔해졌다.

// app/(main)/page.tsx
import SearchFilter from '@/components/common/SearchFilter'
import TagBadge, { DEFAULT_TAGS } from '@/components/common/TagBadge'

export default function HomePage() {
  return (
    <><SearchFilterplaceholder="내용이나 태그로 검색..."
        className="text-base"
        containerClassName="h-12 w-full rounded-[16px] bg-background border border-border/60 focus-within:ring-2 ring-ring/40"
      />

      <div className="flex flex-wrap gap-2 mt-4">
        {DEFAULT_TAGS.map((t) => (
          <TagBadge key={t.value} size="md">
            {t.label}
          </TagBadge>
        ))}
      </div>
    </>
  )
}

⚙️ 이렇게 구조를 바꾸고 나서 얻은 점

  1. Header가 페이지 전환 시 유지된다.

    //post/1로 이동해도 Header가 새로 렌더링되지 않는다.

    layout의 “지속 렌더링(persistent)” 개념을 직접 체감했다.

  2. 역할이 명확해졌다.

    • RootLayout → 전역 구조 (HTML, body, Provider)
    • (main)/layout → Header + Container
    • (main)/page → 실제 페이지 콘텐츠
  3. 확장하기 쉬운 구조가 되었다.

    나중에 /auth/mypage 같은 다른 섹션이 생겨도

    (auth)/layout.tsx, (mypage)/layout.tsx를 만들어서 독립적으로 관리 가능하다.


💭 느낀 점

Next.js의 “중첩 레이아웃” 개념을 이번에 처음 제대로 이해했다.

React로만 개발할 때는 컴포넌트를 공통으로 import해서 쓰면 된다고 생각했는데,

Next.js는 아예 폴더 구조 단위로 UI의 계층과 지속성을 관리한다는 게 새로웠다.

특히 “URL은 바뀌지 않지만 page만 교체될 때 layout은 그대로 유지된다”는

렌더링 구조가 진짜 강력했다.

처음엔 괜히 복잡해 보였는데,

이제는 “(main), (auth), (mypage)”처럼 폴더로 섹션을 구분하는 게

진짜 유지보수하기 좋은 구조라는 걸 몸으로 느꼈다.


profile
코린이

0개의 댓글