작은 프로젝트에서는 아무 구조나 괜찮지만, 팀이 커지고 기능이 늘어나면 아키텍처가 생산성을 결정합니다.

개념 정의

대규모 앱 설계 는 코드를 어떤 기준으로 분리하고, 모듈 간 의존성을 어떻게 관리할지에 대한 아키텍처 결정입니다. SvelteKit은 파일 기반 라우팅이 구조의 근간이 되므로, 이를 기반으로 확장해야 합니다.

권장 폴더 구조

PLAINTEXT
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 구조 (대안)

PLAINTEXT
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/

컴포넌트 설계 원칙

PLAINTEXT
컴포넌트를 3계층으로 나눕니다:

1. UI 컴포넌트 (Presentational)
   - 순수 표현. 비즈니스 로직 없음
   - Props로 데이터를 받고, 이벤트로 알림
   - 예: Button, Card, Modal, Input

2. Feature 컴포넌트
   - 특정 기능의 로직 포함
   - Store나 API와 연결
   - 예: LoginForm, PostEditor, UserProfile

3. Page 컴포넌트 (+page.svelte)
   - 라우트에 대응
   - Feature 컴포넌트를 조합
   - load 함수에서 데이터 수신

상태 관리 전략

PLAINTEXT
로컬 상태 ($state)
├── 컴포넌트 내부에서만 사용
├── 폼 입력, UI 토글 등
└── 가장 기본, 대부분 여기서 해결

공유 상태 (Context API)
├── 컴포넌트 트리 범위
├── 테마, 로케일, 기능 설정
└── 같은 컴포넌트의 다른 인스턴스가 다른 값

전역 상태 (Store / .svelte.js)
├── 앱 전체 공유
├── 인증, 알림, 전역 설정
└── 어디서든 import하여 사용

서버 상태 (load 함수)
├── 서버에서 가져오는 데이터
├── +page.server.js에서 관리
└── 자동 캐싱과 무효화

코드 공유 패턴

JAVASCRIPT
// 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';
SVELTE
<!-- 사용 측 -->
<script>
  import { Button, Card, Modal } from '$lib/components/ui';
</script>

에러 처리 전략

JAVASCRIPT
// 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계층 컴포넌트 분리와 적절한 상태 관리 전략을 조합하면 확장 가능한 아키텍처가 완성됩니다.

댓글 로딩 중...