← 블로그로 돌아가기
개발 2026년 4월 1일
Turborepo로 웹 + 크롬 확장 동시 개발하기
Next.js 웹앱과 WXT 크롬 확장을 하나의 모노레포에서 관리하는 실전 구성.
Turborepo 모노레포 Chrome Extension WXT Next.js
왜 모노레포인가
Repasta는 두 개의 앱으로 구성된다.
- apps/web — Next.js App Router 웹앱 (변환 엔진, 결제, 블로그)
- apps/extension — WXT 기반 크롬 확장 (페이지 감지, 원클릭 변환)
처음에는 별도 레포로 시작했다. 문제는 공유 코드였다. 변환 로직의 타입 정의, API 응답 인터페이스, 유틸 함수가 양쪽에서 필요했다. 복사-붙여넣기로 버텼지만 한 쪽을 고치면 다른 쪽이 깨졌다.
모노레포로 합치면 packages/shared에 공유 코드를 두고, 양쪽에서 import하면 된다.
프로젝트 구조
Repasta_/
├── apps/
│ ├── web/ # Next.js App Router
│ └── extension/ # WXT Chrome Extension
├── packages/
│ └── shared/ # 공유 타입, 유틸
├── turbo.json
├── pnpm-workspace.yaml
└── package.json
pnpm-workspace.yaml에서 워크스페이스를 정의한다.
packages:
- "apps/*"
- "packages/*"
Turborepo 설정
turbo.json에서 각 앱의 빌드 파이프라인을 정의한다.
{
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "dist/**", ".output/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"lint": {
"dependsOn": ["^build"]
}
}
}
핵심은 "dependsOn": ["^build"]다. shared 패키지가 먼저 빌드되고, 그 다음 web과 extension이 빌드된다. 의존성 순서를 Turborepo가 자동으로 관리한다.
공유 패키지
// packages/shared/src/types.ts
export interface ConversionResult {
slides: Slide[];
caption: string;
hashtags: string[];
}
export interface Slide {
title: string;
body: string;
order: number;
}
// packages/shared/src/constants.ts
export const MAX_SLIDES = 10;
export const MAX_CAPTION_LENGTH = 2200;
export const FREE_DAILY_LIMIT = 1;
web과 extension 모두 같은 타입을 참조한다.
// apps/web/src/lib/convert.ts
import type { ConversionResult } from '@repasta/shared';
// apps/extension/src/lib/api.ts
import type { ConversionResult } from '@repasta/shared';
타입을 한 곳에서 바꾸면 양쪽 다 반영된다. 타입이 안 맞으면 빌드에서 잡힌다.
WXT와 Next.js 공존
WXT는 크롬 확장 프레임워크로, 내부적으로 Vite를 사용한다. Next.js는 Turbopack을 사용한다. 빌드 도구가 다르지만 Turborepo가 각각 독립적으로 실행하기 때문에 충돌이 없다.
turbo dev를 실행하면 두 앱이 동시에 뜬다.
turbo dev
# apps/web:dev — http://localhost:3000
# apps/extension:dev — chrome extension hot reload
주의점
- 버전 동기화: shared 패키지 버전을
"workspace:*"로 설정해야 항상 최신 버전을 참조한다 - 빌드 순서: shared에 런타임 코드가 있으면 반드시 빌드 후 사용해야 한다. 타입만 export하면 빌드 없이도 동작
- 환경변수 분리: web과 extension의
.env가 다르다. 루트가 아닌 각 앱 디렉토리에.env배치 - Chrome Extension 제한: 확장 프로그램은 Node.js API를 사용할 수 없다. shared에 Node 전용 코드를 넣으면 extension 빌드가 깨진다
정리
모노레포는 “멋있어서” 쓰는 게 아니다. 공유 코드가 있고, 두 앱이 같은 도메인 모델을 다루면 모노레포가 답이다. Turborepo + pnpm 조합은 설정이 간단하고, 빌드 캐시로 CI 시간도 줄어든다.