μ΄ˆκΈ°μ—” 쉽고 λΉ λ₯΄κ²Œ 데이터λ₯Ό κ΄€λ¦¬ν•˜λ €κ³  Notion(λ…Έμ…˜) DBλ₯Ό 연동해 JSON 파일둜 λΉŒλ“œ μ‹œμ μ— λ™κΈ°ν™”ν•˜λŠ” 방식을 μ“°κΈ°λ‘œ κ³„νšν–ˆμ§€λ§Œ, μœ μ €λ“€μ΄ μ‹€μ‹œκ°„μœΌλ‘œ 둜고λ₯Ό μ œλ³΄ν•˜κ³ , μ œκ°€ μ–΄λ“œλ―Ό νŽ˜μ΄μ§€μ—μ„œ 이λ₯Ό μ‹€μ‹œκ°„μœΌλ‘œ κ²€μ¦ν•˜κ³  λ°˜μ˜ν•˜κΈ°μ—” λ…Έμ…˜μ€ λ„ˆλ¬΄λ‚˜ 느리고 정적인 도ꡬ라고 νŒλ‹¨ν•΄μ„œ μ§„μ§œ λ°μ΄ν„°λ² μ΄μŠ€(DB)로 κΈ‰ μ„ νšŒν•˜μ˜€μŠ΅λ‹ˆλ‹€.

1. Notionμ—μ„œ Supabase둜, 데이터 λŒ€μ΄λ™ (Data Migration)

κ³Όκ°ν•˜κ²Œ GitHub Actions에 물렀있던 λ…Έμ…˜ 동기화 μ›Œν¬ν”Œλ‘œμš°λ₯Ό ν†΅μ§Έλ‘œ λ‚ λ €λ²„λ ΈμŠ΅λ‹ˆλ‹€. κ΄€λ ¨ μ˜μ‘΄μ„± νŒ¨ν‚€μ§€(@notionhq/client)와 낑은 logos.json νŒŒμΌλ„ κ°€μ°¨ 없이 νœ΄μ§€ν†΅μœΌλ‘œ λ³΄λƒˆμ£ . (μ‚­μ œν•  λ•Œμ˜ μΎŒκ°μ΄λž€! 🧹)

그리고 κ·Έ μžλ¦¬μ— μ§„μ§œ κ΄€κ³„ν˜• λ°μ΄ν„°λ² μ΄μŠ€(PostgreSQL)인 Supabaseλ₯Ό μ•‰ν˜”μŠ΅λ‹ˆλ‹€. λ‹¨μˆœνžˆ 데이터 μ €μž₯μ†Œλ§Œ λ°”κΎΌ 게 μ•„λ‹ˆλΌ, λ°μ΄ν„°μ˜ μž…λ ₯κ³Ό 좜λ ₯을 제 μ†μœΌλ‘œ 100% ν†΅μ œν•˜κΈ° μ‹œμž‘ν•˜λ©΄μ„œ ν”„λ‘œμ νŠΈμ˜ 체급 μžμ²΄κ°€ μ™„μ „νžˆ λ‹¬λΌμ‘ŒμŠ΅λ‹ˆλ‹€.

  • νƒ€μž… λ§€ν•‘ 버그와 'ONLY PNG' λ°°μ§€ 정상화: 초기 μ–΄λ“œλ―Όμ—μ„œ "이 λΈŒλžœλ“œλŠ” PNG만 μžˆμ–΄μš”!"라고 체크λ₯Ό 해도 자꾸 데이터가 곡쀑뢄해(undefined)λ˜λŠ” 버그가 μžˆμ—ˆμŠ΅λ‹ˆλ‹€. Setup SQL λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 슀크립트λ₯Ό μ§œμ„œ DB μ»¬λŸΌμ„ μƒˆλ‘œ 고치고, rowToBrand와 brandToRow μ–‘λ°©ν–₯ λ§€ν•‘ νƒ€μž…μ„ μ΄˜μ΄˜ν•˜κ²Œ λ³΄κ°•ν–ˆμŠ΅λ‹ˆλ‹€. ν”„λ‘ νŠΈμ—”λ“œκ°€ λ©‹λŒ€λ‘œ 데이터λ₯Ό μΆ”μΈ‘ν•˜λ˜ μž„μ‹œλ°©νŽΈ λ‘œμ§μ„ λ‹€ κ±·μ–΄λ‚΄κ³ , 였직 DB 원본(단일 μ†ŒμŠ€)μ—λ§Œ μ˜μ‘΄ν•΄ 'ONLY PNG' λ°°μ§€λ₯Ό 였차 없이 μ •ν™•ν•˜κ²Œ λ…ΈμΆœμ‹œν‚€λŠ” μ§œλ¦Ών•¨μ„ λ§›λ³΄μ•˜μŠ΅λ‹ˆλ‹€. 🏷️
  • γ„Ήγ„±λ§Œ 쳐도 λ‚˜μ˜€λŠ” "μ΄ˆμ„± 검색" κ΅¬ν˜„: λ…Έμ…˜ APIλ‘œλŠ” 상상도 ν•  수 μ—†λ˜ μ •κ΅ν•œ 검색 κ²½ν—˜λ„ μ„ λ¬Όν–ˆμŠ΅λ‹ˆλ‹€. λΈŒλžœλ“œλ₯Ό 등둝할 λ•Œ 'ν•œκΈ€ μ΄ˆμ„± κ²€μƒ‰μš© ν•„λ“œ'κ°€ λ°μ΄ν„°λ² μ΄μŠ€μ— μžλ™μœΌλ‘œ μƒμ„±λ˜μ–΄ ν•¨κ»˜ μ €μž₯λ˜λ„λ‘ μ˜λ¦¬ν•˜κ²Œ μ½”λ“œλ₯Ό μ„€κ³„ν–ˆμŠ΅λ‹ˆλ‹€. 덕뢄에 μœ μ €κ°€ 검색창에 μ΄ˆμ„±λ§Œ 툭툭 쳐도 μ›ν•˜λŠ” λΈŒλžœλ“œλ₯Ό 번개처럼 μ°Ύμ•„μ£ΌλŠ” λŒ€κΈ°μ—…κΈ‰ 검색 UXκ°€ μ™„μ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€. πŸ”

