2 min read
Next.js에서 Clean Architecture 실천
UseCase와 Infra 경계를 명확히 분리해 기능 확장 시 복잡도 증가를 억제하는 구조

도입
Next.js 프로젝트가 커질수록 페이지 중심 구조만으로는 도메인 규칙을 관리하기 어려워진다. App Router의 편의성에 의존하면 비즈니스 로직이 Route, Server Action, 컴포넌트에 분산되어 변경 비용이 급격히 증가한다. 이 글은 Next.js 환경에서 Clean Architecture를 실용적으로 적용하는 구조를 제시한다.

문제 정의
아키텍처 품질이 떨어질 때 공통적으로 나타나는 신호가 있다.
- Server Action이 직접 DB를 호출해 도메인 규칙이 재사용되지 않는다.
- 인프라 예외가 UI로 직접 노출돼 오류 처리 정책이 일관되지 않다.
- 테스트가 페이지 단위로만 존재해 도메인 회귀를 빠르게 검출하지 못한다.
핵심은 프레임워크에 맞추되 도메인 중심 의존성 방향을 유지하는 것이다. 기능 구현보다 경계 유지가 장기 속도를 결정한다.
핵심 개념
| 관점 | 설계 기준 | 검증 포인트 |
|---|---|---|
| 의존성 | 안쪽 레이어가 바깥 레이어를 몰라야 함 | 도메인 테스트 프레임워크 독립성 |
| 유즈케이스 | 입력/출력 모델 명시 | 액션/라우트 재사용률 |
| 어댑터 | DB/API를 포트로 캡슐화 | 교체 시 영향 파일 수 |
| 프레젠테이션 | UI는 결과 모델만 소비 | 에러 메시지 일관성 |
Clean Architecture는 계층을 늘리는 방식이 아니라 책임을 고정하는 방식이다. 특히 Next.js에서는 Route Handler와 Server Action을 인터페이스 어댑터로 보는 관점이 유효하다.
코드 예시 1: UseCase + Port 정의
export type PublishPostInput = {
actorId: string;
title: string;
body: string;
};
export interface PostRepository {
save(input: { id: string; title: string; body: string; authorId: string }): Promise<void>;
}
export class PublishPostUseCase {
constructor(private readonly repo: PostRepository) {}
async execute(input: PublishPostInput) {
if (input.title.length < 8) throw new Error("title too short");
const id = crypto.randomUUID();
await this.repo.save({ id, title: input.title, body: input.body, authorId: input.actorId });
return { id };
}
}
코드 예시 2: Server Action 어댑터
"use server";
export async function publishPostAction(formData: FormData) {
const useCase = new PublishPostUseCase(new PostgresPostRepository());
const output = await useCase.execute({
actorId: String(formData.get("actorId")),
title: String(formData.get("title")),
body: String(formData.get("body")),
});
return { ok: true, postId: output.id };
}
아키텍처 흐름
Mermaid diagram rendering...
Clean Architecture 레이어 구조
Next.js에서는 프레임워크 코드가 강하지만 의존성 방향을 유지하면 도메인 안정성을 확보할 수 있다. 아래처럼 계층별 책임을 고정하면 기능 추가 시 회귀 범위를 줄일 수 있다.
| Layer | 책임 | 적용 방식 |
|---|---|---|
| Entities | 도메인 규칙 | Post, Policy |
| Use Cases | 비즈니스 플로우 | PublishPostUseCase |
| Interface Adapters | Action/Route/Presenter | publishPostAction |
| Frameworks | Next.js, DB, Cache | App Router, Postgres |
인프라 구성도
Mermaid diagram rendering...
트레이드오프
- 레이어 분리는 초기 코드량을 늘리지만 변경 영향 범위를 명확히 줄인다.
- 포트 인터페이스를 두면 테스트가 쉬워지지만 작은 프로젝트에서는 과하다고 느낄 수 있다.
- 구조를 엄격히 지키면 팀 생산성이 안정되지만 온보딩 시 학습 비용이 필요하다.
정리
Next.js에서 Clean Architecture를 적용하는 목적은 이론적 순수성이 아니라 운영 안정성이다. 도메인 규칙을 중심에 두고 프레임워크를 어댑터로 배치하면 기능 확장 속도와 품질을 함께 확보할 수 있다.
이미지 출처
- Cover: [source link](https://commons.wikimedia.org/wiki/File:Photocopy_of_blueprint_(See_field_records_for_original_blueprint)
- License: Public domain / Author: Unknown
- Note: Wikimedia Commons 무료 라이선스 이미지를 다운로드 후 1600px 기준 JPG로 최적화했습니다.