μ΄ νλ‘μ νΈλ 3λ μ μ κ°μΈμ μΌλ‘ μ§ννλ λλ€ μμ μΆμ² μμ΄λμ΄λ₯Ό κΈ°λ°μΌλ‘ μλ‘κ² λ¦¬λΉλν λ²μ μ λλ€.
μ΄κΈ° νλ‘μ νΈλͺ μ 'Music Roulette'μ΄μμΌλ, 리λ΄μΌνλ©΄μ νλ‘μ νΈμ 컨μ μ λνλΌ μ μλ 'OneSong'μΌλ‘ λ³κ²½νμμ΅λλ€.
μ΄κΈ° λ²μ μ React
, CSS Module
, Shazam API
λ₯Ό κΈ°λ°μΌλ‘ κ°λ°λμμΌλ,
νμ¬λ Next.js
μ TailwindCSS
λ₯Ό νμ©νμ¬ μ 체 ꡬ쑰μ UXλ₯Ό λλμ μΌλ‘ κ°μ νμμ΅λλ€.
λν κΈ°μ‘΄μ μ¬μ©νλ Shazam API
μ μ£Όμ κΈ°λ₯μ΄ λλΆλΆ deprecatedλμ΄,
μλ‘μ΄ λ°μ΄ν° μμ€λ₯Ό κΈ°λ°μΌλ‘ νλ‘μ νΈλ₯Ό μ¬κ΅¬μ±νκ² λμμ΅λλ€.
Next.js
(App Router κΈ°λ°)TypeScript
TailwindCSS
Zustand
Jest
MSW
π νλ‘μ νΈ λ£¨νΈ
βββ __tests__/ # ν
μ€νΈ ν΄λ (srcμ λμΌν ꡬ쑰)
βββ public/ # μ μ νμΌ (favicon, manifest λ±)
βββ src/ # μ ν리μΌμ΄μ
μμ€ μ½λ
β βββ app/ # Next.js μ± λΌμ°ν
ꡬ쑰
β β βββ (with-audio) # μ€λμ€ νλ μ΄μ΄κ° κ³ μ λ νμ΄μ§ λ μ΄μμ κ·Έλ£Ή
β β βββ api # Next.js API λΌμ°νΈ (μλ² ν¨μ κΈ°λ° νΈλ€λ¬)
β β βββ assets # μ± μ μ μμ°
β βββ components/ # μ¬μ¬μ© κ°λ₯ν UI λ° κΈ°λ₯ μ»΄ν¬λνΈ
β β βββ common/ # κ³΅ν΅ μ»΄ν¬λνΈ (AudioPlayer λ±)
β β βββ country/ # λ©μΈ νμ΄μ§ κ΄λ ¨ μ»΄ν¬λνΈ
β β βββ guest/ # λΉνμ μ μ© νμ΄μ§ κ΄λ ¨ μ»΄ν¬λνΈ
β β βββ icons/ # νλ‘λ°μ΄λ μμ΄μ½ μ»΄ν¬λνΈ
β β βββ layouts/ # λ μ΄μμ κ΄λ ¨ μ»΄ν¬λνΈ
β β βββ liked-songs/ # μ°ν λ
Έλ νμ΄μ§ κ΄λ ¨ μ»΄ν¬λνΈ
β β βββ me/ # νμ μ μ© νμ΄μ§ κ΄λ ¨ μ»΄ν¬λνΈ
β β βββ ui/ # shadcn/ui κΈ°λ° UI μ»΄ν¬λνΈ λνΌ
β βββ constants/ # μμκ° μ μ (κ΅κ° μ½λ λ±)
β βββ hooks/ # 컀μ€ν
ν
μ μ
β βββ lib/ # μ νΈ ν¨μ λ° API fetch λ‘μ§
β βββ providers # νλ‘λ°μ΄λ λνΌ μ»΄ν¬λνΈ
β βββ stores/ # Zustand μν κ΄λ¦¬ μ€ν μ΄
β βββ types/ # νμ
μ μ
β βββ auth.ts # Auth.js νκ²½ μ€μ
β βββ middleware.ts # Next.js λ―Έλ€μ¨μ΄
βββ jest.config.ts # Jest μ€μ
βββ tsconfig.json # TypeScript μ€μ
βββ README.md
βββ package.json
-
μ μ μν
- ν΄λΉ μνλ₯Ό μλΉνλ μ»΄ν¬λνΈμμ μ§μ λΆλ¬μμ μ¬μ©
- μ μ μνλ₯Ό
props
λ‘ μ λ¬νλ λ°©μμ μ§μ
-
μ§μ μν
- ν΄λΉ μνλ₯Ό νμλ‘ νλ μ»΄ν¬λνΈμ
props
λ‘ μ λ¬νμ¬ μ¬μ© - μ»΄ν¬λνΈ λμ€κ° 3λ¨κ³ μ΄μ λμ΄κ° κ²½μ° μ μ μνλ‘ μΉκ²© κ³ λ €
- ν΄λΉ μνλ₯Ό νμλ‘ νλ μ»΄ν¬λνΈμ
- μΌλΆ κ΅κ°λ³ λλ€ κ³‘ μΆμ²
- Apple Music RSS κΈ°λ° λ―Έλ¦¬λ£κΈ° μ§μ
- 곑 μ λͺ©, μν°μ€νΈλͺ , μ¨λ²μ»€λ², μ¨λ²λͺ μ 보 μ 곡
- λΉνμ μ°ν λ Έλ λͺ¨μ보기 (μ μ μν κ΄λ¦¬ + λ‘컬μ€ν λ¦¬μ§ μ°λ)
- μ€λμ€ νλ μ΄μ΄ 컀μ€ν°λ§μ΄μ§ (μ¬μ / μΌμμ μ§ / μ μ§ / μ¬μ μμΉ νμ)
- κ΅κ° μ ν λλ‘λ€μ΄ λ©λ΄
- μ΅κ·Ό μΆμ² κΈ°λ‘ μ μ₯
- νμ κΈ°λ₯ λμ
λ° κ°λ³ μ°ν λ
Έλ λͺ¨μ보기 (
Auth.js
+Supabase DB
) - ν΅μ¬ λ‘μ§ μ λ ν μ€νΈ λ° λ λλ§ ν μ€νΈ
- μν°μ€νΈ / μ¨λ² μμΈ νμ΄μ§ ꡬν (νμ¬λ μΈλΆ λ§ν¬ μ²λ¦¬λ‘ λ체)
- λ€κ΅μ΄ λμ
- ν μ€νΈ μ½λ λ° λ°°ν¬ μλν
- νλ μμν¬:
Jest
,MSW
- ν
μ€νΈ λμ
- μ νΈ ν¨μ
- 컀μ€ν ν
Zustand
μ μ μ€ν μ΄- μΈλΆ API ν¨μ (MSWλ₯Ό νμ©ν API λͺ¨νΉ ν μ€νΈ ν¬ν¨)
- ν₯ν μ μ§μ μΌλ‘ ν΅ν© ν μ€νΈ -> E2E ν μ€νΈλ‘ νμ₯ μμ
-
1. μ€λμ€ νλ μ΄μ΄ μ»΄ν¬λνΈ: μ¬μ μ€ κ³Όλν 리λ λλ§ λ°μ (2025.05.05)
- μμΈ
- Progressκ° λκΈ°λ― μμ§μ¬
requestAnimationFrame
μ μ μ©ν΄μ UIλ κ°μ λμμΌλcurrentTime
μνλ₯Ό λ§€ νλ μ μ λ°μ΄νΈνμ¬ λΆνμν 리λ λλ§μ΄ κ³Όλνκ² λ°μ
- Progressκ° λκΈ°λ― μμ§μ¬
- ν΄κ²° λ°©λ²
currentTime
μ refλ‘ κ΄λ¦¬νμ¬ μ€μ μ¬μ μκ° μΆμ μ DOM κΈ°λ°μΌλ‘ μ²λ¦¬- νλ©΄μ νμνλ μ¬μ μκ°μΈ
displayTime
μ 0.1μ΄ λ¨μλ‘ μν μ λ°μ΄νΈνμ¬ λ¦¬λ λ λΉλλ₯Ό μ μ΄ AudioPlayer
λ΄λΆ μ½λ°± ν¨μλuseCallback
μΌλ‘ κ΄λ¦¬νκ³ , νμ μ»΄ν¬λνΈλReact.memo
λ‘ μ²λ¦¬νμ¬ λΆνμν 리λ λλ§ μ΅μν
- κ²°κ³Ό
- μ΅μ ν μ : 1802ν 리λ λλ§
- μ΅μ ν ν: 262ν 리λ λλ§
- μ½ 85.5% 리λ λλ§ κ°μ
- μ¬μ μν κ΄λ ¨ UIλ§ λ¦¬λ λλ§λλλ‘ κ°μ
- μμΈ
-
2. 첫 λ‘κ·ΈμΈ μ, μ΄λ―Έ μ‘΄μ¬νλ μ΄λ©μΌλ‘ μΈμλμ΄ νμκ°μ μ²λ¦¬κ° λμ§ μμ (2025.05.07)
-
μ΄κΈ°μ μΆμ ν μμΈ
- λ‘κ·ΈμΈ μ
/
νμ΄μ§μμ λ€μ/country/[code]
λΌμ°ν°λ‘ 리λ€μ΄λ νΈ λλ©΄μAuthUserInitializer
κ° λλ² μ€νλμ΄μcreateUser
λ©μλκ° λ λ² νΈμΆλ¨ - μ΄λ‘ μΈν΄ μ€λ³΅ νμ μμ± μλλ‘ μΈν
insert
μ€ν¨λ‘ μΆμ
- λ‘κ·ΈμΈ μ
-
μ€μ μμΈ
AuthUserInitializer
λ΄ μ¬μ΄λ μ΄ννΈκ° λκ°λ‘ λΆλ¦¬λμ΄ μμ-
- μ΄λ©μΌ μ€λ³΅ νμΈ -> μμΌλ©΄
insert
- μ΄λ©μΌ μ€λ³΅ νμΈ -> μμΌλ©΄
-
- μ°ν λ Έλ λͺ©λ‘μ localStorage -> Supabase DBλ‘ μ΄κ΄ + μν νλκ·Έ μ λ°μ΄νΈ
-
- λΉλκΈ° μ€ν μμκ° λ³΄μ₯λμ§ μμ
insert
->select
μselect
->insert
λ μ΄μ€ λ°μνμ¬ νμ΄λ°μ λ°λΌinsert
μμ μ€λ³΅ν€ μλ¬ λ°μ
-
ν΄κ²° λ°©λ²
insert
λμupsert
λ‘ λ³κ²½νμ¬ μ΄λ©μΌμ΄ μ‘΄μ¬νλ©΄ λμ΄κ°κ³ μμΌλ©΄ μλ‘ μ½μ νμ¬ κ΅¬μ‘° μμ μ± ν보
const { data, error } = await supabase .from('users') .upsert( [ { email, name: name ?? null, avatar_url: image ?? null, provider, }, ], { onConflict: 'email', }, ) .select('id') .single()
-
-
3. Production νκ²½μμ
getToken()
μ΄ νμnull
μ λ°ννμ¬ νλΌμ΄λΉ λΌμ°ν° μλ μν¨ (2025.05.08)- μμΈ
- Auth.js + App Router νλ‘μ νΈμμ
cookieName
μ΄ λ³΄μ μμ μ΄μ λ‘__Secure-authjs.session-token
μΌλ‘ λ³κ²½λ¨ getToken()
μ κΈ°λ³Έμ μΌλ‘next-auth.session-token
λ§ μΈμ- μΏ ν€λ λΈλΌμ°μ μ μ‘΄μ¬νμ§λ§,
getToken()
μ΄cookieName
μ μ°Ύμ§ λͺ»νμ¬null
μ λ°ν
- Auth.js + App Router νλ‘μ νΈμμ
- ν΄κ²° λ°©λ²
src/moddileware.ts
λ΄getToken()
νΈμΆ μcookieName
μ λͺ μμ μΌλ‘ μ§μ
const token = await getToken({ req: request, secret: process.env.AUTH_SECRET, cookieName: '__Secure-authjs.session-token', })
- μμΈ
main
β 리λ΄μΌλ λ²μ legacy/main
β λ κ±°μ λ²μ (λ°°ν¬ μ€λ¨, μ½λλ§ νμΈ κ°λ₯)