ALPACO ํ”„๋ก ํŠธ์—”๋“œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜

ALPACO (AI Learning PAth COmpanion)๋Š” ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ฐ ๊ด€๋ จ ๋ถ„์•ผ์—์„œ ์‚ฌ์šฉ์ž์˜ ํ•™์Šต ์—ฌ์ •์„ ์ง€์›ํ•˜๋„๋ก ์„ค๊ณ„๋œ ํ˜„๋Œ€์ ์ธ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ž…๋‹ˆ๋‹ค. ์ด ํ”„๋ก ํŠธ์—”๋“œ๋Š” Next.js, TypeScript, Tailwind CSS๋กœ ๊ตฌ์ถ•๋˜์–ด ๋ฐ˜์‘ํ˜• ๋ฐ ๋Œ€ํ™”ํ˜• ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ธ์ฆ์„ ์œ„ํ•œ AWS Cognito, ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์ ‘๊ทผ์„ ์œ„ํ•œ API Gateway, ํ˜ธ์ŠคํŒ… ๋ฐ ์ฝ˜ํ…์ธ  ์ „์†ก์„ ์œ„ํ•œ S3/CloudFront ๋“ฑ ๋‹ค์–‘ํ•œ ๋ฐฑ์—”๋“œ ์„œ๋น„์Šค์™€ ํ†ตํ•ฉ๋ฉ๋‹ˆ๋‹ค.

๋ชฉ์ฐจ

  1. ์ฃผ์š” ๊ธฐ๋Šฅ
  2. ๊ธฐ์ˆ  ์Šคํƒ
  3. ์‚ฌ์ „ ์ค€๋น„ ์‚ฌํ•ญ
  4. ์‹œ์ž‘ํ•˜๊ธฐ
  5. ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ
  6. ํ”„๋กœ๋•์…˜ ๋นŒ๋“œ
  7. ๋ฐฐํฌ
  8. ๋ฆฐํŒ… ๋ฐ ํฌ๋งทํŒ…
  9. ์ฃผ์š” Next.js ์„ค์ • (next.config.ts)
  10. ์Šคํƒ€์ผ๋ง (Tailwind CSS)
  11. TypeScript ์„ค์ •
  12. ๋” ์•Œ์•„๋ณด๊ธฐ

์ฃผ์š” ๊ธฐ๋Šฅ

  • ์‚ฌ์šฉ์ž ์ธ์ฆ: AWS Cognito๋ฅผ ํ†ตํ•œ ์•ˆ์ „ํ•œ ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ ๋ฐ ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ.
  • ๋Œ€ํ™”ํ˜• ๋ฌธ์ œ ํ•ด๊ฒฐ: ์ฝ”๋”ฉ ๋ฌธ์ œ ์ธํ„ฐํŽ˜์ด์Šค (Monaco ๊ธฐ๋ฐ˜ ์ฝ”๋“œ ํŽธ์ง‘๊ธฐ ์‚ฌ์šฉ ๊ฐ€๋Šฅ์„ฑ).
  • Markdown ์ฝ˜ํ…์ธ  ํ‘œ์‹œ: react-markdown์„ ์‚ฌ์šฉํ•œ ๋ฆฌ์น˜ ํ…์ŠคํŠธ ์ฝ˜ํ…์ธ  ๋ Œ๋”๋ง.
  • ๋ฐ˜์‘ํ˜• ๋””์ž์ธ: Tailwind CSS๋ฅผ ์‚ฌ์šฉํ•œ ๋‹ค์–‘ํ•œ ํ™”๋ฉด ํฌ๊ธฐ์— ์ ์‘ํ•˜๋Š” UI.
  • ๋™์  UI ์š”์†Œ: Framer Motion์„ ์‚ฌ์šฉํ•œ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋ฐ Sonner๋ฅผ ์‚ฌ์šฉํ•œ ์•Œ๋ฆผ.
  • ํฌ๊ธฐ ์กฐ์ ˆ ๊ฐ€๋Šฅ ํŒจ๋„: ์œ ์—ฐํ•œ ๋ ˆ์ด์•„์›ƒ ์กฐ์ •์„ ์œ„ํ•จ.
  • ๊ตฌ๋ฌธ ๊ฐ•์กฐ: ์ฝ”๋“œ ์Šค๋‹ˆํŽซ์„ ๋ช…ํ™•ํ•˜๊ฒŒ ํ‘œ์‹œ.
  • ๋ฐฑ์—”๋“œ ์„œ๋น„์Šค ์—ฐ๋™: ๋ฌธ์ œ ์ƒ์„ฑ, ์ฝ”๋“œ ์ฑ„์ , ์ปค๋ฎค๋‹ˆํ‹ฐ ๊ธฐ๋Šฅ, ์ฑ—๋ด‡ ์ƒํ˜ธ์ž‘์šฉ์„ ์œ„ํ•œ ๋‹ค์–‘ํ•œ API ์—ฐ๊ฒฐ.

