π¨ 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 νκ·Έ λ° νμΌλͺ μ ν€μλ ν¬ν¨
- URL ꡬ쑰μ μ₯μλͺ
/μΉ΄ν
κ³ λ¦¬ ν¬ν¨ (μ:
-
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