대규모 Svelte 앱 설계 — 폴더 구조와 아키텍처 패턴
작은 프로젝트에서는 아무 구조나 괜찮지만, 팀이 커지고 기능이 늘어나면 아키텍처가 생산성을 결정합니다.
개념 정의
대규모 앱 설계 는 코드를 어떤 기준으로 분리하고, 모듈 간 의존성을 어떻게 관리할지에 대한 아키텍처 결정입니다. SvelteKit은 파일 기반 라우팅이 구조의 근간이 되므로, 이를 기반으로 확장해야 합니다.
권장 폴더 구조
src/
├── routes/ # 라우팅 (SvelteKit 규칙)
│ ├── (marketing)/ # 마케팅 페이지 그룹
│ │ ├── +layout.svelte
│ │ └── pricing/
│ ├── (app)/ # 앱 페이지 그룹
│ │ ├── +layout.svelte
│ │ ├── dashboard/
│ │ └── settings/
│ └── api/ # API 라우트
│ └── v1/
│
├── lib/ # 공유 코드 ($lib)
│ ├── components/ # UI 컴포넌트
│ │ ├── ui/ # 범용 (Button, Modal, Input)
│ │ ├── layout/ # 레이아웃 (Header, Sidebar)
│ │ └── features/ # 기능별 (UserCard, PostEditor)
│ │
│ ├── server/ # 서버 전용 코드
│ │ ├── db.js # 데이터베이스 클라이언트
│ │ ├── auth.js # 인증 로직
│ │ └── email.js # 이메일 발송
│ │
│ ├── stores/ # 전역 상태
│ │ ├── auth.js
│ │ ├── theme.js
│ │ └── notifications.js
│ │
│ ├── utils/ # 유틸리티 함수
│ │ ├── format.js
│ │ ├── validation.js
│ │ └── api.js
│ │
│ ├── types/ # TypeScript 타입 정의
│ │ └── index.ts
│ │
│ └── constants/ # 상수
│ └── index.js
│
├── app.html
├── app.css
├── app.d.ts
└── hooks.server.js
Feature-based 구조 (대안)
src/lib/
├── features/
│ ├── auth/
│ │ ├── components/ # 인증 관련 컴포넌트
│ │ │ ├── LoginForm.svelte
│ │ │ └── RegisterForm.svelte
│ │ ├── stores/ # 인증 상태
│ │ │ └── authStore.js
│ │ ├── utils/ # 인증 유틸
│ │ │ └── validation.js
│ │ └── index.js # 공개 API (re-export)
│ │
│ ├── blog/
│ │ ├── components/
│ │ ├── stores/
│ │ └── index.js
│ │
│ └── shared/ # 공통 기능
│ ├── components/
│ └── utils/
컴포넌트 설계 원칙
컴포넌트를 3계층으로 나눕니다:
1. UI 컴포넌트 (Presentational)
- 순수 표현. 비즈니스 로직 없음
- Props로 데이터를 받고, 이벤트로 알림
- 예: Button, Card, Modal, Input
2. Feature 컴포넌트
- 특정 기능의 로직 포함
- Store나 API와 연결
- 예: LoginForm, PostEditor, UserProfile
3. Page 컴포넌트 (+page.svelte)
- 라우트에 대응
- Feature 컴포넌트를 조합
- load 함수에서 데이터 수신
상태 관리 전략
로컬 상태 ($state)
├── 컴포넌트 내부에서만 사용
├── 폼 입력, UI 토글 등
└── 가장 기본, 대부분 여기서 해결
공유 상태 (Context API)
├── 컴포넌트 트리 범위
├── 테마, 로케일, 기능 설정
└── 같은 컴포넌트의 다른 인스턴스가 다른 값
전역 상태 (Store / .svelte.js)
├── 앱 전체 공유
├── 인증, 알림, 전역 설정
└── 어디서든 import하여 사용
서버 상태 (load 함수)
├── 서버에서 가져오는 데이터
├── +page.server.js에서 관리
└── 자동 캐싱과 무효화
코드 공유 패턴
// src/lib/components/ui/index.js — 배럴 파일로 깔끔한 import
export { default as Button } from './Button.svelte';
export { default as Input } from './Input.svelte';
export { default as Modal } from './Modal.svelte';
export { default as Card } from './Card.svelte';
<!-- 사용 측 -->
<script>
import { Button, Card, Modal } from '$lib/components/ui';
</script>
에러 처리 전략
// src/lib/utils/errors.js
export class AppError extends Error {
constructor(message, code, statusCode = 500) {
super(message);
this.code = code;
this.statusCode = statusCode;
}
}
export class NotFoundError extends AppError {
constructor(resource) {
super(`${resource}을(를) 찾을 수 없습니다`, 'NOT_FOUND', 404);
}
}
export class UnauthorizedError extends AppError {
constructor() {
super('인증이 필요합니다', 'UNAUTHORIZED', 401);
}
}
면접 포인트
- "대규모 Svelte 앱의 가장 큰 도전은?": 상태 관리의 복잡성, 코드 분할 전략, 팀 간 일관된 패턴 유지입니다. SvelteKit의 파일 기반 라우팅이 구조를 강제하는 장점이 있지만, lib 디렉토리의 조직화는 팀 컨벤션에 달려 있습니다.
- "Layer 기반 vs Feature 기반 구조?": 소규모 프로젝트는 Layer(components/, utils/, stores/)가 직관적이고, 대규모 프로젝트는 Feature(auth/, blog/, payment/) 기반이 모듈 독립성에 유리합니다.
정리
대규모 앱 설계의 핵심은 "관련된 코드를 가까이, 무관한 코드를 멀리" 두는 것입니다. SvelteKit의 라우팅 구조를 기본으로, UI/Feature/Page 3계층 컴포넌트 분리와 적절한 상태 관리 전략을 조합하면 확장 가능한 아키텍처가 완성됩니다.
댓글 로딩 중...