2. 1μ΄ˆκ°€ μ•„μ‰¬μš΄ λͺ¨λ°”일 μ„±λŠ₯ κ°œμ„ : λ¬΄μžλΉ„ν•œ 캐싱과 λ Œλ”λ§ μ΅œμ ν™”

λ°μ΄ν„°λ² μ΄μŠ€λ₯Ό νƒ„νƒ„ν•˜κ²Œ λ‹€μ‘ŒμœΌλ‹ˆ, 이제 μœ μ €μ—κ²Œ μ „λ‹¬λ˜λŠ” 속도λ₯Ό κ·Ήλ„λ‘œ λŒμ–΄μ˜¬λ¦΄ μ°¨λ‘€μ˜€μŠ΅λ‹ˆλ‹€.

초기의 '둜고착착' ν™ˆ νŽ˜μ΄μ§€λŠ” μ–Έμ œ 데이터가 λ°”λ€”μ§€ λͺ¨λ₯΄λ‹ˆ λ§€ μš”μ²­λ§ˆλ‹€ DBλ₯Ό ν†΅μ§Έλ‘œ μƒˆλ‘œ 찌λ₯΄λŠ” force-dynamic κ΅¬μ‘°μ˜€μŠ΅λ‹ˆλ‹€. κ²°κ³ΌλŠ”? 첫 νŽ˜μ΄μ§€ λ‘œλ”©μ΄ λˆˆμ— λ„κ²Œ λ‹΅λ‹΅ν–ˆμŠ΅λ‹ˆλ‹€. κ²Œλ‹€κ°€ ν™ˆ 화면에 λΈŒλžœλ“œ μΉ΄λ“œκ°€ μˆ˜μ‹­, 수백 κ°œμ”© κΉ”λ¦¬λŠ”λ°, κ·Έ 무거운 κ³ ν™”μ§ˆ 둜고 이미지듀이 ν•œ λ²ˆμ— λ””μ½”λ”©λ˜λŠλΌ λͺ¨λ°”일 κΈ°κΈ°μ—μ„œμ˜ TTI(Time to InteractiveΒ·μ‚¬μš©μžκ°€ μ‹€μ œ μ‘°μž‘ κ°€λŠ₯ν•œ μ‹œκ°„)κ°€ μ΅œμ•…μ„ 달리고 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

λ””μžμ΄λ„ˆλ‘œμ„œ 이 '버벅거림'은 μ ˆλŒ€ μš©λ‚©ν•  수 μ—†μ—ˆκΈ°μ—, μ„±λŠ₯ μ΅œμ ν™” ν’€μ½”μŠ€λ₯Ό λ°Ÿμ•˜μŠ΅λ‹ˆλ‹€. πŸ‘‡