๊ธฐ์ˆ  ์Šคํƒ

  • ํ”„๋ ˆ์ž„์›Œํฌ: Next.js (v15.x, ๊ฐœ๋ฐœ ์‹œ Turbopack ์‚ฌ์šฉ)
  • ์–ธ์–ด: TypeScript
  • UI ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ: React (v19)
  • ์Šคํƒ€์ผ๋ง: Tailwind CSS (v3.3) with PostCSS and Autoprefixer
  • ์ธ์ฆ: AWS Amplify (AWS Cognito ์—ฐ๋™)
  • ์ƒํƒœ ๊ด€๋ฆฌ: React Context / Hooks (Amplify ๋ฐ ์ผ๋ฐ˜์ ์ธ React ํŒจํ„ด์—์„œ ์•”์‹œ)
  • ์ฝ”๋“œ ํŽธ์ง‘๊ธฐ (์ปดํฌ๋„ŒํŠธ): @monaco-editor/react
  • Markdown ๋ Œ๋”๋ง: react-markdown, remark-gfm, rehype-raw
  • UI ์ปดํฌ๋„ŒํŠธ & ์œ ํ‹ธ๋ฆฌํ‹ฐ:
    • @heroicons/react (์•„์ด์ฝ˜)
    • react-resizable-panels (๋ ˆ์ด์•„์›ƒ)
    • react-syntax-highlighter (์ฝ”๋“œ ํ‘œ์‹œ)
    • sonner (ํ† ์ŠคํŠธ ์•Œ๋ฆผ)
    • framer-motion (์• ๋‹ˆ๋ฉ”์ด์…˜)
    • date-fns (๋‚ ์งœ ์œ ํ‹ธ๋ฆฌํ‹ฐ)
  • ๋ฆฐํŒ…: ESLint (eslint-config-next ์‚ฌ์šฉ)
  • ๋ฐฐํฌ: AWS S3 (์ •์  ์›น์‚ฌ์ดํŠธ ํ˜ธ์ŠคํŒ…) + AWS CloudFront (CDN)

์‚ฌ์ „ ์ค€๋น„ ์‚ฌํ•ญ

  • Node.js (LTS ๋ฒ„์ „ ๊ถŒ์žฅ, ์˜ˆ: v18.x, v20.x)
  • ํŒจํ‚ค์ง€ ๋งค๋‹ˆ์ €:
    • npm (Node.js์™€ ํ•จ๊ป˜ ์ œ๊ณต)
    • yarn
    • pnpm
    • bun
  • ํ”„๋กœ์ ํŠธ์˜ AWS ๊ณ„์ • ์ ‘๊ทผ ๊ถŒํ•œ (๋ฐฑ์—”๋“œ ์„œ๋น„์Šค์˜ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๊ฐ’ ํ•„์š”).

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

์ €์žฅ์†Œ ๋ณต์ œ

git clone <repository-url>
cd capstone-2025-04/frontend

์˜์กด์„ฑ ์„ค์น˜

์„ ํ˜ธํ•˜๋Š” ํŒจํ‚ค์ง€ ๋งค๋‹ˆ์ €๋ฅผ ์„ ํƒํ•˜์—ฌ ์‚ฌ์šฉํ•˜์„ธ์š”:

# npm ์‚ฌ์šฉ ์‹œ
npm install

# yarn ์‚ฌ์šฉ ์‹œ
yarn install

# pnpm ์‚ฌ์šฉ ์‹œ
pnpm install

# bun ์‚ฌ์šฉ ์‹œ
bun install

ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ •

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋ฐฑ์—”๋“œ ์„œ๋น„์Šค์— ์—ฐ๊ฒฐํ•˜๋ ค๋ฉด ์—ฌ๋Ÿฌ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

  1. ์ƒ˜ํ”Œ ํ™˜๊ฒฝ ํŒŒ์ผ์„ ๋ณต์‚ฌํ•ฉ๋‹ˆ๋‹ค:
    cp .env.local.sample .env.local
    
  2. .env.local ํŒŒ์ผ์„ ํŽธ์ง‘ํ•˜๊ณ  ๊ฐ’์„ ์ฑ„์›๋‹ˆ๋‹ค. ์ด ๊ฐ’๋“ค์€ ์ผ๋ฐ˜์ ์œผ๋กœ ๋ฐฑ์—”๋“œ ์ธํ”„๋ผ ๋ฐฐํฌ(์˜ˆ: infrastructure/app Terraform ๋ชจ๋“ˆ์˜ Cognito, API Gateway, CloudFront ์ถœ๋ ฅ) ๊ฒฐ๊ณผ๋กœ๋ถ€ํ„ฐ ์–ป์Šต๋‹ˆ๋‹ค.

    # .env.local
    # ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋„๋ก NEXT_PUBLIC_ ์ ‘๋‘์‚ฌ๋กœ ์‹œ์ž‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    
    # AWS ์„ค์ •
    NEXT_PUBLIC_AWS_REGION=ap-northeast-2 # ์‚ฌ์šฉํ•˜๋Š” AWS ๋ฆฌ์ „
    
    # AWS Cognito (`infrastructure/auth` Terraform ์ถœ๋ ฅ๊ฐ’)
    NEXT_PUBLIC_COGNITO_USER_POOL_ID=YOUR_USER_POOL_ID
    NEXT_PUBLIC_COGNITO_CLIENT_ID=YOUR_APP_CLIENT_ID
    NEXT_PUBLIC_COGNITO_DOMAIN=YOUR_COGNITO_AUTH_DOMAIN # ์˜ˆ: alpaco-auth-prod.auth.ap-northeast-2.amazoncognito.com
    
    # ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ธฐ๋ณธ URL (`infrastructure/app` Terraform ์ถœ๋ ฅ๊ฐ’: cloudfront_distribution_domain_name ๋˜๋Š” ์‚ฌ์šฉ์ž ์ •์˜ ๋„๋ฉ”์ธ)
    NEXT_PUBLIC_APP_BASE_URL=YOUR_CLOUDFRONT_OR_CUSTOM_DOMAIN_NAME # ์˜ˆ: https://d2rgzjzynamwq2.cloudfront.net ๋˜๋Š” https://www.alpaco.us
    
    # API Gateway ํ˜ธ์ถœ URL (๊ฐ ๋ฐฑ์—”๋“œ Terraform ์ถœ๋ ฅ๊ฐ’)
    NEXT_PUBLIC_COMMUNITY_API_BASE_URL=YOUR_COMMUNITY_API_GATEWAY_INVOKE_URL
    NEXT_PUBLIC_PROBLEM_GENERATION_API_BASE_URL=YOUR_PROBLEM_GEN_API_GATEWAY_INVOKE_URL
    NEXT_PUBLIC_PROBLEM_API_BASE_URL=YOUR_PROBLEM_API_GATEWAY_INVOKE_URL
    NEXT_PUBLIC_CODE_GRADER_BASE_URL=YOUR_CODE_GRADER_API_GATEWAY_INVOKE_URL
    NEXT_PUBLIC_SUBMISSIONS_API_BASE_URL=YOUR_SUBMISSIONS_API_GATEWAY_INVOKE_URL
    
    # ์ฑ—๋ด‡ API ์—”๋“œํฌ์ธํŠธ (API Gateway ๋˜๋Š” WebSocket API๋ฅผ ์œ„ํ•œ ํŠน์ • CloudFront ๋ฐฐํฌ์ผ ์ˆ˜ ์žˆ์Œ)
    NEXT_PUBLIC_CHATBOT_API_ENDPOINT=YOUR_CHATBOT_API_ENDPOINT
    

    ์ฐธ๊ณ : .env.local ํŒŒ์ผ์€ .gitignore์— ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฉฐ, ์ €์žฅ์†Œ์— ์ ˆ๋Œ€๋กœ ์ปค๋ฐ‹ํ•ด์„œ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค.

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

์˜์กด์„ฑ ์„ค์น˜ ๋ฐ .env.local ์„ค์ • ํ›„:

npm run dev
# ๋˜๋Š”
yarn dev
# ๋˜๋Š”
pnpm dev
# ๋˜๋Š”
bun dev

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์ผ๋ฐ˜์ ์œผ๋กœ http://localhost:3000์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Next.js ๊ฐœ๋ฐœ ์„œ๋ฒ„๋Š” package.json์— ๋ช…์‹œ๋œ ๋Œ€๋กœ ๋” ๋น ๋ฅธ ๋นŒ๋“œ๋ฅผ ์œ„ํ•ด Turbopack์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

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

์ผ๋ฐ˜์ ์ธ Next.js ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค:

frontend/
โ”œโ”€โ”€ public/               # ์ •์  ์—์…‹ (ํŒŒ๋น„์ฝ˜, ์ด๋ฏธ์ง€ ๋“ฑ)
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ app/              # Next.js App Router (๋ ˆ์ด์•„์›ƒ, ํŽ˜์ด์ง€, ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ)
โ”‚   โ”œโ”€โ”€ components/       # ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ React ์ปดํฌ๋„ŒํŠธ
โ”‚   โ”œโ”€โ”€ api/              # API ํด๋ผ์ด์–ธํŠธ ํ•จ์ˆ˜ (์˜ˆ: API Gateway์™€ ์ƒํ˜ธ์ž‘์šฉ)
โ”‚   โ”œโ”€โ”€ hooks/            # ์‚ฌ์šฉ์ž ์ •์˜ React Hooks
โ”‚   โ”œโ”€โ”€ lib/              # ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜, ์„ค์ • (์˜ˆ: Amplify ์„ค์ •)
โ”‚   โ”œโ”€โ”€ styles/           # ์ „์—ญ ์Šคํƒ€์ผ, Tailwind ๊ธฐ๋ณธ ์Šคํƒ€์ผ
โ”‚   โ””โ”€โ”€ ...               # ๊ธฐํƒ€ ํŠน์ • ๋””๋ ‰ํ† ๋ฆฌ (์ปจํ…์ŠคํŠธ, ํƒ€์ž… ๋“ฑ)
โ”œโ”€โ”€ .env.local            # ๋กœ์ปฌ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ (Gitignored)
โ”œโ”€โ”€ .env.local.sample     # ์ƒ˜ํ”Œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜
โ”œโ”€โ”€ next.config.ts        # Next.js ์„ค์ •
โ”œโ”€โ”€ tailwind.config.js    # Tailwind CSS ์„ค์ •
โ”œโ”€โ”€ postcss.config.js     # PostCSS ์„ค์ •
โ”œโ”€โ”€ tsconfig.json         # TypeScript ์„ค์ •
โ”œโ”€โ”€ eslint.config.mjs     # ESLint ์„ค์ • (์ƒˆ๋กœ์šด flat config ํ˜•์‹)
โ”œโ”€โ”€ package.json          # ํ”„๋กœ์ ํŠธ ์˜์กด์„ฑ ๋ฐ ์Šคํฌ๋ฆฝํŠธ
โ””โ”€โ”€ README.md             # ์ด ํŒŒ์ผ

ํ”„๋กœ๋•์…˜ ๋นŒ๋“œ

ํ”„๋กœ๋•์…˜์„ ์œ„ํ•ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋นŒ๋“œ(์ •์  ์ต์ŠคํฌํŠธ)ํ•˜๋ ค๋ฉด:

npm run build

์ด ๋ช…๋ น์–ด๋Š” ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค:

  1. ์ฝ”๋“œ ๋ฆฐํŠธ (Next.js ๋นŒ๋“œ ํ”„๋กœ์„ธ์Šค์— ๋”ฐ๋ผ).
  2. TypeScript ๋ฐ React ์ฝ”๋“œ ์ปดํŒŒ์ผ.
  3. out/ ๋””๋ ‰ํ† ๋ฆฌ์— ์ •์  HTML, CSS, JavaScript ํŒŒ์ผ ์ƒ์„ฑ. ์ด๋Š” next.config.ts์˜ output: "export" ์„ค์ • ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

๋ฐฐํฌ

๋ฐฐํฌ ์ „๋žต

ํ”„๋ก ํŠธ์—”๋“œ๋Š” AWS S3์— ์ •์  ์‚ฌ์ดํŠธ๋กœ ๋ฐฐํฌ๋˜๋ฉฐ, AWS CloudFront๋ฅผ ํ†ตํ•ด ์ „ ์„ธ๊ณ„์ ์œผ๋กœ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. ์ด ์ ‘๊ทผ ๋ฐฉ์‹์€ ๋‹ค์Œ์„ ํ™œ์šฉํ•ฉ๋‹ˆ๋‹ค:

  • ์ •์  ์ต์ŠคํฌํŠธ: Next.js์˜ output: "export" ๊ธฐ๋Šฅ์€ ์‚ฌ์ดํŠธ์˜ ์™„์ „ํ•œ ์ •์  ๋ฒ„์ „์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  • S3: ์ €๋ ดํ•˜๊ณ  ์•ˆ์ •์ ์ธ ์ •์  ํŒŒ์ผ ํ˜ธ์ŠคํŒ….
  • CloudFront: ๊ธ€๋กœ๋ฒŒ ์ฝ˜ํ…์ธ  ์ „์†ก(CDN), HTTPS/SSL ์ข…๋ฃŒ(ACM ์ธ์ฆ์„œ ์‚ฌ์šฉ), ์‚ฌ์šฉ์ž ์ •์˜ ๋„๋ฉ”์ธ ์ง€์›.
  • Origin Access Control (OAC): S3 ๋ฒ„ํ‚ท ์ฝ˜ํ…์ธ ๊ฐ€ CloudFront๋ฅผ ํ†ตํ•ด์„œ๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋„๋ก ๋ณด์žฅ.

์ธํ”„๋ผ (Terraform)

AWS ์ธํ”„๋ผ(S3 ๋ฒ„ํ‚ท, CloudFront ๋ฐฐํฌ, ACM ์ธ์ฆ์„œ, Route 53 ๋ ˆ์ฝ”๋“œ, GitHub Actions์šฉ IAM ์—ญํ• )๋Š” Terraform์— ์˜ํ•ด ํ”„๋กœ๋น„์ €๋‹๋˜๊ณ  ๊ด€๋ฆฌ๋ฉ๋‹ˆ๋‹ค. ๊ด€๋ จ Terraform ์„ค์ •์€ capstone-2025-04/infrastructure/app/ ๋””๋ ‰ํ† ๋ฆฌ์— ์žˆ์Šต๋‹ˆ๋‹ค. ์ธํ”„๋ผ ์„ค์ •์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ ํ•ด๋‹น ๋””๋ ‰ํ† ๋ฆฌ์˜ README.md๋ฅผ ์ฐธ์กฐํ•˜์‹ญ์‹œ์˜ค.

Terraform์— ์˜ํ•ด ์ƒ์„ฑ๋˜๋Š” ์ฃผ์š” ์ธํ”„๋ผ ๊ตฌ์„ฑ ์š”์†Œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

  • aws_s3_bucket: out/ ๋””๋ ‰ํ† ๋ฆฌ์˜ ์ •์  ๋นŒ๋“œ ์ถœ๋ ฅ์„ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
  • aws_cloudfront_distribution: S3์—์„œ ์ฝ˜ํ…์ธ ๋ฅผ ์ œ๊ณตํ•˜๊ณ  SSL ๋ฐ ์‚ฌ์šฉ์ž ์ •์˜ ๋„๋ฉ”์ธ์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • aws_acm_certificate: ์‚ฌ์šฉ์ž ์ •์˜ ๋„๋ฉ”์ธ(alpaco.us ๋ฐ www.alpaco.us, auth.alpaco.us์™€ ๊ฐ™์€ ํ•˜์œ„ ๋„๋ฉ”์ธ)์— ๋Œ€ํ•œ HTTPS์šฉ.
  • aws_route53_record: ์‚ฌ์šฉ์ž ์ •์˜ ๋„๋ฉ”์ธ์„ CloudFront ๋ฐฐํฌ๋กœ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
  • aws_cloudfront_function: ํด๋ผ์ด์–ธํŠธ ์ธก ๋ผ์šฐํŒ…์„ ์œ„ํ•œ URL ์žฌ์ž‘์„ฑ์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • aws_iam_role (GitHub Actions์šฉ): GitHub Actions๊ฐ€ S3์— ๋ฐฐํฌํ•˜๊ณ  CloudFront๋ฅผ ๋ฌดํšจํ™”ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค.

GitHub Actions๋ฅผ ์ด์šฉํ•œ CI/CD

๋ฐฐํฌ๋Š” GitHub Actions๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž๋™ํ™”๋ฉ๋‹ˆ๋‹ค. ์›Œํฌํ”Œ๋กœ์šฐ(capstone-2025-04/.github/workflows/deploy.yml์— ์œ„์น˜)๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ํŠน์ • ํŠธ๋ฆฌ๊ฑฐ(์˜ˆ: master ๋ธŒ๋žœ์น˜ ๋˜๋Š” release/** ๋ธŒ๋žœ์น˜๋กœ ํ‘ธ์‹œ) ๋ฐœ์ƒ ์‹œ ๋‹ค์Œ ๋‹จ๊ณ„๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค:

  1. ์ฝ”๋“œ ์ฒดํฌ์•„์›ƒ (actions/checkout@v4): ์ตœ์‹  ์ฝ”๋“œ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ํ‘ธ์‹œ๋œ ์ฐธ์กฐ(๋ธŒ๋žœ์น˜ ๋˜๋Š” ํƒœ๊ทธ)๋ฅผ ์ฒดํฌ์•„์›ƒํ•ฉ๋‹ˆ๋‹ค.
  2. Node.js ์„ค์ • (actions/setup-node@v4): Node.js 20 ๋ฒ„์ „์„ ์„ค์ •ํ•˜๊ณ  frontend/package-lock.json์„ ์‚ฌ์šฉํ•˜์—ฌ npm ์บ์‹œ๋ฅผ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  3. (์„ ํƒ์ ) OIDC ํ† ํฐ ๋””๋ฒ„๊น…: OIDC ํ† ํฐ ๊ฒ€์ƒ‰ ๋ฐ ํŽ˜์ด๋กœ๋“œ ๋””์ฝ”๋”ฉ์„ ์‹œ๋„ํ•˜์—ฌ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค.
  4. AWS ์ž๊ฒฉ ์ฆ๋ช… ๊ตฌ์„ฑ (aws-actions/configure-aws-credentials@v4): GitHub Secrets (AWS_IAM_ROLE_ARN, AWS_REGION)์— ์ €์žฅ๋œ ์ •๋ณด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ OIDC๋ฅผ ํ†ตํ•ด IAM ์—ญํ• ์„ ์ˆ˜์ž„ํ•˜์—ฌ AWS์— ์•ˆ์ „ํ•˜๊ฒŒ ์ ‘๊ทผํ•ฉ๋‹ˆ๋‹ค.
  5. ์˜์กด์„ฑ ์„ค์น˜: ./frontend ๋””๋ ‰ํ† ๋ฆฌ์—์„œ npm install์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  6. Next.js ์ •์  ์‚ฌ์ดํŠธ ๋นŒ๋“œ: ./frontend ๋””๋ ‰ํ† ๋ฆฌ์—์„œ npm run build๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๋นŒ๋“œ ํ”„๋กœ์„ธ์Šค์— ํ•„์š”ํ•œ NEXT_PUBLIC_ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋“ค์ด GitHub Secrets๋กœ๋ถ€ํ„ฐ ์ฃผ์ž…๋ฉ๋‹ˆ๋‹ค.
    • NEXT_PUBLIC_AWS_REGION
    • NEXT_PUBLIC_COGNITO_USER_POOL_ID
    • NEXT_PUBLIC_COGNITO_CLIENT_ID
    • NEXT_PUBLIC_COGNITO_DOMAIN
    • NEXT_PUBLIC_APP_BASE_URL: https://$ (CloudFront ๋„๋ฉ”์ธ ๋˜๋Š” ์‚ฌ์šฉ์ž ์ •์˜ ๋„๋ฉ”์ธ)
    • ๊ฐ API ์„œ๋น„์Šค์˜ NEXT_PUBLIC_*_API_BASE_URL ๋ฐ NEXT_PUBLIC_CHATBOT_API_ENDPOINT
  7. S3์— ํŒŒ์ผ ๋™๊ธฐํ™”: aws s3 sync ./frontend/out s3://$ --delete ๋ช…๋ น์„ ์‚ฌ์šฉํ•˜์—ฌ ๋นŒ๋“œ๋œ out/ ๋””๋ ‰ํ† ๋ฆฌ์˜ ๋‚ด์šฉ์„ S3 ๋ฒ„ํ‚ท์— ๋™๊ธฐํ™”ํ•˜๊ณ , ๊ธฐ์กด์— ์žˆ์ง€๋งŒ ์ƒˆ ๋นŒ๋“œ์—๋Š” ์—†๋Š” ํŒŒ์ผ์„ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค.
  8. CloudFront ์บ์‹œ ๋ฌดํšจํ™”: aws cloudfront create-invalidation --distribution-id $ --paths "/*" ๋ช…๋ น์„ ์‚ฌ์šฉํ•˜์—ฌ CloudFront ์บ์‹œ๋ฅผ ๋ฌดํšจํ™”ํ•˜์—ฌ ์‚ฌ์šฉ์ž๊ฐ€ ์ตœ์‹  ๋ฒ„์ „์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

๋ฐฐํฌ์— ํ•„์š”ํ•œ GitHub Secrets:

  • AWS_IAM_ROLE_ARN: GitHub Actions๊ฐ€ ์‚ฌ์šฉํ•  IAM ์—ญํ• ์˜ ARN (์˜ˆ: arn:aws:iam::897722694537:role/alpaco-github-actions-deploy-role-production)
  • AWS_REGION: AWS ๋ฆฌ์ „ (์˜ˆ: ap-northeast-2)
  • AWS_S3_BUCKET_NAME: ๋Œ€์ƒ S3 ๋ฒ„ํ‚ท ์ด๋ฆ„ (์˜ˆ: alpaco-frontend-production-alpaco-frontend-bucket)
  • AWS_CLOUDFRONT_DISTRIBUTION_ID: CloudFront ๋ฐฐํฌ ID (์˜ˆ: E3Q5IEHTZGF1U5)
  • ROUTE53_DOMAIN_NAME: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ธฐ๋ณธ ๋„๋ฉ”์ธ ์ด๋ฆ„ (์˜ˆ: www.alpaco.us ๋˜๋Š” d2rgzjzynamwq2.cloudfront.net)
  • NEXT_PUBLIC_AWS_REGION: ํ”„๋ก ํŠธ์—”๋“œ ๋นŒ๋“œ ์‹œ ์‚ฌ์šฉ๋  AWS ๋ฆฌ์ „
  • NEXT_PUBLIC_COGNITO_USER_POOL_ID: Cognito ์‚ฌ์šฉ์ž ํ’€ ID
  • NEXT_PUBLIC_COGNITO_CLIENT_ID: Cognito ์•ฑ ํด๋ผ์ด์–ธํŠธ ID
  • NEXT_PUBLIC_COGNITO_DOMAIN: Cognito ์ธ์ฆ ๋„๋ฉ”์ธ
  • NEXT_PUBLIC_COMMUNITY_API_BASE_URL: ์ปค๋ฎค๋‹ˆํ‹ฐ API Gateway URL
  • NEXT_PUBLIC_CHATBOT_API_ENDPOINT: ์ฑ—๋ด‡ API ์—”๋“œํฌ์ธํŠธ
  • NEXT_PUBLIC_PROBLEM_GENERATION_API_BASE_URL: ๋ฌธ์ œ ์ƒ์„ฑ API Gateway URL
  • NEXT_PUBLIC_PROBLEM_API_BASE_URL: ๋ฌธ์ œ API Gateway URL
  • NEXT_PUBLIC_CODE_GRADER_BASE_URL: ์ฝ”๋“œ ์ฑ„์ ๊ธฐ API Gateway URL
  • NEXT_PUBLIC_SUBMISSIONS_API_BASE_URL: ์ œ์ถœ API Gateway URL

์ด ๊ฐ’๋“ค์€ infrastructure/app Terraform ๋ชจ๋“ˆ์˜ ์ถœ๋ ฅ์ด๊ฑฐ๋‚˜ ํ•ด๋‹น ๋ฐฑ์—”๋“œ ์„œ๋น„์Šค ์„ค์ •์—์„œ ํŒŒ์ƒ๋ฉ๋‹ˆ๋‹ค.

URL ์žฌ์ž‘์„ฑ์„ ์œ„ํ•œ CloudFront ํ•จ์ˆ˜

์ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์ •์ ์œผ๋กœ ์ต์ŠคํฌํŠธ๋˜๋ฏ€๋กœ, S3์—์„œ /profile๊ณผ ๊ฐ™์€ ๊ฒฝ๋กœ์— ์ง์ ‘ ์ ‘๊ทผํ•˜๋ฉด ํ•ด๋‹น ํŒŒ์ผ์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— 403/404 ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. S3๋Š” /profile/index.html์„ ๊ธฐ๋Œ€ํ•ฉ๋‹ˆ๋‹ค. infrastructure/app Terraform ์„ค์ •์—๋Š” CloudFront ๋ฐฐํฌ์˜ โ€œ๋ทฐ์–ด ์š”์ฒญ(Viewer Request)โ€ ์ด๋ฒคํŠธ์™€ ์—ฐ๊ฒฐ๋œ aws_cloudfront_function (url_rewrite_function)์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ํ•จ์ˆ˜๋Š” URI๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์žฌ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค:

  • URI๊ฐ€ /๋กœ ๋๋‚˜๋ฉด index.html์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
  • URI์— ํŒŒ์ผ ํ™•์žฅ์ž๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ(์˜ˆ: /profile), /index.html์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฅผ ํ†ตํ•ด ํด๋ผ์ด์–ธํŠธ ์ธก ๋ผ์šฐํŒ…์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ž‘๋™ํ•˜๊ณ  ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ ์‹œ ์ ์ ˆํ•œ index.html ํŒŒ์ผ์ด ๋กœ๋“œ๋˜์–ด Next.js๊ฐ€ ๋ผ์šฐํŒ…์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, CloudFront์—์„œ ๋ฐœ์ƒํ•˜๋Š” 403 ๋ฐ 404 ์˜ค๋ฅ˜๋Š” 200 ์ƒํƒœ ์ฝ”๋“œ์™€ ํ•จ๊ป˜ /index.html์„ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ๊ตฌ์„ฑ๋˜์–ด Next.js ํด๋ผ์ด์–ธํŠธ ์ธก ๋ผ์šฐํ„ฐ์— ๋ผ์šฐํŒ…์„ ์œ„์ž„ํ•ฉ๋‹ˆ๋‹ค.

๋ฆฐํŒ… ๋ฐ ํฌ๋งทํŒ…

์ฝ”๋“œ๋ฒ ์ด์Šค๋ฅผ ๋ฆฐํŠธํ•˜๋ ค๋ฉด:

npm run lint

์ด ํ”„๋กœ์ ํŠธ๋Š” React, JSX ์ ‘๊ทผ์„ฑ, TypeScript์— ๋Œ€ํ•œ ๊ทœ์น™์„ ํฌํ•จํ•˜๋Š” eslint-config-next ์„ค์ •์„ ์‚ฌ์šฉํ•˜๋Š” ESLint๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ESLint ์„ค์ •์€ eslint.config.mjs์— ์žˆ์Šต๋‹ˆ๋‹ค.

์ฃผ์š” Next.js ์„ค์ • (next.config.ts)

next.config.ts ํŒŒ์ผ์—๋Š” ์ •์  ์‚ฌ์ดํŠธ ์ƒ์„ฑ์„ ์œ„ํ•œ ์ค‘์š”ํ•œ ์„ค์ •์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค:

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  output: "export", // ์ •์  HTML ์ต์ŠคํฌํŠธ ํ™œ์„ฑํ™”
  trailingSlash: true, // URL ๋์— ์Šฌ๋ž˜์‹œ ์ถ”๊ฐ€ (์˜ˆ: /about/ ๋Œ€์‹  /about) (S3 ํ˜ธํ™˜์„ฑ)
  reactStrictMode: true,
  images: {
    unoptimized: true, // Next.js ์ด๋ฏธ์ง€ ์ตœ์ ํ™” API ๋น„ํ™œ์„ฑํ™” (`next export`์— ํ•„์š”)
  },
};

export default nextConfig;
  • output: "export": ์ด๊ฒƒ์ด ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค. Next.js์—๊ฒŒ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ S3์™€ ๊ฐ™์€ ์ •์  ์›น ์„œ๋ฒ„์—์„œ ํ˜ธ์ŠคํŒ…ํ•  ์ˆ˜ ์žˆ๋Š” ์ •์  HTML, CSS, JavaScript ํŒŒ์ผ ๋ชจ์Œ์œผ๋กœ ๋นŒ๋“œํ•˜๋„๋ก ์ง€์‹œํ•ฉ๋‹ˆ๋‹ค.
  • trailingSlash: true: S3๊ฐ€ ์›น์‚ฌ์ดํŠธ ํ˜ธ์ŠคํŒ…์šฉ์œผ๋กœ ๊ตฌ์„ฑ๋˜๊ฑฐ๋‚˜ CloudFront๋ฅผ ํ†ตํ•ด ์ ‘๊ทผ๋  ๋•Œ S3 ๊ฐ์ฒด ํ™•์ธ์— ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค. S3๋Š” ์ข…์ข… ์Šฌ๋ž˜์‹œ๋กœ ๋๋‚˜๋Š” ๋””๋ ‰ํ† ๋ฆฌ์™€ ์œ ์‚ฌํ•œ ๊ฒฝ๋กœ์—์„œ ๋” ์ž˜ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.
  • images: { unoptimized: true }: ๊ธฐ๋ณธ Next.js ์ด๋ฏธ์ง€ ์ตœ์ ํ™” API๋Š” Node.js ์„œ๋ฒ„๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ •์  ์ต์ŠคํฌํŠธ์˜ ๊ฒฝ์šฐ ์ด๋ฏธ์ง€๋Š” ์ •์  ์—์…‹์œผ๋กœ ์ฒ˜๋ฆฌ๋˜์–ด์•ผ ํ•˜๋ฏ€๋กœ ์ตœ์ ํ™”๊ฐ€ ๋น„ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค. ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์ด๋ฏธ์ง€ ์ตœ์ ํ™”๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ๋นŒ๋“œ ์‹œ ๋˜๋Š” CloudFront ๊ธฐ๋Šฅ์„ ํ†ตํ•ด ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค(์—ฌ๊ธฐ์„œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๊ตฌ์„ฑ๋˜์ง€ ์•Š์Œ).

์Šคํƒ€์ผ๋ง (Tailwind CSS)

์ด ํ”„๋กœ์ ํŠธ๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ ์šฐ์„  ์Šคํƒ€์ผ๋ง์„ ์œ„ํ•ด Tailwind CSS๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

  • ์„ค์ •: tailwind.config.js
  • PostCSS: postcss.config.js (Tailwind ๋ฐ Autoprefixer ํ†ตํ•ฉ)
  • ์‚ฌ์šฉ์ž ์ •์˜: tailwind.config.js์—๋Š” ์‚ฌ์šฉ์ž ์ •์˜ ์ƒ‰์ƒ ํŒ”๋ ˆํŠธ(primary, secondary ๋“ฑ) ๋ฐ ํ‚คํ”„๋ ˆ์ž„ ์• ๋‹ˆ๋ฉ”์ด์…˜(slideDown)์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.
  • ํƒ€์ดํฌ๊ทธ๋ž˜ํ”ผ: @tailwindcss/typography ํ”Œ๋Ÿฌ๊ทธ์ธ์€ ๋งˆํฌ๋‹ค์šด์œผ๋กœ ์ƒ์„ฑ๋œ ์ฝ˜ํ…์ธ  ์Šคํƒ€์ผ๋ง์„ ์œ„ํ•ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

TypeScript ์„ค์ •

์ด ํ”„๋กœ์ ํŠธ๋Š” TypeScript์šฉ์œผ๋กœ ์„ค์ •๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

  • ์„ค์ •: tsconfig.json
  • ์ฃผ์š” ์„ค์ •:
    • target: "ES2017"
    • module: "esnext"
    • moduleResolution: "bundler" (์ตœ์‹  ๋ชจ๋“ˆ ํ™•์ธ ๋ฐฉ์‹)
    • jsx: "preserve"
    • strict: true (๋ชจ๋“  ์—„๊ฒฉํ•œ ํƒ€์ž… ๊ฒ€์‚ฌ ์˜ต์…˜ ํ™œ์„ฑํ™”)
    • noEmit: true (TypeScript ์ปดํŒŒ์ผ๋Ÿฌ๋Š” JS๋ฅผ ์ƒ์„ฑํ•˜์ง€ ์•Š์Œ; Next.js๊ฐ€ ์ฒ˜๋ฆฌ)
    • paths: { "@/*": ["./src/*"] } (src ๋””๋ ‰ํ† ๋ฆฌ์—์„œ ์ ˆ๋Œ€ ๊ฒฝ๋กœ ์ž„ํฌํŠธ ํ—ˆ์šฉ, ์˜ˆ: import MyComponent from '@/components/MyComponent').

๋” ์•Œ์•„๋ณด๊ธฐ

์ด ํ”„๋กœ์ ํŠธ์—์„œ ์‚ฌ์šฉ๋œ ๊ธฐ์ˆ ์— ๋Œ€ํ•ด ๋” ์•Œ์•„๋ณด๋ ค๋ฉด ๋‹ค์Œ ์ž๋ฃŒ๋ฅผ ์ฐธ์กฐํ•˜์‹ญ์‹œ์˜ค: