🎨 4단계 : 개발 ν‘œμ€€ 및 ꡬ쑰 섀계 - 100-hours-a-week/7-team-ddb-wiki GitHub Wiki

λ¬Έμ„œ 관리

μž‘μ„±μΌ 2024-04-27
버전 1.0
μž‘μ„±μž suzy.kang (κ°•μˆ˜μ§€)
κ²€ν† μž kevin
μƒνƒœ μ΄ˆμ•ˆ

λ³€κ²½ 이λ ₯

버전 λ‚ μ§œ μž‘μ„±μž κ²€ν† μž λ³€κ²½λ‚΄μš©

1. 폴더 ꡬ쑰

1.1 Feature-Sliced Design (FSD) ꡬ쑰

src/
β”œβ”€β”€ app/ # Next.js App Router
β”œβ”€β”€ pages/ # Next.js Pages Router
β”œβ”€β”€ shared/ # ν”„λ‘œμ νŠΈ μ „μ—­ 곡톡 λͺ¨λ“ˆ
β”‚ β”œβ”€β”€ api/ # 곡톡 API ν΄λΌμ΄μ–ΈνŠΈ/ν•¨μˆ˜
β”‚ β”œβ”€β”€ config/ # ν™˜κ²½μ„€μ •, 라우트, SEO λ“±
β”‚ β”œβ”€β”€ constants/ # μƒμˆ˜ μ •μ˜
β”‚ β”œβ”€β”€ lib/ # λ²”μš© μœ ν‹Έλ¦¬ν‹° ν•¨μˆ˜
β”‚ β”œβ”€β”€ types/ # μ „μ—­ νƒ€μž…/μΈν„°νŽ˜μ΄μŠ€
β”‚ └── components/ # 곡톡 UI μ»΄ν¬λ„ŒνŠΈ
β”œβ”€β”€ entities/ # λΉ„μ¦ˆλ‹ˆμŠ€ μ—”ν‹°ν‹°
β”‚ └── (entity)/ # 예: user, place, moment
β”‚ β”œβ”€β”€ model/ # μ—”ν‹°ν‹° μƒνƒœ, store, λΉ„μ¦ˆλ‹ˆμŠ€ 둜직
β”‚ β”œβ”€β”€ api/ # μ—”ν‹°ν‹° μ „μš© API
β”‚ β”œβ”€β”€ types/ # μ—”ν‹°ν‹° νƒ€μž…
β”‚ └── components/ # μ—”ν‹°ν‹° μ „μš© μ»΄ν¬λ„ŒνŠΈ
β”œβ”€β”€ features/ # κΈ°λŠ₯ λ‹¨μœ„ 폴더
β”‚ β”œβ”€β”€ user/
β”‚ β”‚ β”œβ”€β”€ components/ # 인증/μœ μ € κ΄€λ ¨ μ»΄ν¬λ„ŒνŠΈ
β”‚ β”‚ β”œβ”€β”€ hooks/ # 인증/μœ μ € κ΄€λ ¨ μ»€μŠ€ν…€ ν›…
β”‚ β”‚ β”œβ”€β”€ model/ # 인증/μœ μ € κ΄€λ ¨ μƒνƒœ, zustand store λ“±
β”‚ β”‚ β”œβ”€β”€ api/ # 인증/μœ μ € κ΄€λ ¨ API ν•¨μˆ˜
β”‚ β”‚ β”œβ”€β”€ types/ # 인증/μœ μ € κ΄€λ ¨ νƒ€μž…
β”‚ β”‚ └── index.ts
β”‚ β”œβ”€β”€ place/
β”‚ β”œβ”€β”€ community/
β”‚ └── onboarding/

  • shared/components: Button, Input, Modal, Spinner, Toast λ“± μ–΄λ””μ„œλ‚˜ μž¬μ‚¬μš©λ˜λŠ” UI
  • entities/(entity)/components: μ—”ν‹°ν‹°(예: user, place) μ „μš© μ»΄ν¬λ„ŒνŠΈ
  • features/(feature)/components: κΈ°λŠ₯(예: user, place) μ „μš© μ»΄ν¬λ„ŒνŠΈ