[ κΈ°μ‘΄ ]  λ§€ μš”μ²­λ§ˆλ‹€ DB 풀쿼리 ──> λͺ¨λ“  이미지 λ™μ‹œ λ””μ½”λ“œ ──> λͺ¨λ°”일 λ²„λ²…μž„ 🐒
[ κ°œμ„  ]  ISR (1μ‹œκ°„ λ‹¨μœ„ μΊμ‹œ) ──> Lazy Loading + Async Decoding ──> 번개 같은 λ‘œλ”© ⚑
  • ISR(Incremental Static Regeneration) λ„μž…: ν™ˆ νŽ˜μ΄μ§€μ™€ λΈŒλžœλ“œ 상세 νŽ˜μ΄μ§€λ₯Ό 1μ‹œκ°„ λ‹¨μœ„λ‘œ 캐싱(ISR)λ˜λ„λ‘ μ „ν™˜ν–ˆμŠ΅λ‹ˆλ‹€. 맀번 DBλ₯Ό 찌λ₯΄λŠ” λŒ€μ‹  λ‹€μ΄λ‚΄λ―Ήν•˜κ²Œ μƒμ„±λœ 정적 νŽ˜μ΄μ§€λ₯Ό λ˜μ Έμ£Όλ‹ˆ 속도가 μ••λ„μ μœΌλ‘œ λΉ¨λΌμ‘ŒμŠ΅λ‹ˆλ‹€.
  • 이미지 λ Œλ”λ§ μ΅œμ ν™”: λΈŒλžœλ“œ 둜고 <img> νƒœκ·Έμ— loading="lazy"와 decoding="async"λ₯Ό κΈ°λ³Έ μž₯μ°©ν–ˆμŠ΅λ‹ˆλ‹€. μœ μ €μ˜ ν™”λ©΄ μŠ€ν¬λ‘€μ— 맞좰 ν•„μš”ν•œ 둜고만 λΉ„λ™κΈ°λ‘œ λ””μ½”λ“œλ˜λ„λ‘ μœ λ„ν•˜μ—¬ λͺ¨λ°”일 CPU의 뢀담을 μ‹Ή λœμ–΄μ£Όμ—ˆμŠ΅λ‹ˆλ‹€.
  • μ‚¬μš©μž 행동 예츑 데이터 ν”„λ¦¬νŽ˜μΉ˜(Prefetch): μœ μ €κ°€ λΈŒλžœλ“œ μΉ΄λ“œμ— 마우슀λ₯Ό ν˜Έλ²„ν•˜κ±°λ‚˜ ν„°μΉ˜ν•˜λŠ” μˆœκ°„, 상세 νŽ˜μ΄μ§€ 데이터λ₯Ό λ°±κ·ΈλΌμš΄λ“œμ—μ„œ 미리 λ•‘κ²¨μ˜€λ„λ‘(prefetch) μ„€μ •ν•˜κ³ , React cacheλ₯Ό μ μš©ν•΄ 쀑볡 API ν˜ΈμΆœμ„ λ§‰μ•˜μŠ΅λ‹ˆλ‹€. μœ μ € μž…μž₯μ—μ„œλŠ” ν΄λ¦­ν•˜μžλ§ˆμž νŽ˜μ΄μ§€κ°€ 'μ°©!' ν•˜κ³  λœ¨λŠ” λ§ˆλ²• 같은 κ²½ν—˜μ„ ν•˜κ²Œ 된 것이죠. πŸ₯°

3. "μ–΄λ“œλ―Όμ΄ μˆ˜μ •ν•œ 게 μ™œ μ•ˆ λ°”λ€Œμ£ ?" μΊμ‹œ 경계 μ„€κ³„μ˜ κΉ¨λ‹¬μŒ

ν•˜μ§€λ§Œ μΊμ‹œ(ISR)의 달콀함에 μ·¨ν•΄μžˆλ‹€ λ³΄λ‹ˆ μ˜ˆμƒμΉ˜ λͺ»ν•œ 운영 μ΄μŠˆκ°€ ν„°μ‘ŒμŠ΅λ‹ˆλ‹€. μ–΄λ“œλ―Ό λŒ€μ‹œλ³΄λ“œμ—μ„œ μ œκ°€ 직접 μ˜€νƒ€λ₯Ό μˆ˜μ •ν•˜κ±°λ‚˜ μƒˆ 둜고λ₯Ό μŠΉμΈν–ˆλŠ”λ°λ„, μ •μž‘ ν™ˆ ν™”λ©΄μ—λŠ” 1μ‹œκ°„ λ™μ•ˆ λ°˜μ˜λ˜μ§€ μ•ŠλŠ” κ²ƒμ΄μ—ˆμŠ΅λ‹ˆλ‹€! 🀯

