본문으로 바로가기
리옵트 핸드북
리옵트 핸드북
엔터프라이즈 프로젝트 설계

모노레포 기반

Ch1. 모노레포 아키텍처 설계Ch2. Workspace 설계와 프로토콜Ch3. 공유 패키지 설계 패턴Ch4. Turborepo 파이프라인과 캐싱

앱 & 배포

Ch5. Next.js App Router 엔터프라이즈 패턴Ch6. Vercel 배포 전략Ch7. CI/CD 파이프라인 설계Ch8. 테스트 전략

에이전트 & 운영

Ch9. Agentic 개발 체계Ch10. skills.sh 생태계 활용Ch11. 보안과 코드 거버넌스Ch12. 모니터링과 장애 대응

부록

부록. 실무 템플릿Ch13. 레퍼런스와 아키텍처 결정 기록부록. 업데이트 이력검증 리포트
핸드북›엔터프라이즈 프로젝트 설계›Ch3. 공유 패키지 설계 패턴
한국어English

Ch3. 공유 패키지 설계 패턴

DB(Prisma), UI, utils, config, types 패키지의 설계 패턴과 트리셰이킹

핵심 요약

  • DB(Prisma)·UI·utils·types·config 패키지는 빌드 여부와 exports 방식(배럴 vs 서브패스)을 유형별로 다르게 설계합니다.
  • Prisma는 @org/db 패키지로 공유하고, 클라이언트는 module scope가 아닌 getDb() 함수 안에서 lazy initialize + globalThis 캐시로 HMR 연결 폭증을 막습니다.
  • UI 패키지는 컴포넌트별 서브패스 export로 소비자가 필요한 것만 import해 번들 크기를 줄입니다.
  • sideEffects: false로 미사용 export를 제거하되, 글로벌 CSS가 있으면 ["*.css"]로 CSS만 보존합니다.
  • 대부분은 내부 패키지(private: true, 소스 직접 export)로 충분합니다. React 19에서는 forwardRef 없이 ref를 prop으로 받습니다.

패키지 유형별 설계 원칙

유형빌드 여부exports 방식예시
DB (Prisma)prisma generate 필요배럴 export@org/db
UI 컴포넌트소스 직접 export컴포넌트별 서브패스@org/ui/button
유틸리티소스 직접 export배럴 + 서브패스@org/utils, @org/utils/cn
타입 정의빌드 불필요배럴 export@org/types
설정빌드 불필요프리셋별 서브패스@org/tsconfig/nextjs

DB 패키지 (Prisma)

모노레포에서 Prisma는 @org/db 패키지로 공유합니다. 스키마와 Prisma Client를 한곳에서 관리하면 여러 Next.js 앱이 같은 타입-안전 DB 클라이언트를 import합니다.

packages/db/
├── prisma/
│   ├── schema.prisma     # 스키마 정의
│   └── migrations/       # 마이그레이션 이력
├── src/
│   ├── client.ts         # PrismaClient 싱글턴
│   └── index.ts          # 배럴 export
├── package.json
└── tsconfig.json
// packages/db/src/client.ts
import { PrismaClient } from '@prisma/client';

const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined };

export function getDb() {
  if (!globalForPrisma.prisma) {
    globalForPrisma.prisma = new PrismaClient({
      log: process.env.NODE_ENV === 'development' ? ['query', 'warn', 'error'] : ['error'],
    });
  }

  return globalForPrisma.prisma;
}
// packages/db/src/index.ts
export { getDb } from './client';
export type { User, Post, Prisma } from '@prisma/client';
// packages/db/package.json
{
  "name": "@acme/db",
  "private": true,
  "exports": {
    ".": "./src/index.ts"
  },
  "scripts": {
    "db:generate": "prisma generate",
    "db:migrate": "prisma migrate dev",
    "db:push": "prisma db push",
    "db:studio": "prisma studio"
  },
  "dependencies": {
    "@prisma/client": "^7.8.0"
  },
  "devDependencies": {
    "prisma": "^7.8.0"
  }
}

앱에서 사용할 때:

// apps/web/app/api/users/route.ts
import { getDb } from '@acme/db';

export async function GET() {
  const db = getDb();
  const users = await db.user.findMany({ take: 20 });
  return Response.json(users);
}
// apps/admin/app/users/actions.ts
'use server';
import { getDb } from '@acme/db';

export async function deleteUser(id: string) {
  const db = getDb();
  await db.user.delete({ where: { id } });
}

Prisma Client lazy singleton 패턴

Next.js 빌드와 정적 평가 과정에서는 런타임 환경 변수가 준비되기 전에 모듈이 먼저 로드되기도 합니다. DB 클라이언트는 module scope에서 즉시 생성하지 말고 getDb() 같은 함수 안에서 lazy initialize하세요. 개발 환경에서는 globalThis 캐시로 HMR 중 연결 폭증을 막습니다.

UI 컴포넌트 패키지

// packages/ui/src/button.tsx
import { cn } from '@acme/utils/cn';
import type { ComponentPropsWithRef } from 'react';