2. λ°˜μ‘ν˜• λ””μžμΈ 지원 μ „λž΅

  • λͺ¨λ°”일 μš°μ„ (Mobile First) μ ‘κ·Ό

    κΈ°λ³Έ λ·°ν¬νŠΈλŠ” 375px, λͺ¨λ°”일 κΈ°μ€€μœΌλ‘œ UI 섀계

    Tailwind CSS의 λͺ¨λ°”일 μš°μ„  클래슀 적극 ν™œμš©

  • μ›Ήμ—μ„œλ„ λͺ¨λ°”일 μ‚¬μ΄μ¦ˆλ‘œ κ³ μ •

    PC ν™˜κ²½μ—μ„œλ„ λͺ¨λ°”일 λ·°(375px)둜 κ³ μ •, 쀑앙 μ •λ ¬

  • rem/em λ‹¨μœ„ μ‚¬μš©

    폰트, μ—¬λ°± 등은 rem/em λ‹¨μœ„λ‘œ 톡일

  • 이미지/μ•„μ΄μ½˜ λ°˜μ‘ν˜•

    Next.js Image μ»΄ν¬λ„ŒνŠΈ, SVG μ•„μ΄μ½˜ μ‚¬μš©


3. μ½”λ“œ μ»¨λ²€μ…˜

  • 넀이밍

    • μ»΄ν¬λ„ŒνŠΈ/νƒ€μž…/μΈν„°νŽ˜μ΄μŠ€: PascalCase
    • ν•¨μˆ˜/λ³€μˆ˜/ν›…: camelCase
    • μƒμˆ˜: UPPER_SNAKE_CASE
    • 파일/폴더: kebab-case
  • μ»΄ν¬λ„ŒνŠΈ

    • ν•¨μˆ˜ν˜• μ»΄ν¬λ„ŒνŠΈ, React.FC μ‚¬μš© μ§€μ–‘
    • props, state, hooks, handler, render μˆœμ„œ
  • νƒ€μž…

    • interfaceλŠ” 객체 ν˜•νƒœμ˜ νƒ€μž…, ν™•μž₯ κ°€λŠ₯성이 μžˆλŠ” 경우 μ‚¬μš©
    • type은 μœ λ‹ˆμ˜¨ νƒ€μž…, ꡐ차 νƒ€μž…, κΈ°λ³Έ νƒ€μž…μ˜ 별칭에 μ‚¬μš©
    • νƒ€μž…/μΈν„°νŽ˜μ΄μŠ€λŠ” types.ts둜 뢄리
    // interface μ‚¬μš© μ˜ˆμ‹œ (객체 ν˜•νƒœ, ν™•μž₯ κ°€λŠ₯)
    interface ButtonProps {
      variant?: 'primary' | 'secondary';
      size?: 'sm' | 'md' | 'lg';
      onClick?: () => void;
    }
    
    // type μ‚¬μš© μ˜ˆμ‹œ (μœ λ‹ˆμ˜¨ νƒ€μž…, 별칭)
    type ButtonVariant = 'primary' | 'secondary' | 'tertiary';
    type Size = 'sm' | 'md' | 'lg';
    
    
  • 주석

    • ν•¨μˆ˜/μ»΄ν¬λ„ŒνŠΈ 상단 JSDoc μ‚¬μš©
    • λ³΅μž‘ν•œ λ‘œμ§μ—λ§Œ 인라인 주석 μΆ”κ°€
    • μ‚¬μ†Œν•˜κ±°λ‚˜ 자λͺ…ν•œ μ½”λ“œμ—λŠ” 주석 μ‚¬μš© μ§€μ–‘
    • 주석보닀 λͺ…ν™•ν•œ λ³€μˆ˜λͺ…κ³Ό ν•¨μˆ˜λͺ…μœΌλ‘œ μ½”λ“œ μžμ²΄κ°€ μ„€λͺ…λ˜λ„λ‘ μž‘μ„±
    // 쒋은 μ˜ˆμ‹œ
    /**
     * λ‚ μ§œλ₯Ό YYYY-MM-DD ν˜•μ‹μœΌλ‘œ λ³€ν™˜
     * @param date - λ³€ν™˜ν•  λ‚ μ§œ 객체
     * @returns 포맷된 λ‚ μ§œ λ¬Έμžμ—΄
     */
    const formatDate = (date: Date): string => {
      // 둜컬 μ‹œκ°„λŒ€λ‘œ λ³€ν™˜ (ꡭ제 ν‘œμ€€μ‹œμ™€ 차이가 μžˆμ„ 수 있음)
      return date.toISOString().split('T')[0];
    };
    
    // μ§€μ–‘ν•  μ˜ˆμ‹œ
    // 이름을 λ°˜ν™˜ν•˜λŠ” ν•¨μˆ˜
    const getName = () => name; // λΆˆν•„μš”ν•œ 주석
    
    
  • μ½”λ”© νŒ¨ν„΄

    • μ»΄ν¬λ„ŒνŠΈλŠ” export function λ°©μ‹μœΌλ‘œ μž‘μ„±

      // μ˜ˆμ‹œ: shared/components/Button/Button.tsx
      export function Button(props: ButtonProps) {
        // ...
      }
      
      
    • 일반 κΈ°λŠ₯ ν•¨μˆ˜(μœ ν‹Έ, ν›… λ“±)λŠ” ν™”μ‚΄ν‘œ ν•¨μˆ˜λ‘œ μž‘μ„±

      // μ˜ˆμ‹œ: shared/lib/formatDate.ts
      export const formatDate = (date: Date) => {
        // ...
      };
      
      
    • 폴더별 index.tsμ—μ„œ exportν•˜μ—¬ import 경둜λ₯Ό λ‹¨μˆœν™”

      // μ˜ˆμ‹œ: shared/components/Button/index.ts
      export * from './Button';
      // μ‚¬μš©: import { Button } from 'shared/components';
      
      

