π§βοΈ λ€μ΄κ°κΈ°μ μμ
λ¬΄μ¨ νλ‘μ νΈμΈκ°μ?
μκ°
π§βοΈ “λ²μ μ² κ³΅μ DB + μ체 RAG (μκ··νλ‘ + LLM μ»΄νμΌ wiki)λ‘ λͺ¨λ μΈμ©μ κ΅μ°¨κ²μ¦ν΄μ LLM νκ°μ μ°¨λ¨νλ νκ΅ λ²λ Ή·μΈλ¬΄ Q&A μ΄μμ€ν΄νΈ”
→ λ¨μΌ Next.js νλ‘μ νΈλ‘ λ κ° λλ©μΈ(LawLens + SalesLens)μ μ΄μνλ νκ΅ λ²λ Ή AI SaaS
Github
Url
ꡬ쑰 λΆμ
리뷰νκΈ° μ μ ꡬ쑰 λ¨Όμ μ§κ³ κ°μλ©΄!
μ΄ 10κ°μ λλ©μΈ, 14κ°μ νμ΄μ§
β― tree
.
βββ app
β βββ _components
β βββ account
β β βββ organization
β β β βββ [id]
β β β β βββ page.tsx
β β β βββ page.tsx
β β βββ page.tsx
β βββ billing
β β βββ return
β β βββ page.tsx
β βββ chat
β β βββ [id]
β β β βββ page.tsx
β β βββ page.tsx
β βββ disclaimer
β β βββ page.tsx
β βββ layout.tsx
β βββ login
β β βββ layout.tsx
β β βββ page.tsx
β βββ page.tsx
β βββ pricing
β β βββ page.tsx
β βββ privacy
β β βββ page.tsx
β βββ refund
β β βββ page.tsx
β βββ saleslens
β β βββ layout.tsx
β β βββ page.tsx
β βββ terms
β βββ page.tsx
λ¬Έμ μν©
μ 체μ μΈ νμ΄μ§ λ λλ§ μλλ 무λνμ§λ§, μ λ μ¬μ΄λλ°μμ λνλ₯Ό ν΄λ¦νλ©΄, μ±ν μ°½μΌλ‘ μ νλκΈ°κΉμ§ λ―Έλ¬ν λλ μ΄κ° μμμ΄μ
λ°λΌμ λΆλμ΄ λ무 λ°©λν κ΄κ³λ‘, "μ±ν
λΆλΆμ νμ΄μ§ μ ν μλκ° λ리λ€"λ λ¬Έμ μ μ§μ€ν΄μ /src/chat λΆλΆλ§ 리ν©ν λ§μ μ§ννμ΄μ.
μμΈ
LawLensλ μ λνλ₯Ό μμν λ μλμ κ°μ νλ¦μ΄μμ
/chatμμ λ©μμ§ μ λ ₯-

- μλ²μμ AI μλ΅ μ€νΈλ¦¬λ° μμ
- μλ΅μ΄ λλλ©΄ νμ΄μ§κ° 리λ λλ§ λλ©΄μ(κΉλΉ‘ ν) → URLμ΄
/chat/uuid-1234λ‘ λ°λ

νλ¦μ 보면 μ μ μλ―, μλ΅μ΄ λλλ μκ° λ©μμ§κ° μμμλΆν° λ€μ λ‘λλλ©°, κΉλΉ‘μ΄λ©΄μ μ΄λ―Έ λ€ κ·Έλ €μ‘λ λ΄μ©μ΄ μ²μλΆν° λ€μ λ λλ§λλ κ±Έ νμΈν μ μμμ΄μ.
useEffect(() => {
if (isNew && !navigatedRef.current && status === 'ready' && messages.length > 0) {
navigatedRef.current = true;
router.replace(`/chat/${conversationId}`);
}
}, [isNew, status, messages.length, router, conversationId]);
router.replace() λ URLμ λ°κΎΈλ ν¨μκ° μλ App Routerμμλ "μ΄ κ²½λ‘λ‘ λ€λΉκ²μ΄μ
νλ ν¨μ"μμ. λ°λΌμ μ¬ν¨μΉκ° λ°λΌμμ.
Next.js App Routerλ μλ² μ»΄ν¬λνΈ RSC κΈ°λ°μ΄λ―λ‘, router.replace()λ₯Ό μ¬μ©νλ©΄ μ URLμ ν΄λΉνλ μλ² μ»΄ν¬λνΈλ₯Ό λ€μ ν¨μΉν΄μ.
μ¦ URLμ΄ λ°λμμΌλ―λ‘ μλ²μμλ λ°λ URLμ RSCλ₯Ό λ€μ ν¨μΉνκ³ ChatLayout.tsx νμΌμ ν΄λΉνλ 쿼리λ₯Ό λͺ¨λ λ€μ μ€ν ν μ΄ν μλ‘ λ¦¬λ λλ§μ νκ² λλ νλ¦μ΄μμ΄μ.
μμ
λ΅λ³ μ΄νμλ URLλ§ λ°κΎΈκ³ μΆμ μλλ₯Ό μ μ§νλ €λ©΄, Next.jsλ₯Ό μ°νν΄μ window.history.replaceState λ₯Ό μ¨μ£Όλ©΄ λΌμ.
// Before
router.replace(`/chat/${conversationId}`);
// After
window.history.replaceState(null, '', `/chat/${conversationId}`);
λ€λ§ νκ°μ§ μ£Όμν μ μ, ‘Next.jsλ₯Ό μ°νν΄μ’ URLμ λ³κ²½νλ κ²μ΄κΈ° λλ¬Έμ λΌμ°ν° μνμ μμ‘΄νλ λ€λ‘κ°κΈ° λμ λ±μ μννμ λ λ΄λΆ μνκ° μμκ³Ό λ€λ₯Ό μ μλ€λ νΈλ μ΄λ μ€νκ° μμ΄μ.

