Skip to content

Latest commit

ย 

History

493 Commits

Folders and files

NameName
Last commit message
Last commit date
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Hi, Ditto!

DITTO FrontEnd

๊ตญ๊ฐ€ ํŠธ๋ Œ๋“œ ๋ถ„์„๋ถ€ํ„ฐ ์ฝ”์Šค ์ถ”์ฒœ๊นŒ์ง€, ๋ฐฑํ™”์  ์™ธ๊ตญ์ธ ๋ฐฉ๋ฌธ๊ฐ์„ ์œ„ํ•œ AI ์‡ผํ•‘ ๋ฉ”์ดํŠธ

Next.js 16 App Router ยท React 19 ยท Three.js ยท Tailwind CSS ยท Zustand ยท next-intl ยท PWA

HI-FO! 3ํŒ€

ํŒ€์žฅ ๋ถ€ํŒ€์žฅ ํŒ€์› ํŒ€์›
๋„๊ฑด์šฐ ๊ณตํฌ์ง„ ์•ˆ์˜์ฐฌ ์•ˆ์ •๋ฏผ
@woodgeon @heejinkong @Ui-chan @Dev-Anniee

๋ชฉ์ฐจ

ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ ยท ์ฃผ์š” ํ™”๋ฉด ยท ํ”„๋ก ํŠธ์—”๋“œ๊ฐ€ ํ‘ธ๋Š” ๋ฌธ์ œ ยท ๊ฐœ๋ฐœ ๊ฐ€์ด๋“œ


ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ

์™œ ๋งŒ๋“ค์—ˆ๋‚˜

ํ•œ๊ตญ ๊ด€์‹ฌ ๊ณ„๊ธฐ

์™ธ๊ตญ์ธ์ด ํ•œ๊ตญ์— ๊ด€์‹ฌ์„ ๊ฐ–๊ฒŒ ๋œ ๊ณ„๊ธฐ 1์œ„๋Š” ํ•œ๋ฅ˜ ์ฝ˜ํ…์ธ  38.3%(K-POP ยท DRAMA ยท K-BEAUTY ยท K-FOOD)์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ๊ทธ ๊ด€์‹ฌ์„ ์‹ค์ œ ์‡ผํ•‘์œผ๋กœ ์˜ฎ๊ธฐ๋ ค๋ฉด ์ •๋ณด๊ฐ€ ํŒ๋งค์ฒ˜ ยท ์˜์ƒ ์ฝ˜ํ…์ธ  ยท SNS ยท ํ›„๊ธฐ๋กœ ์ „๋ถ€ ํฉ์–ด์ ธ ์žˆ์Šต๋‹ˆ๋‹ค.

์ •๋ณด ํƒ์ƒ‰๊ณผ ์„ ํƒ ๋ถ€๋‹ด

SNS์—์„œ ํƒ์ƒ‰ โ†’ ์šด์˜ ์ •๋ณด ํ™•์ธ โ†’ ํ›„๊ธฐ ๋น„๊ต โ†’ ๋งค์žฅ ์ •๋ณด ๋Œ€์กฐ โ†’ ์ผ์ •ยท๋™์„  ์žฌ๊ตฌ์„ฑ. ๋‹ค์„ฏ ๋‹จ๊ณ„๋ฅผ ๋งค๋ฒˆ ๋ฐ˜๋ณตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์„ ํƒ์ง€๊ฐ€ ๋งŽ์•„์งˆ์ˆ˜๋ก ๋ถˆํ™•์‹ค์„ฑ์€ ์ปค์ง€๊ณ , ๊ฒฐ์ •์€ ๋Šฆ์–ด์ง‘๋‹ˆ๋‹ค.

DITTO โ€” ํฉ์–ด์ง„ ๊ด€์‹ฌ์„ ํ•˜๋‚˜์˜ ์ฝ”์Šค๋กœ

DITTO ์„œ๋น„์Šค ๊ฐœ์š”

๋„ค๊ฐ€ ํ•ด๋ดค์œผ๋‹ˆ, ๋‚˜๋„.

01 DISCOVER ๋ฐœ๊ฒฌ 02 PLAN ๊ณ„ํš 03 NAVIGATE ์ด๋™
๊ตญ๊ฐ€๋ณ„ Trend Map์œผ๋กœ K-ํŠธ๋ Œ๋“œ๋ฅผ ๊ณจ๋ผ ๋‹ด๋Š”๋‹ค ์š”์ฒญ์„ ๋ฐ˜์˜ํ•œ AI ๋งž์ถคํ˜• ์ฝ”์Šค๋ฅผ ๋งŒ๋“ ๋‹ค 3D ์ง€๋„์™€ ์‹ค๋‚ด ๊ธธ์ฐพ๊ธฐ๋กœ ์‹ค์ œ ์ด๋™๊นŒ์ง€ ์•ˆ๋‚ดํ•œ๋‹ค

์ด ์ €์žฅ์†Œ๋Š” ๊ทธ ์„ธ ๋‹จ๊ณ„๋ฅผ ์†๋‹˜์ด ์‹ค์ œ๋กœ ๋งŒ์ง€๋Š” ํ™”๋ฉด์ž…๋‹ˆ๋‹ค.


์ฃผ์š” ํ™”๋ฉด

DISCOVER โ€” ๊ตญ๊ฐ€๋ณ„ Trend Map

๊ตญ๊ฐ€๋ณ„ Trend Map

4๊ฐœ๊ตญ์˜ ๊ด€์‹ฌ ์ฝ˜ํ…์ธ ์™€ ๋ธŒ๋žœ๋“œ๋ฅผ ๋ถ„์„ํ•ด ๊ธฐ๋ณธ ์ฝ”์Šค๋ฅผ ์ถ”์ฒœํ•ฉ๋‹ˆ๋‹ค. ๊ตญ๊ฐ€ยท์–ธ์–ด๋Š” use-preference-store๊ฐ€ ditto-country ยท ditto-language ์ฟ ํ‚ค๋กœ ๋“ค๊ณ  ์žˆ์–ด SSR ๋‹จ๊ณ„์—์„œ ์ด๋ฏธ ๊ฒฐ์ •๋ฉ๋‹ˆ๋‹ค โ€” hydration ๋’ค์— ์–ธ์–ด๊ฐ€ ๋’ค๋Šฆ๊ฒŒ ๋ฐ”๋€Œ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

PLAN โ€” AI ๋งž์ถคํ˜• ์ฝ”์Šค ์ƒ์„ฑ

AI ๋งž์ถคํ˜• ์ฝ”์Šค ์ƒ์„ฑ

/ai-course. ์ฝ”์Šค ํŽธ์ง‘์ฒ˜๋Ÿผ ์—ฌ๋Ÿฌ Client Component๊ฐ€ ๊ณต์œ ํ•˜๋Š” ๋‹ค๋‹จ๊ณ„ ์ž‘์„ฑ ์ƒํƒœ๋Š” use-course-editor-store ํ•œ ๊ณณ์— ๋ชจ์๋‹ˆ๋‹ค. ์ƒ์„ฑ๋œ ์ฝ”์Šค๋Š” ์ €์žฅํ•˜๊ณ  ๊ณต์œ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

NAVIGATE โ€” 3D ์ฝ”์Šค์™€ ์‹ค๋‚ด ๊ธธ์ฐพ๊ธฐ

3D ์ฝ”์Šค์™€ ์‹ค๋‚ด ๊ธธ์ฐพ๊ธฐ

three + @react-three/fiber + @react-three/drei๋กœ 8๊ฐœ ์ธต์„ ํ•˜๋‚˜์˜ ํ™”๋ฉด์— ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.

01 ์ถ”์ฒœ ์ฝ”์Šค๋ฅผ ์ธต๋ณ„ 3D๋กœ ํ™•์ธ
02 ํ˜„์žฌ ์œ„์น˜์—์„œ ๋‹ค์Œ ๋งค์žฅ๊นŒ์ง€ ๊ธธ์ฐพ๊ธฐ
03 ์ธต์ด ๋ฐ”๋€Œ์–ด๋„ ํ•˜๋‚˜์˜ ๊ฒฝ๋กœ๋กœ ์—ฐ๊ฒฐ

BONI โ€” ๋”ํ˜„๋Œ€ ์•ˆ๋‚ด ์บ๋ฆญํ„ฐ

BONI

๋ฐœ๊ฒฌ ยท ๊ณ„ํš ยท ์ด๋™ ์ „ ๊ณผ์ •์„ ํ•จ๊ป˜ํ•˜๋Š” ์นœ๊ตฌ.


ํ”„๋ก ํŠธ์—”๋“œ๊ฐ€ ํ‘ธ๋Š” ๋ฌธ์ œ

8๊ฐœ ์ธต์„ ํ•˜๋‚˜์˜ ๊ธธ์ฐพ๊ธฐ ๊ทธ๋ž˜ํ”„๋กœ

์‹ค๋‚ด ๊ธธ์ฐพ๊ธฐ ํŒŒ์ดํ”„๋ผ์ธ

CVAT๋กœ ๋ผ๋ฒจ๋งํ•œ ํ†ต๋กœยท์ถœ์ž…๊ตฌ๋ฅผ ๋…ธ๋“œยท๊ฐ„์„  ๊ทธ๋ž˜ํ”„๋กœ ๋ฐ”๊พธ๊ณ  A* ๋กœ ํƒ์ƒ‰ํ•œ ๊ฒฐ๊ณผ๋ฅผ, ์ด ์ €์žฅ์†Œ๊ฐ€ 2D ยท 3D๋กœ ๊ทธ๋ฆฝ๋‹ˆ๋‹ค. ์ธต ๊ทธ๋ž˜ํ”„ 8๊ฐœ ยท ์žฅ์†Œ ์›์žฅ 147๊ณณ ยท ๋ฐฉ ํด๋ฆฌ๊ณค์ด ๊ทธ ์ž…๋ ฅ์ž…๋‹ˆ๋‹ค.

์ง€๋„ ์ž์‚ฐ์€ ๋ฒˆ๋“ค์ด ์•„๋‹ˆ๋ผ CDN์—์„œ

์ธํ”„๋ผ ๊ฐœ์„ 