4. Lint & Formatting

  • ESLint
    • next/core-web-vitals, prettier, import/order λ“± ν™•μž₯
    • import μˆœμ„œ: builtin β†’ external β†’ internal β†’ parent β†’ sibling β†’ index, μ•ŒνŒŒλ²³μˆœ, 그룹별 ν•œ 쀄 띄움
  • Prettier
    • μ„Έλ―Έμ½œλ‘  μ‚¬μš©, μ‹±κΈ€ 쿼트, 2μΉΈ λ“€μ—¬μ“°κΈ°, 80자 μ€„λ°”κΏˆ, trailing comma
  • μžλ™ν™”
    • husky + lint-staged둜 컀밋 μ „ lint/format μžλ™ μ‹€ν–‰

5. Git μ „λž΅

  • 브랜치
    • main: 배포/운영
    • dev: 톡합 개발
    • feat/xxx: κΈ°λŠ₯ 개발
  • 컀밋 λ©”μ‹œμ§€
    • feat:, fix:, chore:, style:, refactor:, test:, docs: λ“± prefix μ‚¬μš©
    • μ˜ˆμ‹œ: feat: 둜그인 API 연동, fix: ν”„λ‘œν•„ 이미지 버그 μˆ˜μ •
    • 컀밋은 μ΅œλŒ€ν•œ μž‘μ€ λ‹¨μœ„λ‘œ μͺΌκ°œμ„œ μž‘μ„± (κΈ°λŠ₯, 버그 μˆ˜μ • λ“± 단일 λͺ©μ )
    • ν•˜λ‚˜μ˜ 컀밋에 μ—¬λŸ¬ κΈ°λŠ₯μ΄λ‚˜ μˆ˜μ •μ‚¬ν•­μ„ ν¬ν•¨ν•˜μ§€ μ•ŠμŒ
  • PR κ·œμΉ™
    • dev β†’ main, feat/xxx β†’ dev둜만 λ¨Έμ§€
    • PR ν…œν”Œλ¦Ώ μ‚¬μš©
      • PR 제λͺ© ν˜•μ‹: [feat] 둜그인 κΈ°λŠ₯ κ΅¬ν˜„, [fix] ν”„λ‘œν•„ 이미지 λ‘œλ”© 버그 μˆ˜μ •

      • 제λͺ©μ€ [νƒ€μž…] λ‚΄μš© ν˜•μ‹μœΌλ‘œ μΌκ΄€λ˜κ²Œ μž‘μ„±

      • PR ν…œν”Œλ¦Ώ μ˜ˆμ‹œ:

        ## πŸ“ PR κ°œμš”
        <!-- 이 PR이 ν•΄κ²°ν•˜λŠ” λ¬Έμ œλ‚˜ μΆ”κ°€ν•˜λŠ” κΈ°λŠ₯에 λŒ€ν•œ κ°„λž΅ν•œ μ„€λͺ… -->
        
        ## πŸ” 변경사항
        <!-- μ£Όμš” 변경사항 λͺ©λ‘ (뢈릿 포인트) -->
        -
        -
        -
        ## πŸ”— κ΄€λ ¨ 이슈
        <!-- κ΄€λ ¨λœ 이슈 링크 (e.g. Closes #123) -->
        
        
        ## πŸ“Έ μŠ€ν¬λ¦°μƒ· (Optional)
        <!-- UI 변경사항이 μžˆκ±°λ‚˜ ν•„μš”ν•œ 경우 μŠ€ν¬λ¦°μƒ· μΆ”κ°€ -->
        
        ## πŸ§ͺ ν…ŒμŠ€νŠΈ (Optional)
        <!-- ν…ŒμŠ€νŠΈ 방법 및 κ²°κ³Ό -->
        - [ ] ν…ŒμŠ€νŠΈ 1
        - [ ] ν…ŒμŠ€νŠΈ 2
        
        ## 🚨 μ£Όμ˜μ‚¬ν•­ (Optional)
        <!-- 리뷰어가 μ•Œμ•„μ•Ό ν•  μ£Όμ˜μ‚¬ν•­μ΄λ‚˜ 고렀사항 -->
        
        
        

6. λΈŒλΌμš°μ € ν˜Έν™˜μ„±

  • 지원 λΈŒλΌμš°μ €
    • Chrome, Safari, Firefox, Edge (각 μ΅œμ‹  2버전)
    • λͺ¨λ°”일: iOS Safari, Android Chrome
  • 폴리필 μ „λž΅
    • Next.js λ‚΄μž₯ 폴리필 ν™œμš© (fetch, Promise, Object.assign λ“±)

7. SEO μ „λž΅

  • 메타 νƒœκ·Έ

    • title, description, og/meta νƒœκ·Έ 각 νŽ˜μ΄μ§€λ³„ 동적 관리
    • Next.js Head μ»΄ν¬λ„ŒνŠΈ ν™œμš©
    • μž₯μ†Œ λ°μ΄ν„°μ˜ 이름, μ£Όμ†Œ, μΉ΄ν…Œκ³ λ¦¬ 등을 meta description에 포함
    • Open Graph μ΄λ―Έμ§€λ‘œ μž₯μ†Œ/기둝 λŒ€ν‘œ 이미지 ν™œμš©
  • κ΅¬μ‘°ν™”λœ 데이터 λ§ˆν¬μ—…

    • μž₯μ†Œ μ •λ³΄λŠ” [Schema.org](http://schema.org/) LocalBusiness νƒ€μž…μœΌλ‘œ λ§ˆν¬μ—…

    • 기둝/λͺ¨λ¨ΌνŠΈλŠ” Article λ˜λŠ” BlogPosting νƒ€μž…μœΌλ‘œ λ§ˆν¬μ—…

    • JSON-LD ν˜•μ‹ ν™œμš©:

      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{
          __html: JSON.stringify({
            "@context": "<https://schema.org>",
            "@type": "LocalBusiness",
            "name": place.name,
            "address": {
              "@type": "PostalAddress",
              "streetAddress": place.address
            },
            "geo": {
              "@type": "GeoCoordinates",
              "latitude": place.latitude,
              "longitude": place.longitude
            },
            "image": place.images[0]
          })
        }}
      />
      
      
  • SSR/SSG/ISR

    • μž₯μ†Œ 검색 κ²°κ³Ό νŽ˜μ΄μ§€λŠ” SSR둜 κ΅¬ν˜„ν•˜μ—¬ 검색 ν‚€μ›Œλ“œ 기반 SEO μ΅œμ ν™”
    • μž₯μ†Œ 상세 νŽ˜μ΄μ§€λ„ SSR둜 κ΅¬ν˜„ν•˜μ—¬ μ΅œμ‹  데이터λ₯Ό 검색 엔진에 λ…ΈμΆœ
    • 기둝 νŽ˜μ΄μ§€λ„ SSR 적용으둜 λ‚΄μš© 기반 검색 λ…ΈμΆœ κ°•ν™”
    • Next.js의 getServerSideProps ν™œμš©ν•˜μ—¬ μ„œλ²„ μ‚¬μ΄λ“œμ—μ„œ 데이터 페칭
  • μž₯μ†Œ/기둝 데이터 SEO μ΅œμ ν™”

    • URL ꡬ쑰에 μž₯μ†Œλͺ…/μΉ΄ν…Œκ³ λ¦¬ 포함 (예: /place/[category]/[slug])
    • μž₯μ†Œ ν‚€μ›Œλ“œ, μ§€μ—­λͺ… λ“± 검색 μ΅œμ ν™”λœ μ½˜ν…μΈ  ꡬ성
    • κ΄€λ ¨ μž₯μ†Œμ™€ 기둝을 λ‚΄λΆ€ 링크둜 μ—°κ²°ν•˜μ—¬ 검색 μ—”μ§„ 크둀링 κ°œμ„ 
    • 이미지에 alt νƒœκ·Έ 및 파일λͺ…에 ν‚€μ›Œλ“œ 포함
  • robots.txt, sitemap.xml

    • 검색엔진 μ΅œμ ν™” 파일 제곡
    • 동적 sitemap μƒμ„±μœΌλ‘œ λͺ¨λ“  μž₯μ†Œμ™€ 기둝 νŽ˜μ΄μ§€ 포함
    • μ£Όμš” μΉ΄ν…Œκ³ λ¦¬λ³„ sitemap 뢄리

8. ν…ŒμŠ€νŠΈ μ „λž΅

  • λ‹¨μœ„ ν…ŒμŠ€νŠΈ: Jest
  • E2E ν…ŒμŠ€νŠΈ: Cypress
  • ν…ŒμŠ€νŠΈ 파일 μœ„μΉ˜: 동일 폴더 λ‚΄ .test.ts
  • ν…ŒμŠ€νŠΈ 컀버리지:
    • μ£Όμš” 둜직, 핡심 μ»΄ν¬λ„ŒνŠΈ, API 연동 μ€‘μ‹¬μœΌλ‘œ ν…ŒμŠ€νŠΈ μž‘μ„±
    • λΉ„μ¦ˆλ‹ˆμŠ€ 둜직이 λ³΅μž‘ν•œ μœ ν‹Έλ¦¬ν‹° ν•¨μˆ˜ μš°μ„  ν…ŒμŠ€νŠΈ
    • λ‹¨μˆœ UI λ Œλ”λ§μ΄λ‚˜ μ‚¬μ†Œν•œ 헬퍼 ν•¨μˆ˜λŠ” ν…ŒμŠ€νŠΈ μž‘μ„± μ§€μ–‘

9. μ„±λŠ₯ μ΅œμ ν™”

  • 이미지 μ΅œμ ν™”: Next.js Image, WebP, lazy loading
  • μ½”λ“œ μŠ€ν”Œλ¦¬νŒ…: 동적 import, ν•„μš”μ‹œ React.lazy
  • λ²ˆλ“€ 크기 관리: shadcn/ui, ν•„μš”ν•œ μ»΄ν¬λ„ŒνŠΈλ§Œ import
  • 지도/μ™ΈλΆ€ SDK: lazy load, Suspense ν™œμš©

10. μ ‘κ·Όμ„±

  • ARIA 속성: λ²„νŠΌ, μž…λ ₯ λ“± μ£Όμš” UI에 ARIA 속성 λΆ€μ—¬
  • ν‚€λ³΄λ“œ λ„€λΉ„κ²Œμ΄μ…˜: Tab, Enter, Space λ“± ν‚€λ³΄λ“œ μ ‘κ·Όμ„± 보μž₯

11. 기타

  • μƒνƒœ 관리: zustand, store 뢄리, persist μ‚¬μš©μ‹œ 민감정보 주의
  • μœ νš¨μ„± 검사: zod, μ„œλ²„-ν΄λΌμ΄μ–ΈνŠΈ μŠ€ν‚€λ§ˆ 곡유
  • API 연동: fetch, TanStack Query, use() ν˜Όν•©, μ—λŸ¬/λ‘œλ”© 처리 일관성
  • μŠ€ν† λ¦¬λΆ:
    • UI μ»΄ν¬λ„ŒνŠΈ λ¬Έμ„œν™”, λ””μžμΈ μ‹œμŠ€ν…œ 일관성 μœ μ§€
    • μŠ€ν† λ¦¬ νŒŒμΌμ€ ν•΄λ‹Ή μ»΄ν¬λ„ŒνŠΈ 폴더 내에 μœ„μΉ˜
    • 파일λͺ… κ·œμΉ™: μ»΄ν¬λ„ŒνŠΈλͺ….stories.tsx