π₯ React μ€ν°λ νκ³
10μ£Ό λμ React μ€κΈ μ€ν°λλ₯Ό λ§μ³€λ€. μ»΄ν¬λνΈ μ€κ³λΆν° ν μ€νΈ μ λ΅κΉμ§, λ κΆκΈνμ§λ§ νΌμμλ νλ³΄μ§ λͺ»νλ λ¬Έμ λ€μ μ€ν°λμλ€κ³Ό νλμ© μ§μ΄κ°λ μκ°μ΄μλ€. "μ μ΄λ κ² μ¨μΌ νλκ°"λ₯Ό κ³μ λ¬»κ³ , μ΄λ»κ² νλ©΄ λ μ½κ² μ€λͺ ν μ μμμ§ κ³ λ―Όνλ μ¬λ―Έκ° μλ μ€ν°λμλ€.
컀리νλΌ νλμ 보기
| μ£Όμ°¨ | μ£Όμ |
|---|---|
| 1μ£Όμ°¨ | μ»΄ν¬λνΈ μ€κ³ μμΉ |
| 2μ£Όμ°¨ | 컀μ€ν ν κ³Ό μ± μ λΆλ¦¬ |
| 3μ£Όμ°¨ | μν κ΄λ¦¬ μ λ΅ λΉκ΅ |
| 4μ£Όμ°¨ | TanStack Query μ¬ν |
| 5μ£Όμ°¨ | λ λλ§ μ΅μ ν μ€μ |
| 6μ£Όμ°¨ | μλ¬ νΈλ€λ§ & μμ μ± |
| 7μ£Όμ°¨ | ν΄λ ꡬ쑰 & μν€ν μ² |
| 8μ£Όμ°¨ | ν μ€νΈ μ λ΅ |
| 9μ£Όμ°¨ | ν¬νΈν΄λ¦¬μ€ μ¬μ΄νΈ λ§λ€μ΄μ€κΈ° (MVP) |
| 10μ£Όμ°¨ | μ€ν°λν λ΄μ©μ κΈ°λ°μΌλ‘ μλ‘ μ½λ 리뷰ν΄μ£ΌκΈ° |
5μ£Όμ°¨ λ λλ§ μ΅μ ν μ€μ (λ°ν μ£Όμ°¨) νκ³
λ λλ§ μ΅μ νλ λ΄κ° μ§μ λ°νλ₯Ό λ§‘μ μ£Όμ°¨μ΄μ, μ€μ 10μ£Όμ°¨ μ½λ리뷰κΉμ§ μ¬μ©λ κ°λ
μ΄μλ€.
λ λλ§ μ΅μ ν μ€μ κ°μ΄λ: React DevTools Profiler νμ©λ², useMemo / useCallback / memo μ μ© κΈ°μ€, λΆνμν λ λλ§ μ°Ύμμ μμ κΈ°
μ€λΉνλ©΄μ κ°μ₯ ν¬κ² λλ 건 μΈ‘μ μμ΄ μ΅μ ννλ©΄ μ λλ€λ κ²μ΄μλ€. μ²μμλ "λͺ¨λ κ³³μ useMemoμ useCallbackμ λΆμ΄λ©΄ λΉ¨λΌμ§μ§ μμκΉ"λΌλ μκ°μ΄ μμλλ°, μ€μ λ‘ Profilerλ₯Ό λλ €λ³΄λ©΄ λ³λͺ©μ΄ μμκ³Ό λ€λ₯Έ κ³³μ μλ κ²½μ°κ° λ§μλ€.
Profilerμ Flame Graphμμ μ
ν μκ°μ΄ λμ μ»΄ν¬λνΈλ₯Ό μ°Ύκ³ , "μ μ΄ μ»΄ν¬λνΈκ° λ λλ§λλκ°"λ₯Ό νμΈν λ€μ μ΅μ ν λꡬλ₯Ό μ μ©νλ μμκ° ν΅μ¬μ΄μλ€. μ€μ LawLens μ½λμμλ λΆνμν useCallbackμ μ κ±°νλ κ²μ΄ μ€νλ € μ±λ₯ κ°μ μΌλ‘ μ΄μ΄μ§λ μ¬λ‘λ μμλ€. μ΅μ νλ 무μΈκ°λ₯Ό μΆκ°νλ κ²μ΄ μλλΌ λΆνμν κ²μ μμ λ κ²μ΄κΈ°λ νλ€λ κ±Έ μ€κ°νλ€.
λ°ν μ€λΉ κ³Όμ μμ νμλΆμ΄ λ¨κ²¨μ£Όμ μ§λ¬Έμ΄ κΈ°μ΅μ λ¨λλ€.
"λͺ¨λ λΆλΆμ λ©λͺ¨μ΄μ μ΄μ vs νμν λΆλΆλ§ λ©λͺ¨μ΄μ μ΄μ μ€ μ΄λ€ μκ²¬μ΄ λ λ³ΈμΈκ³Ό λ§λ κ² κ°μλμ?"
λΉμμλ "νμν λΆλΆλ§"μ΄λΌκ³ λ΅νμ§λ§, κ·Έ κΈ°μ€μ΄ λμ§ μ€μ€λ‘λ νλ¦Ώνλ€. λ°νλ₯Ό μ€λΉνλ©΄μ κ·Έ κΈ°μ€μ΄ μ‘°κΈμ© λͺ νν΄μ‘λ€. λΉμΌ κ³μ°μΈμ§, ν¨μ μ°Έμ‘°μ μμ μ±μ΄ νμνμ§, λΆλͺ¨ 리λ λλ§μ΄ μμμκ² μ νλλμ§
μ΄ μΈ κ°μ§λ₯Ό νμΈνλ κ²μ΄ μμΌλ‘ μ΅μ νμ κΈ°μ€μ΄ λμλ€
μ 체 νκ³
κ°μ₯ ν¬κ² μ±μ₯ν λΆλΆ
"μ"λ₯Ό λ¨Όμ 묻λ μ΅κ΄μ΄ μκ²Όλ€. λꡬλ₯Ό μ¬μ©νκΈ° μ μ μ μ΄ λκ΅¬κ° νμνμ§, λ€λ₯Έ μ νμ§λ 무μμΈμ§λ₯Ό λ¨Όμ κ³ λ―Όνκ² λλ€. λ λλ§ μ΅μ ν λ°νλ₯Ό μ€λΉνλ©΄μ νΉν κ·Έ μ΅κ΄μ΄ κ°ν΄μ‘λ€κ³ μκ°νλ€.
μμ¬μ λ λΆλΆ
μνκΈ°κ°μλ νκ΅ κ³΅λΆμ μ§μ€νλ€ λ³΄λ 4, 8μ£Όμ°¨μ λ΄μ©μ κΉμ΄ μκ² λ°λΌκ°μ§ λͺ»ν λΆλΆμ΄ μλ€. Tanstack Query, μλ¬ νΈλ€λ§κ³Ό ν μ€νΈ μ λ΅μ λ³λλ‘ λ 곡λΆνκ³ μΆμ μ£Όμ λ‘ λ¨μ μλ€.
μ€ν°λμμ λ°°μ΄ κ²
κΈ°μ μ§μλ§νΌμ΄λ ν¨κ» 곡λΆνλ λ°©μλ λ°°μ λ€. μ§λ¬Ένλ κ²μ λλ €μνμ§ μλ λΆμκΈ°, λ€λ₯Έ μ¬λμ μ½λμμ λ°°μ°λ κ²½ν, λ°νλ₯Ό μ€λΉνλ©΄μ κ°λ μ λ€μ μ 리νλ κ³Όμ κ°μ λͺ¨λ κ²μ΄ νΌμ 곡λΆν λλ μ»κΈ° μ΄λ €μ΄ κ²λ€μ΄μλ€.
λ§μΉλ©°
10μ£Όκ° μκ°λ³΄λ€ λΉ λ₯΄κ² μ§λκ°λ€. λ§€ μ£Όμ°¨λ§λ€ "μ΄κ±΄ λμ€μ λ€μ λ΄μΌκ² λ€"λ μ£Όμ κ° μμκ³ , κ·Έκ² μ§κΈμ νμ΅ λ‘λλ§΅μ΄ λλ€.
μ€ν°λλ λλ¬μ§λ§, κ° μ£Όμ°¨μμ λ€λ£¬ μ£Όμ λ€μ κ³μ μ΄μ΄μ 곡λΆν μμ μ΄λ€. νΉν λ λλ§ μ΅μ νμ ν μ€νΈ μ λ΅μ μ€μ νλ‘μ νΈμ λ κΉμ΄ μ μ©ν΄λ³΄κ³ μΆλ€!
'recap' μΉ΄ν κ³ λ¦¬μ λ€λ₯Έ κΈ
| [26λ μλ°κΈ° νκ³ ] Connecting the Dots (0) | 2026.07.11 |
|---|---|
| ASBG(μ ACC) μ§μ νκΈ° (0) | 2026.07.08 |
| [UMC 8κΈ°/Web(React)ννΈ] μλ₯&λ©΄μ ν©κ²© νκΈ° (0) | 2026.07.07 |
| [kakao X goorm] 9oormthonUNIV 3κΈ° λ¨νν€, 첫 ν΄μ»€ν€ νκΈ°! (0) | 2026.07.05 |