Next.js
강의
#0 - Next.js App Router Co-location 패턴
중년개발자
@loxo
30일 전
36
잘 돌아가는 팀에는 명확한 역할 분담이 있다.
기획자는 기획을, 디자이너는 디자인을, 개발자는 구현을 맡는다. 하지만 이들이 서로 다른 건물에 흩어져 있다면 협업 비용은 급격히 올라간다.
Next.js App Router의 Co-location 패턴은 이 문제를 코드 레벨에서 해결한다.
서버 컴포넌트는 데이터와 책임을, 클라이언트 컴포넌트는 상호작용과 경험을 맡되, 둘을 같은 기능 단위 안에 둔다.
역할은 분리하되, 거리는 가깝게 유지하는 구조다.
이 글에서는 App Router 환경에서 Co-location 패턴이 어떤 의미를 가지는지, 그리고 실제 프로젝트에서 어떻게 적용되는지를 단계적으로 설명한다.
Next.js App Router Co-location 패턴
개요
Next.js App Router에서 서버/클라이언트 컴포넌트를 함께 배치하는 공식 권장 패턴입니다. 관련 파일을 기능 단위로 묶어 관리의 편의성과 유지보수성을 높입니다.
파일 구조
bash
app/
└── posts/
└── [id]/
├── page.tsx # Server Component (라우트 진입점)
├── PostDetailClient.tsx # Client Component (상호작용 담당)
├── loading.tsx # 로딩 UI (선택 사항)
├── error.tsx # 에러 경계 (선택 사항)
└── not-found.tsx # 404 UI (선택 사항)Server Component (page.tsx)
데이터 페칭, 메타데이터 생성, SEO 처리를 담당합니다. 클라이언트 상호작용이 필요한 부분은 Client Component로 위임합니다.
typescript
import { postService } from '@/services/post.service';
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { PostDetailClient } from './PostDetailClient';
type Props = {
params: Promise<{ id: string }>;
};
// SEO 메타데이터 생성
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { id } = await params;
const post = await postService.getPost(id);
return {
title: `${post.title} - 내 사이트`,
description: post.content?.substring(0, 160),
};
}
// Server Component - 데이터 페칭
export default async function PostDetailPage({ params }: Props) {
const { id } = await params;
let post;
try {
post = await postService.getPost(id);
} catch {
notFound();
}
if (!post) {
notFound();
}
// 서버에서 가져온 데이터를 클라이언트 컴포넌트로 전달
return <PostDetailClient post={post} />;
}Client Component (PostDetailClient.tsx)
사용자 이벤트(클릭, 입력 등)와 브라우저 API 접근, 상태 관리(useState, useEffect)를 담당합니다.
typescript
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import type { Post } from '@/types/post';
interface PostDetailClientProps {
post: Post; // 서버에서 전달받은 데이터
}
export function PostDetailClient({ post }: PostDetailClientProps) {
const router = useRouter();
const [isBookmarked, setIsBookmarked] = useState(false);
// 클라이언트 측 상호작용
const handleBookmark = () => {
setIsBookmarked(!isBookmarked);
};
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
<button onClick={handleBookmark}>
{isBookmarked ? '북마크 해제' : '북마크'}
</button>
</div>
);
}핵심 원칙 (Key Principles)
| 원칙 | 설명 |
|---|---|
| Co-location (공동 배치) | 관련된 파일들을 동일한 폴더에 함께 둠으로써 응집도를 높입니다. |
| Separation (역할 분리) | 서버는 데이터 페칭, 클라이언트는 상호작용을 담당합니다. |
| Performance (성능) | 서버 컴포넌트를 활용하여 클라이언트 번들 사이즈를 줄입니다. |
| Maintainability (유지보수) | 라우트 단위로 코드가 그룹화되어 있어 찾기 쉽고 수정이 용이합니다. |
사용 시점 (When to Use)
| Server Component | Client Component |
|---|---|
| 데이터 페칭 (Data Fetching) | useState, useEffect 사용 시 |
| 데이터베이스 직접 조회 | onClick, onChange 등 이벤트 처리 |
| API 호출 (보안 민감 정보) | 브라우저 전용 API (window, document 등) |
| 메타데이터 생성 (SEO) | 애니메이션 및 트랜지션 |
| 무거운 의존성 라이브러리 사용 | 실시간 업데이트 (Real-time updates) |
장점 (Benefits)
- SEO 향상: 검색 엔진에 최적화된 서버 렌더링 콘텐츠 제공
- 성능 최적화: 클라이언트 자바스크립트 번들을 줄여 초기 로딩 속도 향상
- 보안 강화: API 키나 민감한 로직을 서버에 숨길 수 있음
- 스트리밍 지원: Suspense를 통한 점진적 페이지 렌더링 가능
참고 자료 (References)
목차
#App Router#Co-location#React#TypeScript#next.js 16
댓글 0
Ctrl + Enter를 눌러 등록할 수 있습니다※ AI 다듬기는 내용을 정제하는 보조 기능이며, 최종 내용은 사용자가 확인해야 합니다.