원인을 μ°Ύμ•„λ³΄λ‹ˆ, κ΄€λ¦¬μžμš© μ–΄λ“œλ―Ό νŽ˜μ΄μ§€μ™€ 일반 μœ μ €μš© ν™ˆ νŽ˜μ΄μ§€κ°€ λ™μΌν•œ 데이터 μ—”λ“œν¬μΈνŠΈλ₯Ό κ³΅μœ ν•˜κ³  μžˆμ—ˆκΈ° λ•Œλ¬Έμ΄μ—ˆμŠ΅λ‹ˆλ‹€.

μ—¬κΈ°μ„œ μ•„μ£Ό κ°’μ§„ μ•„ν‚€ν…μ²˜μ  κ΅ν›ˆμ„ μ–»μ—ˆμŠ΅λ‹ˆλ‹€. "λ™μΌν•œ 데이터라도 λ³΄μ—¬μ£ΌλŠ” '톀(Tone)'κ³Ό 'λͺ©μ '이 λ‹€λ₯΄λ‹€λ©΄ μΊμ‹œ 경계λ₯Ό λͺ…ν™•νžˆ 뢄리해야 ν•œλ‹€"λŠ” κ²ƒμ„μš”.

μ¦‰μ‹œ μ–΄λ“œλ―Ό μ „μš© μ „μš© API μ—”λ“œν¬μΈνŠΈ(/api/admin/brands)λ₯Ό μ™„μ „νžˆ μƒˆλ‘œ νŒŒλ‚΄μ–΄, 이 녀석은 μΊμ‹œ 없이 항상 μ‹€μ‹œκ°„ DB 값을 κΈμ–΄μ˜€λŠ” force-dynamic으둜 μš΄μ˜λ˜λ„λ‘ κ²©λ¦¬ν–ˆμŠ΅λ‹ˆλ‹€. 일반 μœ μ €λ“€μ€ 1μ‹œκ°„ μΊμ‹œλœ λΉ λ₯Έ 화면을 보고, μ–΄λ“œλ―ΌμΈ μ €λŠ” μ‹€μ‹œκ°„μœΌλ‘œ μ™„λ²½ν•˜κ²Œ λ™κΈ°ν™”λœ 데이터λ₯Ό 보며 운영 νš¨μœ¨μ„ κ·ΉλŒ€ν™”ν•  수 있게 λ˜μ—ˆμŠ΅λ‹ˆλ‹€. πŸ“Š

올리브의 ν•œ 쀄 μš”μ•½ πŸ’‘

"λ°±μ—”λ“œ μ•„ν‚€ν…μ²˜μ™€ μ„±λŠ₯ μ΅œμ ν™”λŠ” λ‹¨μˆœνžˆ 기술적 κ³Όμ‹œκ°€ μ•„λ‹™λ‹ˆλ‹€. μœ μ €μ—κ²ŒλŠ” μ§€λ£¨ν•œ 'κΈ°λ‹€λ¦Όμ˜ μ‹œκ°„'을 μ§€μ›Œμ£Όκ³ , λ©”μ΄μ»€μ—κ²ŒλŠ” 'μ •ν™•ν•œ 운영 데이터'λ₯Ό 보μž₯ν•΄ μ£ΌλŠ” 일, 이 λ˜ν•œ μ™„λ²½ν•œ ν”„λ‘œλ•νŠΈ κ²½ν—˜(UX)을 μ™„μ„±ν•˜λŠ” 핡심 νΌμ¦μž…λ‹ˆλ‹€."

μ΄μ–΄μ§€λŠ” [4편]μ—μ„œλŠ”... ν‰ν™”λ‘­λ˜ μ–΄λŠ λ‚ , μ™ΈλΆ€ μŠ€ν¬λž©λ΄‡μ˜ 무차별 핫링크 곡격으둜 인해 단 ν•˜λ£¨ λ§Œμ— 130GB λŒ€μ—­ν­ 폭탄을 맞고 Vercel 계정이 μ •μ§€λ˜μ—ˆλ˜ μ§œλ¦Ών•œ(?) μœ„κΈ° νƒˆμΆœκΈ°μ™€ 인프라 λ°©μ–΄μ„  ꡬ좕 μŠ€ν† λ¦¬κ°€ μ΄μ–΄μ§‘λ‹ˆλ‹€. πŸ’£ (μ§„μ§œ 눈물 없이 λ³Ό 수 μ—†λŠ” 메이컀 μƒμ‘΄κΈ°μž…λ‹ˆλ‹€...)