type ButtonProps = ComponentPropsWithRef<'button'> & {
  variant?: 'primary' | 'secondary' | 'ghost';
  size?: 'sm' | 'md' | 'lg';
};

export function Button({ variant = 'primary', size = 'md', className, ...props }: ButtonProps) {
  return (
    <button
      className={cn(
        'inline-flex items-center justify-center rounded-md font-medium transition-colors',
        variantStyles[variant],
        sizeStyles[size],
        className
      )}
      {...props}
    />
  );
}

서브패스 export 패턴을 쓰면 소비자가 필요한 컴포넌트만 import해 번들 크기를 줄입니다.

// packages/ui/package.json — exports
{
  "exports": {
    ".": "./src/index.ts",
    "./button": "./src/button.tsx",
    "./card": "./src/card.tsx",
    "./dialog": "./src/dialog.tsx"
  }
}

유틸리티 패키지

유틸리티 함수는 순수 함수 원칙을 지킵니다. 외부 상태에 기대지 않고 입력→출력만으로 동작해야 트리셰이킹이 정확하게 작동합니다.

// packages/utils/src/cn.ts
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}
// packages/utils/src/format-date.ts
export function formatDate(date: Date, locale = 'ko-KR'): string {
  return new Intl.DateTimeFormat(locale, {
    year: 'numeric', month: 'long', day: 'numeric'
  }).format(date);
}

타입 정의 패키지

// packages/types/src/user.ts
export type User = {
  id: string;
  email: string;
  name: string;
  role: 'admin' | 'member' | 'viewer';
  createdAt: Date;
};

export type CreateUserInput = Omit<User, 'id' | 'createdAt'>;

Prisma가 생성하는 타입(@prisma/client)과 커스텀 타입(@org/types)을 함께 쓰면 Server Actions과 클라이언트 컴포넌트 사이의 타입 불일치를 컴파일 타임에 잡습니다.

트리셰이킹과 sideEffects

// packages/utils/package.json
{
  "sideEffects": false  // 번들러에게 미사용 export 제거 허용
}
설정효과
"sideEffects": false미사용 export를 번들에서 완전 제거
"sideEffects": ["*.css"]CSS만 부수효과로 보존, 나머지 제거
미설정번들러가 보수적으로 판단 (제거 안 함)

UI 패키지에 글로벌 CSS가 있다면 "sideEffects": ["*.css"]를 설정하세요.

내부 vs 퍼블리시 패키지

내부 패키지 (권장 기본값):
  ├── "private": true
  ├── 소스 직접 export (빌드 불필요)
  ├── workspace:* 참조
  └── 버전 번호 의미 없음

퍼블리시 패키지 (npm 배포 시):
  ├── "private": false
  ├── dist/ 빌드 필수 (tsup, unbuild)
  ├── Changesets로 버전 관리
  └── CI에서 자동 publish

엔터프라이즈 프로젝트는 대부분 내부 패키지로 충분합니다. 외부 배포가 필요해지는 시점에 tsup이나 unbuild를 도입하세요.

React 19 타입 변경

React 19에서 ref가 prop으로 전달되면서 forwardRef가 불필요해졌습니다. UI 패키지의 컴포넌트를 React 19 스타일로 업데이트하세요:

// React 19: forwardRef 없이 ref를 직접 받음
type ButtonProps = ComponentPropsWithRef<'button'> & {
  variant?: 'primary' | 'secondary';
};

export function Button({ ref, variant = 'primary', ...props }: ButtonProps) {
  return <button ref={ref} className={variantStyles[variant]} {...props} />;
}

참고 문서

  • Prisma: Get Started (영어)
  • Prisma: Best Practices for Monorepos (영어)
  • Node.js: Package Exports (영어)
  • Turborepo: Sharing Code (영어)
  • webpack: Tree Shaking (영어)

관련 문서

검증 리포트

Prisma Production Guardrails · Prisma Production Guardrails 핸드북의 구조 및 정합성 검증

Ch2. Workspace 설계와 프로토콜

Yarn/pnpm workspace protocol, 패키지 간 참조, tsconfig 공유 전략

업데이트 내역

Prisma Production Guardrails · Prisma Production Guardrails 핸드북 변경 기록

Ch12. Prisma MCP 활용

Prisma Production Guardrails · Prisma MCP를 안전하게 운영 환경에 도입하기 위한 로컬/리모트 구성, 권한 제어, 운영 패턴

Ch4. 마이그레이션 전략

Prisma Production Guardrails · Expand/Contract 기반 무중단 마이그레이션과 위험 변경 분리 전략

Ch2. Workspace 설계와 프로토콜

Yarn/pnpm workspace protocol, 패키지 간 참조, tsconfig 공유 전략

Ch4. Turborepo 파이프라인과 캐싱

Task pipeline 설계, dependsOn, remote caching, 빌드 최적화

On this page

패키지 유형별 설계 원칙DB 패키지 (Prisma)UI 컴포넌트 패키지유틸리티 패키지타입 정의 패키지트리셰이킹과 sideEffects내부 vs 퍼블리시 패키지React 19 타입 변경참고 문서