2 min read

Next.js에서 Clean Architecture 실천

UseCase와 Infra 경계를 명확히 분리해 기능 확장 시 복잡도 증가를 억제하는 구조

Next.js에서 Clean Architecture 실천 thumbnail

도입

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

Next.js에서 Clean Architecture 실천 커버
Wikimedia Commons 기반 무료 이미지

문제 정의

아키텍처 품질이 떨어질 때 공통적으로 나타나는 신호가 있다.

  • 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 AdaptersAction/Route/PresenterpublishPostAction
FrameworksNext.js, DB, CacheApp Router, Postgres

인프라 구성도

Mermaid diagram rendering...

트레이드오프

  • 레이어 분리는 초기 코드량을 늘리지만 변경 영향 범위를 명확히 줄인다.
  • 포트 인터페이스를 두면 테스트가 쉬워지지만 작은 프로젝트에서는 과하다고 느낄 수 있다.
  • 구조를 엄격히 지키면 팀 생산성이 안정되지만 온보딩 시 학습 비용이 필요하다.

정리

Next.js에서 Clean Architecture를 적용하는 목적은 이론적 순수성이 아니라 운영 안정성이다. 도메인 규칙을 중심에 두고 프레임워크를 어댑터로 배치하면 기능 확장 속도와 품질을 함께 확보할 수 있다.

이미지 출처

댓글