์‹ค๋‚ด ์ง€๋„ ์›์žฅ(JSON 588KB)๊ณผ ์ธต ํ…์Šค์ฒ˜(PNG 2.2MB)๋Š” ํ•œ ๋ฒˆ ๋งŒ๋“ค์–ด์ง€๋ฉด ๋ฐ”๋€Œ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. CloudFront course-resource/*๋กœ ๋‚ด๋ณด๋‚ด๊ณ  3์ฃผ์งœ๋ฆฌ Cache-Control์„ ๋ถ™์˜€์Šต๋‹ˆ๋‹ค. CDN ๋„์ž…์œผ๋กœ ์ด๋ฏธ์ง€ ๋กœ๋”ฉ โˆ’51.7%(94.9ms โ†’ 45.8ms), ๋™์˜์ƒ ์žฌ์ƒ ์‹œ์ž‘ โˆ’48.0%(222ms โ†’ 150ms).

์ธต ํ…์Šค์ฒ˜๋Š” next/image๊ฐ€ ์•„๋‹ˆ๋ผ three.js useTexture๊ฐ€ ๋ฐ›์Šต๋‹ˆ๋‹ค โ€” WebGL ํ…์Šค์ฒ˜๋ผ CORS ํ—ค๋”๊ฐ€ ํ•„์š”ํ•˜๊ณ , CloudFront์˜ Managed-SimpleCORS ์ •์ฑ…์ด ๊ทธ๊ฒƒ์„ ์ฑ„์›๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ๊ณผ ํ•จ์ •์€ ์•„๋ž˜ ์‹ค๋‚ด ์ง€๋„ ์ •์  ์ž์‚ฐ (CDN)์— ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฐ„ํŒ์„ ์ฐ์œผ๋ฉด ๋‚ด ์œ„์น˜๊ฐ€ ์žกํžŒ๋‹ค

OCR ์ตœ์ ํ™”

ํ•˜๋‹จ + ์นด๋ฉ”๋ผ๋กœ ๋งค์žฅ ๋กœ๊ณ ๋ฅผ ์ฐ์œผ๋ฉด POST /api/v1/ocr/locations/recognize๋กœ ๋ณด๋‚ด๊ณ , /scan-map์˜ ์ „์ฒด์ธต 3D ์ง€๋„์— ํ•€์œผ๋กœ ๋‚ด ์œ„์น˜๋ฅผ ๋„์›๋‹ˆ๋‹ค. ์—…๋กœ๋“œ ์ „ ์ „์ฒ˜๋ฆฌ๋กœ ํ‰๊ท  ์šฉ๋Ÿ‰์„ โˆ’85.0%(4.61MB โ†’ 696KB) ์ค„์—ฌ ์‘๋‹ต ์‹œ๊ฐ„๋„ โˆ’25.0%(1.60์ดˆ โ†’ 1.20์ดˆ)๊ฐ€ ๋์Šต๋‹ˆ๋‹ค.

๊ฑฐ๊ธฐ์„œ AI ์ฝ”์Šค ์ƒ์„ฑํ•˜๊ธฐ๋ฅผ ๋ˆ„๋ฅด๋ฉด /ai-course?from=scan์ด ์—ด๋ฆฌ๊ณ  1๋ฒˆ ์žฅ์†Œ๊ฐ€ ๋ฐฉ๊ธˆ ์ธ์‹ํ•œ ๋งค์žฅ์œผ๋กœ ์ฑ„์›Œ์ง‘๋‹ˆ๋‹ค. ์ด ์œ„์น˜ ์ •๋ณด๋Š” ๊ทธ ๋‘ ํ™”๋ฉด์—๋งŒ ๋‚จ๊ณ , ํƒญยทํ—ค๋”ยท๋’ค๋กœ๊ฐ€๊ธฐ๋กœ ๋‚˜๊ฐ€๋ฉด ๋ฐ”๋กœ ์ง€์›๋‹ˆ๋‹ค.

ํ•œ ํ™”๋ฉด, ๋„ค ๊ฐœ ์–ธ์–ด

next-intl๋กœ ํ•œ๊ตญ์–ด ยท ์ค‘๊ตญ์–ด ยท ์ผ๋ณธ์–ด ยท ์˜์–ด๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ๊ณ ์ • UI ๋ฌธ๊ตฌ๋Š” messages/{locale}.json ๋„ค ์นดํƒˆ๋กœ๊ทธ์— ๊ฐ™์€ ํ‚ค๋กœ ๋“ค์–ด๊ฐ€์•ผ ํ•˜๊ณ , ํ‚ค ๊ตฌ์กฐ๊ฐ€ ์–ด๊ธ‹๋‚˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ์–ธ์–ด ํŒŒ์ผ์— ํ•œ๊ตญ์–ด๊ฐ€ ์„ž์ด๋ฉด pnpm test:i18n์ด ์žก์Šต๋‹ˆ๋‹ค.

์ „์ฒด ๊ตฌ์กฐ ์•ˆ์—์„œ ์ด๊ณณ

๊ธฐ์ˆ  ์Šคํƒ

DITTO๋Š” ์ €์žฅ์†Œ ์…‹์— ๊ฑธ์ณ ์žˆ์Šต๋‹ˆ๋‹ค.

์ €์žฅ์†Œ ๋ฌด์—‡
Ditto-FrontEnd (์—ฌ๊ธฐ) Next.js 16 App Router ยท Three.js 3D ์ง€๋„ ยท PWA
Ditto-BackEnd Spring Boot 3 ยท Java 17 ยท MyBatis ยท Oracle
Ditto-AI ๋žŒ๋‹ค 8์ข… ยท RAG ์ถ”์ฒœ ํŒŒ์ดํ”„๋ผ์ธ ยท Azure OpenAI ยท Tavily

๋ธŒ๋ผ์šฐ์ €๋Š” ํ•ญ์ƒ Next๋งŒ ๋ด…๋‹ˆ๋‹ค. next.config.mjs์˜ rewrites๊ฐ€ /api/*๋ฅผ ALB๋กœ ๋„˜๊ธฐ๋ฏ€๋กœ CORS ์„ค์ •์ด ํ•„์š” ์—†๊ณ , ๋ฐฑ์—”๋“œ ์ฃผ์†Œ๋Š” ๋ฒˆ๋“ค์— ๋…ธ์ถœ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋”ํ˜„๋Œ€ ์„œ์šธ์„ ๋””ํ† ํ•œ๋‹ค

Ditto / หˆdษชt.oสŠ / โ€” ๋”ฐ๋ผํ•˜๋‹ค, ๋‚˜๋„(me too). ๋ชจ๋‘๊ฐ€ ๋”ฐ๋ผํ•˜๊ณ  ์‹ถ์€ ๊ฒƒ.


๊ฐœ๋ฐœ ๊ฐ€์ด๋“œ

ํ•ต์‹ฌ ์‚ฌ์šฉ์ž ํ๋ฆ„

๊ตญ๊ฐ€๋ณ„ ํŠธ๋ Œ๋“œ ํƒ์ƒ‰
โ†’ AI ๋งž์ถค ์ฝ”์Šค ์ƒ์„ฑ
โ†’ ์‚ฌ์šฉ์ž ์ฝ”์Šค ์ปค์Šคํ…€
โ†’ ๋ชจ๋ฐ”์ผ ์‹ค๋‚ด ๊ธธ์ฐพ๊ธฐ
โ†’ ์—ฌํ–‰์ž ์ปค๋ฎค๋‹ˆํ‹ฐ ๊ณต์œ 

ํ˜„์žฌ ๋‹จ๊ณ„๋Š” ๋ชจ๋ฐ”์ผ PWA ํ”„๋ก ํŠธ์—”๋“œ์ž…๋‹ˆ๋‹ค. ํ™”๋ฉด์€ 430px ์•ฑ ์…ธ๋กœ ๊ณ ์ •๋˜๊ณ , ํ™ˆ ํ™”๋ฉด ์„ค์น˜ยทstandalone ํ‘œ์‹œ๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

๊ธฐ์ˆ  ์Šคํƒ

์˜์—ญ ๊ธฐ์ˆ  ์ฑ…์ž„
Framework Next.js App Router ๋ผ์šฐํŒ…, ์„œ๋ฒ„ ๋ Œ๋”๋ง, ๋นŒ๋“œ
UI React ์ปดํฌ๋„ŒํŠธ์™€ ํ™”๋ฉด ์ƒํ˜ธ์ž‘์šฉ
Language JavaScript ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ
Styling Tailwind CSS ๋ฐ˜์‘ํ˜• UI์™€ ๋””์ž์ธ ํ† ํฐ
3D Three.js ยท @react-three/fiber ยท @react-three/drei 8๊ฐœ ์ธต ์‹ค๋‚ด ์ง€๋„์™€ ์ฝ”์Šค ๊ฒฝ๋กœ ๋ Œ๋”๋ง
HTTP Axios ๋ธŒ๋ผ์šฐ์ € API ํ†ต์‹  ๊ณตํ†ต ์ธ์Šคํ„ด์Šค
Client state Zustand ์—ฌ๋Ÿฌ ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ณต์œ ํ•˜๋Š” ์ƒํƒœ
Internationalization next-intl ์„œ๋ฒ„ ๋ Œ๋”๋ง๊ณผ ํด๋ผ์ด์–ธํŠธ UI์˜ ํ•œยท์ค‘ยท์ผยท์˜ ๊ณ ์ • ๋ฌธ๊ตฌ
Package manager pnpm ์˜์กด์„ฑ ๋ฐ ์ž ๊ธˆ ํŒŒ์ผ ๊ด€๋ฆฌ

์„ค์น˜๋œ ์ •ํ™•ํ•œ ๋ฒ„์ „์€ package.json๊ณผ pnpm-lock.yaml์„ ๊ธฐ์ค€์œผ๋กœ ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿฆด Commit Convention

์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ ๊ทœ์น™

์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€๋Š” ์•„๋ž˜ ํ˜•์‹์œผ๋กœ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค:

<ํƒ€์ž…>:<๋‚ด์šฉ>

ex) feat: ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ ์ถ”๊ฐ€

ํƒ€์ž… ์„ค๋ช…
feat ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ ์ถ”๊ฐ€
fix ๋ฒ„๊ทธ ์ˆ˜์ •
docs ๋ฌธ์„œ ์ˆ˜์ • (README ๋“ฑ)
style ์ฝ”๋“œ ํฌ๋งทํŒ…, ์„ธ๋ฏธ์ฝœ๋ก  ๋ˆ„๋ฝ ๋“ฑ
refactor ์ฝ”๋“œ ๋ฆฌํŒฉํ† ๋ง (๊ธฐ๋Šฅ ๋ณ€ํ™” ์—†์Œ)
test ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ถ”๊ฐ€ ๋˜๋Š” ์ˆ˜์ •
chore ๊ธฐํƒ€ ๋ณ€๊ฒฝ์‚ฌํ•ญ (๋นŒ๋“œ ์„ค์ •, ํŒจํ‚ค์ง€ ๋“ฑ)

๐ŸŒ™ Git Flow ๋ธŒ๋žœ์น˜ ์ „๋žต

์ฃผ์š” ๋ธŒ๋žœ์น˜

๋ธŒ๋žœ์น˜ ์ด๋ฆ„ ์šฉ๋„
main ๋ฐฐํฌ(Release)๊ฐ€ ์ด๋ฃจ์–ด์ง€๋Š” ์•ˆ์ •์ ์ธ ์ฝ”๋“œ
dev ๋‹ค์Œ ๋ฆด๋ฆฌ์Šค๋ฅผ ์ค€๋น„ํ•˜๋Š” ๊ฐœ๋ฐœ ๋ธŒ๋žœ์น˜

๋ธŒ๋žœ์น˜ ๋„ค์ด๋ฐ

๋ธŒ๋žœ์น˜ ๋„ค์ด๋ฐ์€ ์•„๋ž˜ ํ˜•์‹์œผ๋กœ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค:

<ํƒ€์ž…>/<์ด์Šˆ๋ฒˆํ˜ธ>

ex)feat/#23
ํƒ€์ž… ์„ค๋ช…
feat ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ ์ž‘์—…
fix ๋ฒ„๊ทธ ์ˆ˜์ • ์ž‘์—…
hotfix ๊ธ‰ํ•œ ์ˆ˜์ • ์ž‘์—… (๋ฐฐํฌ ํ›„ ๋“ฑ)
refactor ์ฝ”๋“œ ๋ฆฌํŒฉํ† ๋ง
docs ๋ฌธ์„œ ์ž‘์—…
chore ๊ธฐํƒ€ ์ž‘์—… (์„ค์ •, ํŒจํ‚ค์ง€ ๋“ฑ)

โ˜ธ๏ธ Git ๋ธŒ๋žœ์น˜ ๋ฐ ๊ฐœ๋ฐœ ํ”„๋กœ์„ธ์Šค

  1. ๊ธฐ๋Šฅ ๊ฐœ๋ฐœ ์‹œ์ž‘

    • dev ๋ธŒ๋žœ์น˜์—์„œ ์ƒˆ๋กœ์šด feat ๋ธŒ๋žœ์น˜๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๊ฐœ๋ฐœ์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.
  2. ๊ธฐ๋Šฅ ๊ฐœ๋ฐœ ๋ฐ ์ปค๋ฐ‹

    • feat ๋ธŒ๋žœ์น˜์—์„œ ๊ธฐ๋Šฅ์„ ์™„์„ฑํ•˜๊ณ  ์ปค๋ฐ‹์„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  3. ์ฝ”๋“œ ๋ฆฌ๋ทฐ ๋ฐ ๋ณ‘ํ•ฉ

    • ๊ฐœ๋ฐœ ์™„๋ฃŒ ํ›„, feat ๋ธŒ๋žœ์น˜์—์„œ dev ๋ธŒ๋žœ์น˜๋กœ PR(Pull Request)์„ ์ƒ์„ฑํ•˜์—ฌ ์ฝ”๋“œ ๋ฆฌ๋ทฐ๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค.
    • ๋ฆฌ๋ทฐ๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด dev ๋ธŒ๋žœ์น˜์— ๋ณ‘ํ•ฉํ•ฉ๋‹ˆ๋‹ค.
  4. ํ…Œ์ŠคํŠธ

    • dev ๋ธŒ๋žœ์น˜์—์„œ ๋ฐฐํฌ ์ „ ์ตœ์ข… ๊ธฐ๋Šฅ๋“ค์ด ์•ˆ์ •์ ์œผ๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค.
  5. ๋ฐฐํฌ

    • ํ…Œ์ŠคํŠธ๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด dev ๋ธŒ๋žœ์น˜๋ฅผ main ๋ธŒ๋žœ์น˜์— ๋ณ‘ํ•ฉํ•˜์—ฌ ์ตœ์ข… ๋ฐฐํฌ๋ฅผ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

์‹œ์ž‘ํ•˜๊ธฐ

์š”๊ตฌ ํ™˜๊ฒฝ

  • Node.js 20.9 ์ด์ƒ์„ ๋งŒ์กฑํ•˜๋Š” LTS ๋ฒ„์ „ ๊ถŒ์žฅ
  • pnpm 9.12.3

์„ค์น˜ ๋ฐ ์‹คํ–‰

git clone https://github.com/HDF-final/Ditto-FrontEnd.git
cd Ditto-FrontEnd
pnpm install
cp .env.example .env.local
pnpm dev

๋ธŒ๋ผ์šฐ์ €์—์„œ http://localhost:3000์„ ์—ฝ๋‹ˆ๋‹ค.

3000๋ฒˆ ํฌํŠธ ์‹คํ–‰๊ณผ ์ข…๋ฃŒ

๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰:

pnpm dev

์„œ๋ฒ„๊ฐ€ ์‹คํ–‰ ์ค‘์ธ ํ„ฐ๋ฏธ๋„์—์„œ Ctrl+C๋ฅผ ๋ˆ„๋ฅด๋ฉด ์ •์ƒ ์ข…๋ฃŒ๋ฉ๋‹ˆ๋‹ค.

ํ„ฐ๋ฏธ๋„์„ ๋‹ซ์•˜๋Š”๋ฐ ํ”„๋กœ์„ธ์Šค๊ฐ€ ๋‚จ์•„ ์žˆ๋‹ค๋ฉด ๋จผ์ € PID๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

lsof -nP -iTCP:3000 -sTCP:LISTEN

ํ‘œ์‹œ๋œ PID๋งŒ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.

kill <PID>

3000๋ฒˆ ํฌํŠธ๋ฅผ ๋‹ค๋ฅธ ํ”„๋กœ์„ธ์Šค๊ฐ€ ์‚ฌ์šฉ ์ค‘์ผ ๋•Œ ์ž„์‹œ๋กœ ๋‹ค๋ฅธ ํฌํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด:

pnpm exec next dev --port 3001

๋ผ์šฐํŒ…

Next.js App Router๋Š” src/app ์•„๋ž˜์˜ ํด๋”๋ฅผ URL ๊ฒฝ๋กœ๋กœ ์‚ฌ์šฉํ•˜๊ณ , ํด๋” ์•ˆ์˜ page.js๊ฐ€ ์‹ค์ œ ํŽ˜์ด์ง€๋ฅผ ๋…ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

URL ํŒŒ์ผ ์šฉ๋„
/ src/app/page.js ํ™ˆ
/login src/app/login/page.js ๋กœ๊ทธ์ธ (UI ๊ฒ€์ฆ๋งŒ, ์„ฑ๊ณต ์‹œ /๋กœ ์ด๋™)
/signup src/app/signup/page.js ํšŒ์›๊ฐ€์ž… (UI ๊ฒ€์ฆ๋งŒ, ์„ฑ๊ณต ์‹œ /country๋กœ ์ด๋™)
/country src/app/country/page.js ๊ตญ๊ฐ€ยท์–ธ์–ด ์„ ํƒ (์„ฑ๊ณต ์‹œ /persona?lang=โ€ฆ)
/persona src/app/persona/page.js ์‡ผํ•‘ ํƒ€์ž…(ํŽ˜๋ฅด์†Œ๋‚˜) ์„ ํƒ (์„ฑ๊ณต ์‹œ /๋กœ ์ด๋™)
/ai-course src/app/ai-course/page.js AI ์ฝ”์Šค ๋งŒ๋“ค๊ธฐ
/scan-map src/app/scan-map/page.js OCR ๋‚ด ์œ„์น˜ 3D ์ง€๋„
/courses src/app/courses/page.js ์ฝ”์Šค ๋ฆฌ์ŠคํŠธ
/community src/app/community/page.js ์—ฌํ–‰์ž ์ปค๋ฎค๋‹ˆํ‹ฐ
/news src/app/news/page.js ๋‰ด์Šคํ”ผ๋“œ ๋ชฉ๋ก
/news/[slug] src/app/news/[slug]/page.js ๋‰ด์Šคํ”ผ๋“œ ์ƒ์„ธ (์Šฌ๋Ÿฌ๊ทธ ์กฐํšŒ)
/mypage src/app/mypage/page.js ๋งˆ์ดํŽ˜์ด์ง€

์˜ˆ๋ฅผ ๋“ค์–ด /about ํŽ˜์ด์ง€๋ฅผ ์ถ”๊ฐ€ํ•˜๋ ค๋ฉด ๋‹ค์Œ ํŒŒ์ผ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

src/app/about/page.js

page.js์™€ layout.js๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ Server Component์ž…๋‹ˆ๋‹ค. ์ƒํƒœ, ์ด๋ฒคํŠธ, ๋ธŒ๋ผ์šฐ์ € API ๋˜๋Š” Zustand๊ฐ€ ํ•„์š”ํ•œ ์ž‘์€ ์ปดํฌ๋„ŒํŠธ์—๋งŒ ํŒŒ์ผ ์ƒ๋‹จ์— "use client"๋ฅผ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค.

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

Ditto-FrontEnd/
โ”œโ”€โ”€ public/                    # ์ด๋ฏธ์ง€, ์•„์ด์ฝ˜ ๋“ฑ ์ •์  ํŒŒ์ผ
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ app/                   # App Router ํŽ˜์ด์ง€์™€ ๋ ˆ์ด์•„์›ƒ
โ”‚   โ”‚   โ”œโ”€โ”€ page.js            # ํ™ˆ
โ”‚   โ”‚   โ”œโ”€โ”€ ai-course/page.js  # AI ์ฝ”์Šค ๋งŒ๋“ค๊ธฐ
โ”‚   โ”‚   โ”œโ”€โ”€ news/page.js       # ๋‰ด์Šคํ”ผ๋“œ
โ”‚   โ”‚   โ”œโ”€โ”€ mypage/page.js     # ๋งˆ์ดํŽ˜์ด์ง€
โ”‚   โ”‚   โ”œโ”€โ”€ login/page.js      # ๋กœ๊ทธ์ธ
โ”‚   โ”‚   โ”œโ”€โ”€ signup/page.js     # ํšŒ์›๊ฐ€์ž…
โ”‚   โ”‚   โ”œโ”€โ”€ country/page.js    # ๊ตญ๊ฐ€ยท์–ธ์–ด ์„ ํƒ
โ”‚   โ”‚   โ”œโ”€โ”€ persona/page.js    # ์‡ผํ•‘ ํƒ€์ž…(ํŽ˜๋ฅด์†Œ๋‚˜) ์„ ํƒ
โ”‚   โ”‚   โ”œโ”€โ”€ layout.js          # ์ „์—ญ HTML ๋ ˆ์ด์•„์›ƒยทPWA viewport
โ”‚   โ”‚   โ”œโ”€โ”€ manifest.js        # ์›น ์•ฑ ๋งค๋‹ˆํŽ˜์ŠคํŠธ
โ”‚   โ”‚   โ”œโ”€โ”€ icon.js            # PWA ์•„์ด์ฝ˜
โ”‚   โ”‚   โ””โ”€โ”€ globals.css        # ์ „์—ญ ์Šคํƒ€์ผ๊ณผ Tailwind ํ† ํฐ
โ”‚   โ”œโ”€โ”€ components/
โ”‚   โ”‚   โ”œโ”€โ”€ home/              # ํ™ˆ ํ™”๋ฉด ์ „์šฉ ์„น์…˜๊ณผ UI
โ”‚   โ”‚   โ”œโ”€โ”€ ai-course/         # AI ์ฝ”์Šค ์ƒ์„ฑยทํŽธ์ง‘ ํ™”๋ฉด ์ „์šฉ UI
โ”‚   โ”‚   โ”œโ”€โ”€ news/              # ๋‰ด์Šคํ”ผ๋“œ ํ™”๋ฉด ์ „์šฉ UI
โ”‚   โ”‚   โ”œโ”€โ”€ mypage/            # ๋งˆ์ดํŽ˜์ด์ง€ ํ™”๋ฉด ์ „์šฉ UI
โ”‚   โ”‚   โ”œโ”€โ”€ auth/              # ๋กœ๊ทธ์ธยท๊ฐ€์ž…ยท๊ตญ๊ฐ€ยทํŽ˜๋ฅด์†Œ๋‚˜ ์˜จ๋ณด๋”ฉ UI
โ”‚   โ”‚   โ”œโ”€โ”€ common/            # ๋„๋ฉ”์ธ์— ๋ฌถ์ด์ง€ ์•Š๋Š” ์žฌ์‚ฌ์šฉ UI
โ”‚   โ”‚   โ””โ”€โ”€ layout/            # ์•ฑ ์…ธ, ํ—ค๋”, ํ•˜๋‹จ ํƒญ, PWA ๋“ฑ๋ก
โ”‚   โ”œโ”€โ”€ lib/
โ”‚   โ”‚   โ”œโ”€โ”€ api/               # Axios ๊ณตํ†ต ์„ค์ •
โ”‚   โ”‚   โ”œโ”€โ”€ pwa/               # ์•ฑ ์•„์ด์ฝ˜ ๋ Œ๋”๋Ÿฌ
โ”‚   โ”‚   โ”œโ”€โ”€ fixtures/          # ํ™”๋ฉด ๊ฐœ๋ฐœ์šฉ ์ •์  ๋”๋ฏธ ๋ฐ์ดํ„ฐ
โ”‚   โ”‚   โ””โ”€โ”€ utils/             # ์ˆœ์ˆ˜ ํ•จ์ˆ˜์™€ ๋ฒ”์šฉ ์œ ํ‹ธ๋ฆฌํ‹ฐ
โ”‚   โ””โ”€โ”€ stores/                # Zustand ์ „์—ญ ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ
โ”œโ”€โ”€ .env.example               # ๊ณต๊ฐœ ๊ฐ€๋Šฅํ•œ ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์˜ˆ์‹œ
โ”œโ”€โ”€ AGENTS.md                  # ์—์ด์ „ํŠธ ์ž‘์—… ๊ทœ์น™
โ”œโ”€โ”€ eslint.config.mjs
โ”œโ”€โ”€ next.config.mjs
โ””โ”€โ”€ package.json

components/common์—๋Š” ๋ฒ„ํŠผ, ์„ ํƒ๊ธฐ, ๋นˆ ์ƒํƒœ์ฒ˜๋Ÿผ ์—ฌ๋Ÿฌ ๋„๋ฉ”์ธ์—์„œ ๊ณต์œ ํ•˜๋Š” UI๋ฅผ ๋‘ก๋‹ˆ๋‹ค. components/layout์—๋Š” ์•ฑ ์…ธ, ํ—ค๋”, ํ•˜๋‹จ ํƒญ์ฒ˜๋Ÿผ ํŽ˜์ด์ง€ ๊ณจ๊ฒฉ์„ ๋งŒ๋“œ๋Š” UI๋ฅผ ๋‘ก๋‹ˆ๋‹ค. ํŠน์ • ํŽ˜์ด์ง€๋‚˜ ๋„๋ฉ”์ธ์—์„œ๋งŒ ์“ฐ๋Š” ์ปดํฌ๋„ŒํŠธ๋Š” components/home, components/ai-course, components/news, components/mypage, components/auth ์•„๋ž˜์— ๋จผ์ € ๋ฐฐ์น˜ํ•ฉ๋‹ˆ๋‹ค.

lib/fixtures๋Š” API ์—ฐ๋™ ์ „ ํ™”๋ฉด์„ ๊ตฌ์„ฑํ•˜๊ธฐ ์œ„ํ•œ ์ •์  ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ๋งŒ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๊ตญ๊ฐ€ ๋ชฉ๋ก์€ lib/fixtures/countries.js, ์‡ผํ•‘ ํƒ€์ž…์€ lib/fixtures/personas.js๋ฅผ ๋‹จ์ผ ์†Œ์Šค๋กœ ์”๋‹ˆ๋‹ค. lib/utils๋Š” ๋‚ ์งœ ํฌ๋งทํ„ฐ, ๋ฌธ์ž์—ด ๋ณ€ํ™˜, ๊ฐ’ ๊ฒ€์ฆ์ฒ˜๋Ÿผ React์™€ ๋ธŒ๋ผ์šฐ์ € ์ƒํƒœ์— ์˜์กดํ•˜์ง€ ์•Š๋Š” ์ˆœ์ˆ˜ ์œ ํ‹ธ๋ฆฌํ‹ฐ๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. API endpoint๋Š” lib/api์—์„œ ๋ฐฑ์—”๋“œ ๊ณ„์•ฝ์ด ํ™•์ •๋œ ๋’ค ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

์ฝ”์Šค ํŽธ์ง‘์ฒ˜๋Ÿผ ์—ฌ๋Ÿฌ Client Component๊ฐ€ ๊ณต์œ ํ•˜๋Š” ๋‹ค๋‹จ๊ณ„ ์ž‘์„ฑ ์ƒํƒœ๋Š” src/stores/use-course-editor-store.js์— ๋‘ก๋‹ˆ๋‹ค. ํ•˜๋‹จ + ์นด๋ฉ”๋ผ๋กœ ๋กœ๊ณ ๋ฅผ OCR ์Šค์บ”ํ•˜๋ฉด POST /api/v1/ocr/locations/recognize๋กœ ๊ฐ„ํŒ ์ด๋ฏธ์ง€๋ฅผ ๋ณด๋‚ด๊ณ , /scan-map์—์„œ ์ „์ฒด์ธต 3D ์ง€๋„์— ์ž‘์€ ์ง€๋„ ํ•€์œผ๋กœ ๋‚ด ์œ„์น˜๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. AI ์ฝ”์Šค ์ƒ์„ฑํ•˜๊ธฐ๋ฅผ ๋ˆ„๋ฅด๋ฉด /ai-course?from=scan ๋นˆ ์ฝ”์Šค ํŽธ์ง‘ ํ™”๋ฉด์ด ์—ด๋ฆฌ๊ณ  1๋ฒˆ ์žฅ์†Œ๊ฐ€ ๋ฐฉ๊ธˆ ์ธ์‹ํ•œ ๋งค์žฅ์œผ๋กœ ์ฑ„์›Œ์ง‘๋‹ˆ๋‹ค. ์ด OCR ๋‚ด ์œ„์น˜๋Š” ๊ทธ ๋‘ ํ™”๋ฉด์—๋งŒ ์œ ์ง€๋˜๊ณ , ํƒญยทํ—ค๋”ยท๋’ค๋กœ๊ฐ€๊ธฐ๋กœ ๋‚˜๊ฐ€๋ฉด ๋ฐ”๋กœ ์ง€์›๋‹ˆ๋‹ค. ๊ตญ๊ฐ€ยท์–ธ์–ด๋Š” src/stores/use-preference-store.js์—์„œ ์„œ๋กœ ๋…๋ฆฝ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ณ , SSR์—์„œ๋„ ์ฝ์„ ์ˆ˜ ์žˆ๋Š” ditto-country, ditto-language ์ฟ ํ‚ค์— ๋ณด์กดํ•ฉ๋‹ˆ๋‹ค. ์ธ์ฆ ์ •๋ณด์™€ ์„ธ์…˜ ๊ฐ’์€ ๋ธŒ๋ผ์šฐ์ € ์ €์žฅ์†Œ์— ๋„ฃ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•œ ํ™”๋ฉด ๋‚ด๋ถ€์—์„œ๋งŒ ํ•„์š”ํ•œ ์ƒํƒœ๋Š” ์ „์—ญ store๋กœ ์˜ฌ๋ฆฌ์ง€ ์•Š๊ณ  ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ์˜ React state๋กœ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

๊ตญ๊ฐ€ยท์–ธ์–ด ์„ค์ • ์šฐ์„ ์ˆœ์œ„๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๋กœ๊ทธ์ธ ์‚ฌ์šฉ์ž โ†’ GET /users/me์˜ DB ์„ค์ •
๋น„๋กœ๊ทธ์ธ ์‚ฌ์šฉ์ž โ†’ ๊ตญ๊ฐ€ยท์–ธ์–ด ์ฟ ํ‚ค
์ €์žฅ๋œ ์„ค์ • ์—†์Œ โ†’ ๋ธŒ๋ผ์šฐ์ € ์–ธ์–ด + ๊ธฐ๋ณธ ๊ตญ๊ฐ€ KR

์ „์—ญ ๋ ˆ์ด์•„์›ƒ์ด ์š”์ฒญ ์ฟ ํ‚ค์™€ Accept-Language๋ฅผ ์ฝ์–ด Zustand provider์˜ ์ฒซ ์ƒํƒœ์™€ <html lang>์„ ํ•จ๊ป˜ ์ดˆ๊ธฐํ™”ํ•˜๋ฏ€๋กœ, hydration ์ดํ›„ ์–ธ์–ด๊ฐ€ ๋’ค๋Šฆ๊ฒŒ ๋ฐ”๋€Œ๋Š” ํ˜„์ƒ์„ ๋ง‰์Šต๋‹ˆ๋‹ค. ๊ตญ๊ฐ€๋ฅผ ๋ฐ”๊พธ๋ฉด ๊ทธ ๊ตญ๊ฐ€์˜ ๊ธฐ๋ณธ ์–ธ์–ด๋ฅผ ํ•จ๊ป˜ ์„ ํƒํ•˜๋˜, ์‚ฌ์šฉ์ž๊ฐ€ ์–ธ์–ด๋ฅผ ์ง์ ‘ ์„ ํƒํ•œ ๋’ค์—๋Š” ๊ตญ๊ฐ€๋ฅผ ๋ฐ”๊ฟ”๋„ ํ•ด๋‹น ์–ธ์–ด๋ฅผ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค. ํ—ค๋”์—์„œ๋Š” ๊ตญ๊ฐ€์™€ ํ™”๋ฉด ์–ธ์–ด๋ฅผ ๋ณ„๋„ ์„ ํƒ๊ธฐ๋กœ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

์„ ํƒ ์–ธ์–ด๋Š” Backend ๋™์  ์ฝ˜ํ…์ธ ์—๋„ ๋™์ผํ•˜๊ฒŒ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋ธŒ๋ผ์šฐ์ €์˜ ๋ชจ๋“  Axios ์š”์ฒญ์€ ๊ณตํ†ต src/lib/api/client.js ์ธํ„ฐ์…‰ํ„ฐ๊ฐ€ ditto-language ์ฟ ํ‚ค๋ฅผ ์ฝ์–ด Accept-Language ํ—ค๋”์— ๋„ฃ์Šต๋‹ˆ๋‹ค. ๋‰ด์Šค์ฒ˜๋Ÿผ Server Component๊ฐ€ ์ง์ ‘ ํ˜ธ์ถœํ•˜๋Š” ์š”์ฒญ์€ src/lib/api/server-language.js๊ฐ€ ์š”์ฒญ ์ฟ ํ‚ค๋ฅผ ์ฝ์–ด ๊ฐ™์€ ํ—ค๋”๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ์ง€์› ๊ฐ’์€ ko, zh, ja, en์ด๋ฉฐ ๊ฐ’์ด ์—†๊ฑฐ๋‚˜ ์ž˜๋ชป๋˜๋ฉด ko๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ํ˜ธ์ถœ๋ถ€๊ฐ€ Accept-Language๋ฅผ ์ง์ ‘ ์ง€์ •ํ•œ ๊ฒฝ์šฐ ๊ณตํ†ต ๊ณ„์ธต์€ ํ•ด๋‹น ๊ฐ’์„ ๋ฎ์–ด์“ฐ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

ํ•œยท์ค‘ยท์ผยท์˜ UI ๋‹ค๊ตญ์–ด

next-intl ๊ธฐ๋ฐ˜์œผ๋กœ ํ•œ๊ตญ์–ด(ko), ์ค‘๊ตญ์–ด(zh), ์ผ๋ณธ์–ด(ja), ์˜์–ด(en)๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ๋ฒˆ์—ญ ์นดํƒˆ๋กœ๊ทธ๋Š” messages/{locale}.json, ์š”์ฒญ๋ณ„ ์–ธ์–ด ๊ฒฐ์ •์€ src/i18n/request.js, ์ง€์› ์–ธ์–ด์™€ ๊ธฐ๋ณธ๊ฐ’์€ src/i18n/config.js์—์„œ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

๊ณ ์ • UI ๋ฌธ๊ตฌ๋Š” ๋ฉ”์‹œ์ง€ ํ‚ค๋กœ ๊ด€๋ฆฌํ•˜๋ฉฐ, ์–ธ์–ด ์„ ํƒ ์ฟ ํ‚ค๊ฐ€ ๋ฐ”๋€Œ๋ฉด ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ๊นŒ์ง€ ๊ฐ™์€ ์–ธ์–ด๋กœ ๋‹ค์‹œ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค. ์ƒˆ ๋ฌธ๊ตฌ๋ฅผ ์ถ”๊ฐ€ํ•  ๋•Œ๋Š” ๋„ค ์นดํƒˆ๋กœ๊ทธ์— ๋™์ผํ•œ ํ‚ค๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ‚ค ๊ตฌ์กฐ ์ผ์น˜์™€ ๋‹ค๋ฅธ ์–ธ์–ด ํŒŒ์ผ์˜ ํ•œ๊ตญ์–ด ํ˜ผ์ž…์€ pnpm test:i18n์œผ๋กœ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฒˆ ๋‹จ๊ณ„๋Š” ๋ฉ”๋‰ด, ์ œ๋ชฉ, ๋ฒ„ํŠผ, ์ž…๋ ฅ ์•ˆ๋‚ด, ์˜ค๋ฅ˜์™€ ์ ‘๊ทผ์„ฑ ๋ฌธ๊ตฌ ๊ฐ™์€ ํ”„๋ก ํŠธ ๊ณ ์ • UI๋งŒ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. ๋‰ด์Šคยท์ปค๋ฎค๋‹ˆํ‹ฐ ๊ฒŒ์‹œ๋ฌผยท์ฝ”์Šค๋ช…ยทAI ์‘๋‹ต์ฒ˜๋Ÿผ API๋‚˜ ์‚ฌ์šฉ์ž๊ฐ€ ๋งŒ๋“œ๋Š” ๋™์  ์ฝ˜ํ…์ธ ์˜ ์ž๋™ ๋ฒˆ์—ญ์€ Azure Translator ์—ฐ๋™ ๋‹จ๊ณ„์—์„œ ๋ณ„๋„๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ํ˜„์žฌ ๋‹จ๊ณ„์—๋Š” Azure ํ‚ค๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ธ์ฆยท์˜จ๋ณด๋”ฉ ์ด๋™ ์ •์ฑ…:

/signup โ†’ /country โ†’ /persona?lang=โ€ฆ โ†’ /
/login โ†’ /

Axios ์‚ฌ์šฉ๋ฒ•

๋ธŒ๋ผ์šฐ์ € API ํ†ต์‹ ์€ ๊ณตํ†ต Axios ์ธ์Šคํ„ด์Šค๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

import { apiClient } from "@/lib/api/client";

const { data } = await apiClient.get("/courses");
  • axios.create()๋ฅผ ๊ธฐ๋Šฅ ํŒŒ์ผ๋งˆ๋‹ค ์ƒˆ๋กœ ๋งŒ๋“ค์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • API ๊ธฐ์ค€ ์ฃผ์†Œ๋Š” NEXT_PUBLIC_API_BASE_URL๋กœ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • ํŒŒ์ผ ์—…๋กœ๋“œ๋ฅผ ์œ„ํ•ด Content-Type์„ ์ „์—ญ์œผ๋กœ ๊ณ ์ •ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ์ธ์ฆ ๋ฐฉ์‹์ด ํ™•์ •๋˜๋ฉด ๊ณตํ†ต ์ธํ„ฐ์…‰ํ„ฐ๋„ ๊ฐ™์€ ํŒŒ์ผ์—์„œ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • ํ˜„์žฌ ์ธ์Šคํ„ด์Šค๋Š” ๋ธŒ๋ผ์šฐ์ € ํ†ต์‹ ์šฉ์ž…๋‹ˆ๋‹ค. Server Component์˜ ์„œ๋ฒ„ ํ†ต์‹ ์€ ๋ฐฑ์—”๋“œ ๊ตฌ์กฐ ํ™•์ • ํ›„ ๋ณ„๋„ ์„œ๋ฒ„ ๋ชจ๋“ˆ๋กœ ๋ถ„๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

๋ฐฑ์—”๋“œ ํ”„๋ก์‹œ

๋ธŒ๋ผ์šฐ์ €๋Š” ํ•ญ์ƒ ๊ฐ™์€ ์˜ค๋ฆฌ์ง„์˜ /api/*๋กœ ์š”์ฒญํ•˜๊ณ , next.config.mjs์˜ rewrite๊ฐ€ ๋ฐฑ์—”๋“œ๋กœ ๋„˜๊น๋‹ˆ๋‹ค. CORS ์„ค์ • ์—†์ด ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ๊ณ , ์„ธ์…˜ ์ฟ ํ‚ค(JSESSIONID)๋„ ๊ฐ™์€ ์˜ค๋ฆฌ์ง„์œผ๋กœ ์˜ค๊ฐ‘๋‹ˆ๋‹ค.

๋ธŒ๋ผ์šฐ์ €  /api/v1/...  โ†’  Next rewrite  โ†’  ${API_PROXY_TARGET}/api/v1/...

๋ฐฑ์—”๋“œ ์ฃผ์†Œ๊ฐ€ ๋‹ค๋ฅด๋ฉด .env.local์˜ API_PROXY_TARGET๋งŒ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค. NEXT_PUBLIC_ ์ ‘๋‘์‚ฌ๊ฐ€ ์—†์œผ๋ฏ€๋กœ ๋ธŒ๋ผ์šฐ์ € ๋ฒˆ๋“ค์— ๋…ธ์ถœ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

โš ๏ธ rewrite ํ”„๋ก์‹œ ๊ธฐ๋ณธ ํƒ€์ž„์•„์›ƒ์€ 30์ดˆ์ž…๋‹ˆ๋‹ค

30์ดˆ๋ฅผ ๋„˜๊ธฐ๋Š” API๋Š” ํ”„๋ก์‹œ๊ฐ€ ๋จผ์ € ์†Œ์ผ“์„ ๋‹ซ์•„๋ฒ„๋ฆฝ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ๋ฐฑ์—”๋“œ์—๋Š” Broken pipe(ClientAbortException)๊ฐ€ ์ฐํžˆ๊ณ , ๋ธŒ๋ผ์šฐ์ €๋Š” Spring์ด ์ค€ ์  ์—†๋Š” Internal Server Error ๋ฌธ์ž์—ด์„ ๋ฐ›์Šต๋‹ˆ๋‹ค. ์‘๋‹ต ๋ด‰ํˆฌ({"success":false,...}) ํ˜•ํƒœ๊ฐ€ ์•„๋‹ˆ๋ฉด ์ด ๊ฒฝ์šฐ๋ฅผ ์˜์‹ฌํ•˜์„ธ์š”.

AI ์ฝ”์Šค ์ถ”์ฒœ์ด 40~50์ดˆ ๊ฑธ๋ฆฌ๋ฏ€๋กœ next.config.mjs์—์„œ experimental.proxyTimeout์„ 150์ดˆ๋กœ ๋Š˜๋ ค๋‘์—ˆ์Šต๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ(axios) ํƒ€์ž„์•„์›ƒ 120์ดˆ๋ณด๋‹ค ๊ธธ๊ฒŒ ์žก์•„, ์‹œ๊ฐ„ ์ดˆ๊ณผ ํŒ๋‹จ์€ ํ•ญ์ƒ axios๊ฐ€ ๋จผ์ € ํ•˜๊ณ  ํ™”๋ฉด์— ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“  ์•ˆ๋‚ด๊ฐ€ ๋œจ๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

ํƒ€์ž„์•„์›ƒ ๊ณ„์ธต์€ ์„ธ ๊ณณ์ž…๋‹ˆ๋‹ค โ€” axios 120์ดˆ < ํ”„๋ก์‹œ 150์ดˆ, ๊ทธ๋ฆฌ๊ณ  ๋ฐฑ์—”๋“œ. ์ƒˆ ์žฅ๊ธฐ API๋ฅผ ๋ถ™์ผ ๋•Œ ์ด ์ˆœ์„œ๋ฅผ ์œ ์ง€ํ•˜์„ธ์š”.

AI ์ฝ”์Šค ์ถ”์ฒœ (lib/api/ai-course.js)

POST /api/v1/ai/course-recommendations/chat ํ•˜๋‚˜๋กœ ์ƒ์„ฑยท๋‹ค๋“ฌ๊ธฐยท์žฌ์ถ”์ฒœ์„ ๋ชจ๋‘ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

ํ•˜๊ณ  ์‹ถ์€ ๊ฒƒ ๋ณด๋‚ด๋Š” ๊ฐ’
๋งž์ถค ์ฝ”์Šค ์ƒ์„ฑ (์ฒซ ์š”์ฒญ) sessionId ์—†์ด message๋งŒ
๋Œ€ํ™”๋กœ ๋‹ค๋“ฌ๊ธฐ / ์žฌ์ถ”์ฒœ ์ง์ „ ์‘๋‹ต์˜ sessionId + message
  • sessionId๋Š” ์„œ๋ฒ„๊ฐ€ ๋ฐœ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋งŒ๋“ค์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ํ•œ ํ„ด์— 40์ดˆ ์•ˆํŒŽ ๊ฑธ๋ฆฌ๋ฏ€๋กœ ์ด ์š”์ฒญ๋งŒ ํƒ€์ž„์•„์›ƒ์„ 120์ดˆ๋กœ ๋„“ํ˜€ ๋ณด๋ƒ…๋‹ˆ๋‹ค(๊ณตํ†ต ์ธ์Šคํ„ด์Šค ๊ธฐ๋ณธ๊ฐ’์€ 15์ดˆ).
  • ์‘๋‹ต์˜ places[].navigationKey๋ฅผ lib/navigation/course-routing-service.js์˜ resolveCoursePlace()๋กœ ์‹ค๋‚ด ์ง€๋„ ์žฅ์†Œ์— ๋งคํ•‘ํ•ฉ๋‹ˆ๋‹ค. ๋งค์นญ๋˜์ง€ ์•Š๋Š” ํ‚ค๋Š” ๊ธธ์ฐพ๊ธฐ๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฏ€๋กœ ์ฝ”์Šค์—์„œ ์ œ์™ธํ•ฉ๋‹ˆ๋‹ค.
  • ๋กœ๊ทธ์ธ ์„ธ์…˜์ด ํ•„์š”ํ•œ ์—”๋“œํฌ์ธํŠธ์ž…๋‹ˆ๋‹ค. ๋ฏธ์ธ์ฆ ์ƒํƒœ์—์„œ๋Š” 403์ด ์˜ค๊ณ  ํ™”๋ฉด์— ๋กœ๊ทธ์ธ ์•ˆ๋‚ด๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

๋‰ด์Šคํ”ผ๋“œ (lib/api/news.js, lib/api/news.server.js)

K-์ปฌ์ฒ˜ ํŠธ๋ Œ๋“œ ๋‰ด์Šคํ”ผ๋“œ ์กฐํšŒ API ์—”๋“œํฌ์ธํŠธ:

์šฉ๋„ ๋ฉ”์„œ๋“œ ์—”๋“œํฌ์ธํŠธ ํด๋ผ์ด์–ธํŠธ ํ•จ์ˆ˜ ์„œ๋ฒ„ ํ•จ์ˆ˜
๋‰ด์Šคํ”ผ๋“œ ๋ชฉ๋ก ์กฐํšŒ GET /api/v1/news?page={page}&size={size} getNewsFeeds() fetchNewsFeedsServer()
๋‰ด์Šคํ”ผ๋“œ ์ƒ์„ธ ์กฐํšŒ GET /api/v1/news/{newsId} getNewsFeedById(id) getNewsDetailById(id)
๊ฒ€์ƒ‰ ์œ ์ž… ์Šฌ๋Ÿฌ๊ทธ ์กฐํšŒ GET /api/v1/news/slug/{slug} getNewsFeedBySlug(slug) getNewsDetailBySlug(slug)
์‚ฌ์ดํŠธ๋งต ๋ชฉ๋ก ์กฐํšŒ GET /api/v1/news/sitemap getNewsFeedsForSitemap() getNewsSitemap()
  • ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ(src/app/news/page.js, src/app/news/[slug]/page.js, src/app/sitemap.js): lib/api/news.server.js๋ฅผ ํ†ตํ•ด SSR/SSG ๋ฐ ๋ฐฑ์—”๋“œ ๋ฏธ๊ฐ€๋™ ์‹œ ์•ˆ์ „ํ•œ ํด๋ฐฑ(fixtures) ์ฒ˜๋ฆฌ๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.
  • ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ: lib/api/news.js๋ฅผ ํ†ตํ•ด ๋ธŒ๋ผ์šฐ์ € ๊ณตํ†ต Axios ์ธ์Šคํ„ด์Šค๋กœ API ํ†ต์‹ ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

๊ด€๋ฆฌ์ž ํ™”๋ฉด

ROLE_ADMIN ๊ณ„์ •๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ์กฐํšŒ ์ „์šฉ ์šด์˜ ํ™”๋ฉด์ž…๋‹ˆ๋‹ค. ์ผ๋ฐ˜ ์„œ๋น„์Šค ํ—ค๋”ยทํ‘ธํ„ฐ์™€ ๋ถ„๋ฆฌ๋œ ์‚ฌ์ด๋“œ๋ฐ” ๋ ˆ์ด์•„์›ƒ์„ ์‚ฌ์šฉํ•˜๋ฉฐ, ๋ฐฑ์—”๋“œ๊ฐ€ S3์˜ ์ตœ์‹  ํŠธ๋ Œ๋“œ JSON์„ ์ฝ์–ด ์ œ๊ณตํ•œ ๊ฒฐ๊ณผ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.

ํ™”๋ฉด ๊ฒฝ๋กœ ๋‚ด์šฉ
์šด์˜ ๋Œ€์‹œ๋ณด๋“œ /admin ์‚ฐ์ถœ๋ฌผ ์ƒํƒœ, ๊ฒฝ๊ณ  ์ˆ˜, ๊ตญ๊ฐ€๋ณ„ TOP 10 ๋ฏธ๋ฆฌ๋ณด๊ธฐ
๊ตญ๊ฐ€๋ณ„ TOP 10 /admin/trends/rankings ํ•œ๊ตญยท์ค‘๊ตญยท์ผ๋ณธยท๋ฏธ๊ตญ ์ตœ์ข… ์ˆœ์œ„
๊ตญ๊ฐ€๋ณ„ ํ›„๋ณด๊ตฐ /admin/trends/candidates ๊ตญ๊ฐ€๋ณ„ ๊ต์ฐจ ๊ฒ€์ฆ ํ›„๋ณด๊ตฐ
YouTube ๊ธ‰์ƒ์Šน /admin/trends/youtube ์ตœ๊ทผ 7์ผ ์ „์ฒดยทํ•œ๊ตญยท์ผ๋ณธยท๋ฏธ๊ตญ ๊ธ‰์ƒ์Šน ์‹ ํ˜ธ
์Šน์ธ ๋Œ€๊ธฐ ์ฝ”์Šค ์ดˆ์•ˆ /admin/courses ๋ฐฐ์น˜๊ฐ€ ๋งŒ๋“  ์…€๋Ÿฝ ์ฝ”์Šค ์ดˆ์•ˆ โ€” ์นด๋“œ๋กœ ๋ณด๊ณ , ํŒ์—…์—์„œ ํŽธ์ง‘

ํŠธ๋ Œ๋“œ ํ™”๋ฉด์˜ 1์ฐจ ๋ฒ”์œ„๋Š” ์กฐํšŒ์™€ ์ƒˆ๋กœ๊ณ ์นจ๋งŒ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ์ˆœ์œ„ ์ˆ˜๋™ ๋ณ€๊ฒฝ, RDS ์˜ค๋ฒ„๋ผ์ด๋“œ, Lambda ์žฌ์‹คํ–‰์€ ํฌํ•จํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์Šน์ธ ๋Œ€๊ธฐ ์ฝ”์Šค ์ดˆ์•ˆ (/admin/courses)

ditto-celeb-warm-2 ๋ฐฐ์น˜๊ฐ€ ์…€๋Ÿฝ ํ•œ ๋ช…์„ ์กฐ์‚ฌํ•ด ๋งค์žฅ 3 + ์นดํŽ˜ 1 + ์—ฌ๊ฐ€ 1 ์งœ๋ฆฌ ์ฝ”์Šค ์ดˆ์•ˆ์„ ๋งŒ๋“ค์–ด Redis์— ํ•˜๋ฃจ ๋™์•ˆ ๋‘ก๋‹ˆ๋‹ค. ๊ด€๋ฆฌ์ž๊ฐ€ ๋ณด๊ณ  ์Šน์ธํ•ด์•ผ ์†๋‹˜์—๊ฒŒ ๋‚˜๊ฐ‘๋‹ˆ๋‹ค.

์ดˆ์•ˆ์ด ์นด๋“œ๋กœ ๋Š˜์–ด์„œ๊ณ , ์นด๋“œ๋ฅผ ๋ˆ„๋ฅด๋ฉด ํŒ์—… ํŽธ์ง‘๊ธฐ๊ฐ€ ์—ด๋ฆฝ๋‹ˆ๋‹ค. ํ™”๋ฉด์„ ํ†ต์งธ๋กœ ๋ฐ”๊พธ์ง€ ์•Š๋Š” ๊ฒƒ์€ ๊ด€๋ฆฌ์ž๊ฐ€ ๋ชฉ๋ก์œผ๋กœ ๋Œ์•„์˜ค๋Š” ๋น„์šฉ์„ ์—†์• ๊ธฐ ์œ„ํ•ด์„œ์ž…๋‹ˆ๋‹ค. Esc, ๋ฐฐ๊ฒฝ ํด๋ฆญ, ๋‹ซ๊ธฐ ๋ฒ„ํŠผ์œผ๋กœ ๋‹ซ์Šต๋‹ˆ๋‹ค.

์šฉ๋„ ๋ฉ”์„œ๋“œ ์—”๋“œํฌ์ธํŠธ ํด๋ผ์ด์–ธํŠธ ํ•จ์ˆ˜
์ดˆ์•ˆ ๋ชฉ๋ก ์กฐํšŒ GET /api/v1/admin/admin-courses getAdminCourses()
์˜ค๋Š˜ ์‹คํ–‰ ์ƒํ™ฉ ์กฐํšŒ GET /api/v1/admin/admin-courses/run getAdminCourseRun()
์ดˆ์•ˆ ์ƒ์„ธ ์กฐํšŒ GET /api/v1/admin/admin-courses/{celebrity} getAdminCourse(name)
์žฅ์†Œ ์นดํƒˆ๋กœ๊ทธ ์กฐํšŒ GET /api/v1/admin/admin-courses/places getAdminCoursePlaces()

ํŽธ์ง‘๊ธฐ์—์„œ ๊ณ ์น  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ

๋™์„  ๋“œ๋ž˜๊ทธ ๋˜๋Š” โ†‘โ†“ ๋ฒ„ํŠผ์œผ๋กœ ์ˆœ์„œ ๋ณ€๊ฒฝ, โœ• ๋กœ ์ž๋ฆฌ ๋นผ๊ธฐ
๋งค์žฅ ๊ต์ฒด ์ฐจ์ˆœ์œ„ ํ›„๋ณด์—์„œ, ๋˜๋Š” ๋”ํ˜„๋Œ€ ์ „์ฒด 147๊ณณ์—์„œ ์ด๋ฆ„ยท์นดํ…Œ๊ณ ๋ฆฌยท์ธต์œผ๋กœ ๊ฒ€์ƒ‰
ํ…์ŠคํŠธ ์•ˆ๋‚ด ๋ฌธ์žฅ, ์ถ”์ฒœ ์‚ฌ์œ , ๊ทผ๊ฑฐ ์ธ๋ฌผยท๋ธŒ๋žœ๋“œยท๋ฌธ์žฅ, ๊ทผ๊ฑฐ ๊ธฐ์‚ฌ URL
์‚ฌ์ง„ ์‚ฌ์ง„ URL, ์บก์…˜(์นด๋ฆฌ๋‚˜ ร— ํ”„๋ผ๋‹ค), ์ถœ์ฒ˜, ์‚ฌ์ง„์ด ์‹ค๋ฆฐ ๊ธฐ์‚ฌ URL, ์‚ฌ์ง„ ์ข…๋ฅ˜
๋ถ„๋ฅ˜ ์ž๋ฆฌ์˜ ๋ถ„๋ฅ˜(๋งค์žฅยท์Œ์‹์ ยท์นดํŽ˜ยท์—ฌ๊ฐ€), ๊ทผ๊ฑฐ ์ข…๋ฅ˜(์…€๋Ÿฝ ๊ทผ๊ฑฐยท๋™์„ ยท๊ด€๋ฆฌ์ž)
  • ํŽธ์ง‘์€ ์ €์žฅ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ฐฑ์—”๋“œ์—๋„ ๋žŒ๋‹ค์—๋„ ์ดˆ์•ˆ์„ ๊ณ ์น˜๋Š” ์ฐฝ๊ตฌ๊ฐ€ ์•„์ง ์—†์–ด(์ฝ๊ธฐ์™€ ์‚ญ์ œ๋งŒ ์žˆ์Šต๋‹ˆ๋‹ค) ํŒ์—…์„ ๋‹ซ์œผ๋ฉด ์‚ฌ๋ผ์ง‘๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ๋Š” JSON ๋ณต์‚ฌยท๋‚ด๋ ค๋ฐ›๊ธฐ๋กœ ๊ฐ€์ ธ๊ฐ€ ์Šน์ธ ๋žŒ๋‹ค์— ๋„˜๊น๋‹ˆ๋‹ค. ํŽธ์ง‘๊ธฐ ๋งจ ์œ„๊ฐ€ ์ด ์‚ฌ์‹ค์„ ๊ณ„์† ๋งํ•ด ์ค๋‹ˆ๋‹ค.
  • ๋งค์žฅ์„ ๊ฐˆ๋ฉด ๊ทผ๊ฑฐ๋Š” ๊ทธ๋Œ€๋กœ ๋‘๊ณ  ๊ทผ๊ฑฐ ์‚ฌ์ง„๋งŒ ๋—๋‹ˆ๋‹ค. ๋‚จ์˜ ๋ธŒ๋žœ๋“œ ์‚ฌ์ง„์ด ์ƒˆ ๋งค์žฅ์— ๋ถ™๋Š” ๊ฒƒ์ด ์ด ๋ฐฐ์น˜๊ฐ€ ์‹ค์ œ๋กœ ๋ƒˆ๋˜ ์‚ฌ๊ณ ์ž…๋‹ˆ๋‹ค(์นด๋ฆฌ๋‚˜ ร— ๋””์ ค ๊ทผ๊ฑฐ ์ž๋ฆฌ์— MLB๊ฐ€ ๋“ค์–ด๊ฐ”๋Š”๋ฐ ์‚ฌ์ง„๊ณผ ์บก์…˜๋งŒ ๋””์ ค์ด์—ˆ์Šต๋‹ˆ๋‹ค). ์ƒˆ ๋งค์žฅ ์ด๋ฆ„์ด ๊ทผ๊ฑฐ ๋ธŒ๋žœ๋“œ๋ฅผ ํ’ˆ๊ณ  ์žˆ์œผ๋ฉด ๊ทธ๋Œ€๋กœ ๋‘ก๋‹ˆ๋‹ค.
  • ๋ชฉ๋ก์€ ๋จธ๋ฆฌ๋ง๋งŒ ์˜ต๋‹ˆ๋‹ค(์ธ๋ฌผยท์ƒํƒœยท์ฝ”์Šค ๋ชจ์–‘ยท๊ฒฝ๊ณ  ์ˆ˜ยท๋‚จ์€ TTL). ์ดˆ์•ˆ ํ•˜๋‚˜๊ฐ€ ์กฐ์‚ฌ ์›๋ฌธ๊นŒ์ง€ ๋“ค๊ณ  ์žˆ์–ด ์ˆ˜์‹ญ KB๋ผ, ์ „๋ฌธ์€ ์—ฐ ์ดˆ์•ˆ๋งŒ ๋”ฐ๋กœ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
  • ๊ฒฝ๊ณ ๋ฅผ ํŽผ์นœ ์ฑ„๋กœ ๋งจ ์œ„์— ๋‘ก๋‹ˆ๋‹ค. "์‚ฌ์ง„ ์—†์Œ", "๊ทผ๊ฑฐ๋Š” ์žˆ๋Š”๋ฐ ๊ทผ๊ฑฐ ์‚ฌ์ง„์„ ๋ชป ์ฐพ์•˜๋‹ค", "์ฝ”์Šค ๋ชจ์–‘์ด ์–ด๊ธ‹๋‚ฌ๋‹ค"๊ฐ€ ๊ด€๋ฆฌ์ž๊ฐ€ ์ดˆ์•ˆ์—์„œ ์ œ์ผ ๋จผ์ € ๋ด์•ผ ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
  • ์‚ฌ์ง„์€ next/image๊ฐ€ ์•„๋‹ˆ๋ผ <img>๋กœ ๊ทธ๋ฆฝ๋‹ˆ๋‹ค. ์‚ฌ์ง„์ด ๊ธฐ์‚ฌ์—์„œ ์™€์„œ ํ˜ธ์ŠคํŠธ๋ฅผ ๋ฏธ๋ฆฌ ์•Œ ์ˆ˜ ์—†๋Š”๋ฐ next.config.mjs์˜ remotePatterns๋Š” ํ˜ธ์ŠคํŠธ๋ฅผ ์—ด๊ฑฐํ•ด์•ผ ํ†ต๊ณผ์‹œํ‚ต๋‹ˆ๋‹ค. ์›๋ณธ์ด ํ•ซ๋งํฌ๋ฅผ ๋ง‰์œผ๋ฉด ๊ทธ ์ž๋ฆฌ๋งŒ ์ ‘์Šต๋‹ˆ๋‹ค.
  • ๋ฐฐ์น˜ ์‹คํ–‰ ์ƒํ™ฉ(/run)์„ ๋ชป ์ฝ์–ด๋„ ๋ชฉ๋ก์€ ๋ณด์ž…๋‹ˆ๋‹ค โ€” ์œ„ ๋ ๋งŒ ์ ‘ํž™๋‹ˆ๋‹ค.

๋กœ์ปฌ์—์„œ ๋ฐฐํฌ๋œ ๋ฐฑ์—”๋“œ์— ๋ถ™๊ธฐ

ํ”„๋ก ํŠธ๋งŒ ๊ณ ์น  ๋•Œ๋Š” ๋ฐฑ์—”๋“œ๋ฅผ ๋กœ์ปฌ์— ๋„์šธ ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. .env.local์„ .env.example ๊ทธ๋Œ€๋กœ ๋‘๋ฉด ๋ฉ๋‹ˆ๋‹ค.

NEXT_PUBLIC_API_BASE_URL=/api/v1
API_PROXY_TARGET=http://hdf-spring-alb-476185930.ap-northeast-2.elb.amazonaws.com

๋ธŒ๋ผ์šฐ์ €๋Š” localhost:3000/api/*๋งŒ ๋ถ€๋ฅด๊ณ  next.config.mjs์˜ rewrite๊ฐ€ ์„œ๋ฒ„ ์‚ฌ์ด๋“œ๋กœ ALB์— ๋„˜๊น๋‹ˆ๋‹ค. ๊ฐ™์€ ์˜ค๋ฆฌ์ง„์ด๋ผ CORS๋ฅผ ์•ˆ ํƒ‘๋‹ˆ๋‹ค. localhost:3000์€ ๋ฐฑ์—”๋“œ CORS ํ—ˆ์šฉ ๋ชฉ๋ก์—๋„ ๋“ค์–ด ์žˆ์œผ๋ฏ€๋กœ, NEXT_PUBLIC_API_BASE_URL์„ ์ ˆ๋Œ€ ์ฃผ์†Œ๋กœ ๋ฐ”๊ฟ” ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ALB๋ฅผ ์ง์ ‘ ๋ถ€๋ฅด๊ฒŒ ํ•ด๋„ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.

๊ด€๋ฆฌ์ž API๋Š” ์„ธ์…˜ ์ธ์ฆ์ด๋ฏ€๋กœ pnpm dev๋กœ ๋„์šด ๋’ค /login์—์„œ ROLE_ADMIN ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธํ•ด์•ผ /admin/courses๊ฐ€ ๋ณด์ž…๋‹ˆ๋‹ค.

์‹ค๋‚ด ์ง€๋„ ์ •์  ์ž์‚ฐ (CDN)

์‹ค๋‚ด ์ง€๋„ ์›์žฅ(JSON 588KB)๊ณผ ์ธต ํ…์Šค์ฒ˜(PNG 2.2MB)๋Š” ํ•œ ๋ฒˆ ๋งŒ๋“ค์–ด์ง€๋ฉด ๋ฐ”๋€Œ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. CloudFront ์˜ course-resource/* ๋™์ž‘์ด S3 hdf-ditto-images ๋ฅผ ๋‚ด๋ณด๋‚ด๊ณ , ์˜ค๋ธŒ์ ํŠธ๋งˆ๋‹ค 3์ฃผ์งœ๋ฆฌ Cache-Control ์ด ๋ถ™์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

https://d1bxld598du04o.cloudfront.net/course-resource
๋ฌด์—‡ ์–ด๋””
์ธต ๊ทธ๋ž˜ํ”„ 8๊ฐœ ยท ์žฅ์†Œ ์›์žฅ 147๊ณณ ยท ๋ฐฉ ํด๋ฆฌ๊ณค ยท ๋งค๋‹ˆํŽ˜์ŠคํŠธ {CDN}/navigation/v2/*.json
์ธต ํ…์Šค์ฒ˜ 8์žฅ {CDN}/maps/floor-*.png
  • ์œ„ ์ฃผ์†Œ๊ฐ€ ์†Œ์Šค์˜ ๊ธฐ๋ณธ๊ฐ’์ž…๋‹ˆ๋‹ค(src/lib/navigation/navigation-dataset.js). NEXT_PUBLIC_CDN_BASE ๋Š” ๋‹ค๋ฅธ ๋ฒ„ํ‚ทยท๋ฐฐํฌ๋กœ ์˜ฎ๊ธธ ๋•Œ๋งŒ ์ค๋‹ˆ๋‹ค.
  • NEXT_PUBLIC_CDN_BASE= ๋ฅผ ๋นˆ ๊ฐ’์œผ๋กœ ์ฃผ๋ฉด public/ ์•ˆ์˜ ์‚ฌ๋ณธ์œผ๋กœ ๋–จ์–ด์ง‘๋‹ˆ๋‹ค. ์‚ฌ๋ณธ์„ ์ง€์šฐ์ง€ ์•Š์€ ๊ฒƒ์ด CDN ์ด ๋ง‰ํ˜”์„ ๋•Œ์˜ ์•ˆ์ „์žฅ์น˜์ด๊ณ , ๊ทธ ๊ฒฝ๋กœ์—๋„ 3์ฃผ ์บ์‹œ ํ—ค๋”๋ฅผ ๋ถ™์—ฌ ๋’€์Šต๋‹ˆ๋‹ค(next.config.mjs).
  • ๊ธฐ๋ณธ๊ฐ’์ด ํด๋ฐฑ์ด ์•„๋‹ˆ๋ผ CDN ์ธ ์ด์œ . NEXT_PUBLIC_* ๋Š” next build ๊ฐ€ ๋ฒˆ๋“ค์— ๋ฐ•์•„ ๋„ฃ๋Š” ๊ฐ’์ธ๋ฐ, ๋ฐฐํฌ๋Š” ๋„์ปค ์ด๋ฏธ์ง€๋ฅผ ๋งŒ๋“ค ๋•Œ .env ๋ฅผ ์•ˆ ๋„ฃ๊ณ  ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๋„์šธ ๋•Œ๋งŒ ๋„ฃ์Šต๋‹ˆ๋‹ค(--env-file). ๊ทธ๋ž˜์„œ ์˜ˆ์ „ ๊ธฐ๋ณธ๊ฐ’(public/ ์‚ฌ๋ณธ)์ด ๋ฐฐํฌ๋ณธ์— ๊ทธ๋Œ€๋กœ ๋ฐ•ํ˜€, ๋Ÿฐํƒ€์ž„ .env ์— CDN ์ฃผ์†Œ๊ฐ€ ์žˆ์–ด๋„ ์†๋‹˜์€ ๋Š˜ ditto-global.com/navigation/v2/*.json ์œผ๋กœ 588KB ๋ฅผ ํผ ๊ฐ”์Šต๋‹ˆ๋‹ค. ๋ฐฐํฌ๋ณธ์˜ ๊ฐ’์„ ๋ฐ”๊พธ๋ ค๋ฉด docker build --build-arg NEXT_PUBLIC_CDN_BASE=... ๋กœ ๋นŒ๋“œํ•  ๋•Œ ์ฃผ์„ธ์š”(Dockerfile ์— ARG ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค).
  • ํšŒ๊ท€๋Š” pnpm test:navigation-assets ๊ฐ€ ์žก์Šต๋‹ˆ๋‹ค โ€” ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์—†์ด ๋“ค์—ฌ์™”์„ ๋•Œ 8๊ฐœ ์ธต์˜ ์›์žฅยทํ…์Šค์ฒ˜ ์ฃผ์†Œ๊ฐ€ ์ „๋ถ€ CDN ์„ ๊ฐ€๋ฆฌํ‚ค๋Š”์ง€ ๋ด…๋‹ˆ๋‹ค.
  • ๋ฐฑ์—”๋“œ์˜ ditto.map-assets.base-url ๊ณผ ๊ฐ™์€ ๊ณณ์„ ๊ฐ€๋ฆฌ์ผœ์•ผ ํ•ฉ๋‹ˆ๋‹ค. GET /api/v1/places/navigation/assets ๊ฐ€ ๊ฐ™์€ ์ฃผ์†Œ๋ฅผ ๋Œ๋ ค์ฃผ๋ฏ€๋กœ, ์ฃผ์†Œ๋ฅผ ์˜ฎ๊ธธ ๋•Œ๋Š” ๋‘ ๊ณณ์„ ๊ฐ™์ด ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค.
  • ํฌ๋กœ์Šค ์˜ค๋ฆฌ์ง„์ด๋ผ CloudFront course-resource/* ์— Managed-SimpleCORS ์‘๋‹ต ํ—ค๋” ์ •์ฑ…์ด ๋ถ™์–ด ์žˆ์Šต๋‹ˆ๋‹ค. Managed-CachingOptimized ๋Š” Origin ์„ ์›๋ณธ์— ๋„˜๊ธฐ์ง€ ์•Š์•„, ๋ฒ„ํ‚ท CORS ๋งŒ์œผ๋กœ๋Š” ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋ง‰ํž™๋‹ˆ๋‹ค.
  • ์ธต ํ…์Šค์ฒ˜๋Š” three.js useTexture ๊ฐ€ ๋ฐ›์Šต๋‹ˆ๋‹ค(next/image ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค). WebGL ํ…์Šค์ฒ˜๋ผ CORS ํ—ค๋”๊ฐ€ ํ•„์š”ํ•˜๊ณ , ์œ„ ์ •์ฑ…์ด ๊ทธ๊ฒƒ์„ ์ฑ„์›๋‹ˆ๋‹ค.
  • ์›์žฅ์„ ๋‹ค์‹œ ๋งŒ๋“ค๋ฉด ๊ฒฝ๋กœ์˜ v2 ๋ฅผ ์˜ฌ๋ฆฌ์„ธ์š”. ๊ฐ™์€ ํ‚ค๋ฅผ ๋ฎ์–ด์“ฐ๋ฉด ์ตœ๋Œ€ 3์ฃผ ๋™์•ˆ ์˜› ํŒŒ์ผ์ด ๋‚˜๊ฐ‘๋‹ˆ๋‹ค.

Zustand ์‚ฌ์šฉ๋ฒ•

Zustand๋Š” ๊ตญ๊ฐ€ยท์–ธ์–ด, ๋‹ค๋‹จ๊ณ„ ์ฝ”์Šค ์ž‘์„ฑ ์ƒํƒœ, ์ „์—ญ ๋ชจ๋‹ฌ์ฒ˜๋Ÿผ ์—ฌ๋Ÿฌ Client Component๊ฐ€ ๊ณต์œ ํ•ด์•ผ ํ•˜๋Š” ์ƒํƒœ์—๋งŒ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

"use client";

import { usePreferenceStore } from "@/stores/use-preference-store";

const countryCode = usePreferenceStore((state) => state.countryCode);
const languageCode = usePreferenceStore((state) => state.languageCode);
  • ํ•œ ์ปดํฌ๋„ŒํŠธ์—์„œ๋งŒ ์‚ฌ์šฉํ•˜๋Š” ๊ฐ’์€ React useState๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • API ์‘๋‹ต ๋ชฉ๋ก ์ „์ฒด๋ฅผ Zustand์— ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๋ณต์‚ฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ์ธ์ฆ ํ† ํฐ์„ Zustand persist๋‚˜ localStorage์— ์ €์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ๊ตญ๊ฐ€ยท์–ธ์–ด ์ €์žฅ์†Œ๋Š” ์„œ๋ฒ„์—์„œ ์ฟ ํ‚ค์™€ ๋ธŒ๋ผ์šฐ์ € ์–ธ์–ด๋กœ ์ดˆ๊ธฐํ™”ํ•˜๋ฉฐ, ๋กœ๊ทธ์ธ๋œ ๊ฒฝ์šฐ DB ๊ฐ’์œผ๋กœ ๋ฎ์–ด์”๋‹ˆ๋‹ค.
  • /country์—์„œ ๊ตญ๊ฐ€๋Š” ์ฝ˜ํ…์ธ ยทํŠธ๋ Œ๋“œ ์‹œ์žฅ, ์–ธ์–ด๋Š” ํ™”๋ฉด ํ‘œ์‹œ ์–ธ์–ด๋กœ ๋ณ„๋„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

๊ตญ๊ฐ€ยท์–ธ์–ด ์„ค์ • 2์ฐจ ๋ฒ”์œ„

  • ์ง€์› ๊ตญ๊ฐ€: KR, CN, JP, US
  • ์ง€์› ์–ธ์–ด: ko, zh, ja, en
  • ์ €์žฅ API: PATCH /api/v1/users/me/preferences
  • ์„ธ์…˜ ๋ณต์›: GET /api/v1/users/me
  • ๊ฒŒ์ŠคํŠธ ์ €์žฅ: ditto-country, ditto-language, ditto-language-manual ์ฟ ํ‚ค
  • SSR ์ดˆ๊ธฐํ™”: ์ฟ ํ‚ค๊ฐ€ ์—†์œผ๋ฉด Accept-Language์™€ ๊ธฐ๋ณธ ๊ตญ๊ฐ€ KR ์‚ฌ์šฉ
  • ํ—ค๋” ์„ ํƒ๊ธฐ: ๊ตญ๊ฐ€์™€ ํ™”๋ฉด ์–ธ์–ด๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ๋ณ€๊ฒฝ
  • ๊ตญ๊ฐ€ ๋ณ€๊ฒฝ: ์–ธ์–ด๋ฅผ ์ง์ ‘ ์„ ํƒํ•˜๊ธฐ ์ „์—๋Š” ๊ตญ๊ฐ€ ๊ธฐ๋ณธ ์–ธ์–ด ์ ์šฉ, ์ง์ ‘ ์„ ํƒํ•œ ๋’ค์—๋Š” ์–ธ์–ด ์œ ์ง€

์ด๋ฒˆ 2์ฐจ์—๋Š” ์„ค์ • ์šฐ์„ ์ˆœ์œ„ ํ†ตํ•ฉ๊ณผ ์ฒซ ๋ Œ๋”๋ง ๋ณต์›๊นŒ์ง€ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. ํ™”๋ฉด ๊ณ ์ • ๋ฌธ๊ตฌ ์‚ฌ์ „, ์‹ค์ œ UI ์ „์ฒด ์–ธ์–ด ์ „ํ™˜, ๋™์  ์ฝ˜ํ…์ธ  ๋ฒˆ์—ญ ๋ฐ DeepL ์—ฐ๋™์€ ํ›„์† ๋‹จ๊ณ„์ž…๋‹ˆ๋‹ค.

Tailwind CSS

Tailwind CSS 4๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ ์ „์—ญ ๋””์ž์ธ ํ† ํฐ์€ src/app/globals.css์˜ @theme์—์„œ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

  • ์ปดํฌ๋„ŒํŠธ ์Šคํƒ€์ผ์€ Tailwind ์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค๋ฅผ ์šฐ์„ ํ•ฉ๋‹ˆ๋‹ค.
  • ๋ฐ˜๋ณต๋˜๋Š” ์ƒ‰์ƒยท๊ฐ„๊ฒฉยทํฐํŠธ ๊ฐ’์€ ๋””์ž์ธ ํ† ํฐ์œผ๋กœ ์Šน๊ฒฉํ•ฉ๋‹ˆ๋‹ค.
  • ์ž„์˜์˜ ์ „์—ญ CSS๋Š” ๋ธŒ๋ผ์šฐ์ € ๊ธฐ๋ณธ๊ฐ’์ด๋‚˜ ๊ณตํ†ต ํ† ํฐ์ฒ˜๋Ÿผ ๋ฒ”์œ„๊ฐ€ ๋ช…ํ™•ํ•  ๋•Œ๋งŒ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

ํ™˜๊ฒฝ๋ณ€์ˆ˜

.env.example์€ ํ‚ค ์ด๋ฆ„๊ณผ ์•ˆ์ „ํ•œ ์˜ˆ์‹œ๋งŒ ํฌํ•จํ•ด Git์— ์ปค๋ฐ‹ํ•ฉ๋‹ˆ๋‹ค. ์‹ค์ œ ๊ฐ’์€ Git์—์„œ ์ œ์™ธ๋˜๋Š” .env.local์— ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

NEXT_PUBLIC_API_BASE_URL=/api/v1
NEXT_PUBLIC_LOCAL_USER_ID=
API_PROXY_TARGET=http://hdf-spring-alb-476185930.ap-northeast-2.elb.amazonaws.com
ํ‚ค ์„ค๋ช…
NEXT_PUBLIC_API_BASE_URL ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํ˜ธ์ถœํ•  API ๊ธฐ์ค€ ๊ฒฝ๋กœ. ๊ธฐ๋ณธ๊ฐ’ /api/v1.
NEXT_PUBLIC_LOCAL_USER_ID ๋กœ์ปฌ Spring์˜ local ํ”„๋กœํ•„์„ ์˜๋„์ ์œผ๋กœ ์‹œํ—˜ํ•  ๋•Œ๋งŒ ๋„ฃ๋Š” X-User-Id. ๊ธฐ๋ณธ๊ฐ’์€ ๋น„์›Œ ๋‘ก๋‹ˆ๋‹ค.
API_PROXY_TARGET Next๊ฐ€ /api/*๋ฅผ ๋„˜๊ธธ ๋ฐฑ์—”๋“œ ์ฃผ์†Œ. ๊ธฐ๋ณธ๊ฐ’์€ ๋ฐฐํฌ๋œ Backend ALB์ž…๋‹ˆ๋‹ค.

NEXT_PUBLIC_ ์ ‘๋‘์‚ฌ๊ฐ€ ๋ถ™์€ ๊ฐ’์€ ๋ธŒ๋ผ์šฐ์ € ๋ฒˆ๋“ค์— ํฌํ•จ๋˜๋ฏ€๋กœ ๋น„๋ฐ€ ํ‚ค๋ฅผ ๋„ฃ์œผ๋ฉด ์•ˆ ๋ฉ๋‹ˆ๋‹ค. NEXT_PUBLIC_LOCAL_USER_ID๊ฐ€ ๋ช…์‹œ๋œ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋งŒ X-User-Id ํ—ค๋”๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ๋ฐฐํฌ Backend๋ฅผ ํ”„๋ก์‹œํ•  ๋•Œ๋Š” ๋น„์›Œ ๋‘๊ณ  ์‹ค์ œ JSESSIONID ์„ธ์…˜์œผ๋กœ ์ธ์ฆํ•ฉ๋‹ˆ๋‹ค. ๋ธŒ๋ผ์šฐ์ €์˜ /api/* ์š”์ฒญ์€ Next.js rewrite๋ฅผ ํ†ตํ•ด API_PROXY_TARGET์œผ๋กœ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ด€๋ฆฌ

์ด ํ”„๋กœ์ ํŠธ๋Š” pnpm๋งŒ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. package-lock.json์ด๋‚˜ yarn.lock์„ ๋งŒ๋“ค์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

# ํ”„๋กœ๋•์…˜ ์˜์กด์„ฑ ์ถ”๊ฐ€
pnpm add <package>

# ๊ฐœ๋ฐœ ๋„๊ตฌ ์ถ”๊ฐ€
pnpm add -D <package>

# ์ œ๊ฑฐ
pnpm remove <package>

# ์˜ค๋ž˜๋œ ํŒจํ‚ค์ง€ ํ™•์ธ
pnpm outdated

# ์„ค์น˜ ์ƒํƒœ ์žฌํ˜„
pnpm install --frozen-lockfile

package.json๊ณผ pnpm-lock.yaml์€ ํ•จ๊ป˜ ์ปค๋ฐ‹ํ•˜๊ณ  ์ž ๊ธˆ ํŒŒ์ผ์„ ์ง์ ‘ ์ˆ˜์ •ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ฃผ์š” ๋ฒ„์ „ ์—…๊ทธ๋ ˆ์ด๋“œ๋Š” ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ํ™•์ธํ•œ ๋’ค ๋ณ„๋„ ์ž‘์—…์œผ๋กœ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

๋ช…๋ น์–ด

๋ช…๋ น์–ด ์„ค๋ช…
pnpm dev 3000๋ฒˆ ํฌํŠธ์—์„œ ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰
pnpm lint ESLint ๊ฒ€์‚ฌ
pnpm build ํ”„๋กœ๋•์…˜ ๋นŒ๋“œ
pnpm start ๋นŒ๋“œ ๊ฒฐ๊ณผ๋ฅผ 3000๋ฒˆ ํฌํŠธ์—์„œ ์‹คํ–‰
pnpm check lint ํ›„ build ์—ฐ์† ๊ฒ€์ฆ

PWA

์›น ์•ฑ ๋งค๋‹ˆํŽ˜์ŠคํŠธ, ํ™ˆ ํ™”๋ฉด ์•„์ด์ฝ˜, ์„œ๋น„์Šค ์›Œ์ปค, ์„ค์น˜ ๋ฐฐ๋„ˆ๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. ๋ฐ์Šคํฌํ†ฑ์—์„œ๋Š” 430px ํฐ ์…ธ๋กœ ๋ฏธ๋ฆฌ๋ณด๊ณ , ์‹ค์ œ ํฐ์—์„œ๋Š” ์ „์ฒด ํญ์œผ๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

ํ•ญ๋ชฉ ์œ„์น˜
๋งค๋‹ˆํŽ˜์ŠคํŠธ src/app/manifest.js โ†’ /manifest.webmanifest
์•ฑ ์•„์ด์ฝ˜ /icons/192, /icons/512, apple-icon
์„œ๋น„์Šค ์›Œ์ปค public/sw.js (PwaRegister๊ฐ€ ๋“ฑ๋ก)
ํ•˜๋‹จ ํƒญ ํ™ˆ ยท ์ฝ”์Šค ยท ๋งŒ๋“ค๊ธฐ(+) ยท ๋‰ด์Šค ยท ๋งˆ์ด

์„ค์น˜ ๋ฐฉ๋ฒ•:

  • Chrome / Edge / Android: ํ™ˆ ํ•˜๋‹จ ๋ฐฐ๋„ˆ์˜ "์ง€๊ธˆ ์„ค์น˜ํ•˜๊ธฐ", ๋˜๋Š” ๋ธŒ๋ผ์šฐ์ € ๋ฉ”๋‰ด์˜ ์•ฑ ์„ค์น˜
  • iOS Safari: ๊ณต์œ  ๋ฒ„ํŠผ โ†’ ํ™ˆ ํ™”๋ฉด์— ์ถ”๊ฐ€

์„œ๋น„์Šค ์›Œ์ปค๋Š” ์ •์  ์ž์‚ฐ๊ณผ ํ™ˆ ์…ธ์„ ์บ์‹œํ•˜๊ณ , /api/* ์š”์ฒญ์€ ํ•ญ์ƒ ๋„คํŠธ์›Œํฌ๋กœ ๋ณด๋ƒ…๋‹ˆ๋‹ค. ์ธ์ฆ ํ† ํฐ์€ ์บ์‹œํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

About

KOSA X Hyundai Futurenet 11th final project

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages