/Foomee

๐Ÿฅ‘ Foomee - ๋งž์ถคํ˜• ์‹๋‹จ ๊ธฐ๋ก, ๋ถ„์„ ์„œ๋น„์Šค

Primary LanguageTypeScript

Foomee

Cover

๋ชฉ์ฐจ

  1. ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ ๋ฐ ๊ฐœ์š”
  2. ๊ธฐ์ˆ  ์Šคํƒ ๋ฐ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ
  3. User Flow
  4. UI ๊ตฌ์„ฑ ์š”์†Œ
  5. ํ•ต์‹ฌ ๊ธฐ๋Šฅ ๋ฐ ์ฝ”๋“œ
  6. ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…
  7. Branch ์ „๋žต
  8. ๋ฌธ์„œ
  9. User Test
  10. ํšŒ๊ณ 

1. ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ ๋ฐ ๊ฐœ์š”

Foomee ํ”„๋กœ์ ํŠธ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์ž์‹ ์˜ ๊ฑด๊ฐ•์„ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ๋•๊ธฐ ์œ„ํ•œ ๊ฐœ์ธ ๋งž์ถคํ˜• ์‹๋‹จ ๊ธฐ๋ก, ๋ถ„์„ ์„œ๋น„์Šค ์ž…๋‹ˆ๋‹ค.

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

[ํ•ต์‹ฌ ๊ธฐ๋Šฅ]

  1. ์˜์–‘ ์„ฑ๋ถ„ ๋ถ„์„
    • ์˜์–‘์„ฑ๋ถ„ API๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ์‹๋‹จ์˜ ์˜์–‘ ์„ฑ๋ถ„์„ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค.
    • ์ผ์ผ ์„ญ์ทจ๋Ÿ‰๊ณผ ๊ถŒ์žฅ ์„ญ์ทจ๋Ÿ‰์„ ๋น„๊ตํ•˜์—ฌ ๊ฑด๊ฐ• ์ƒํƒœ๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋งํ•ฉ๋‹ˆ๋‹ค.
  2. ์‹๋‹จ ๋ฐ ์ฒด์ค‘ ๊ธฐ๋ก
    • ์‚ฌ์šฉ์ž๊ฐ€ ์„ญ์ทจํ•œ ์Œ์‹๊ณผ ์ฒด์ค‘์„ ์ผ๋ณ„๋กœ ๊ธฐ๋กํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ๊ธฐ๋ก๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์บ˜๋ฆฐ๋” ํ˜•์‹์œผ๋กœ ์ œ๊ณตํ•˜์—ฌ, ์‹๋‹จ๊ณผ ์ฒด์ค‘ ๋ณ€ํ™”๋ฅผ ํ•œ๋ˆˆ์— ์‰ฝ๊ฒŒ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  3. ๋ฐ์ดํ„ฐ ์‹œ๊ฐํ™”
    • ์‚ฌ์šฉ์ž์˜ ์‹๋‹จ ๋ฐ ์ฒด์ค‘ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์–‘ํ•œ ์ฐจํŠธ๋กœ ์‹œ๊ฐํ™”ํ•ฉ๋‹ˆ๋‹ค.
    • ์นผ๋กœ๋ฆฌ ์„ญ์ทจ, ์˜์–‘์†Œ ๋น„์œจ, ์ฒด์ค‘ ๋ณ€ํ™” ๋“ฑ์„ ์ง๊ด€์ ์œผ๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  4. ์ฑ—๋ด‡ ํ”ผ๋“œ๋ฐฑ
    • OPENAI API ๋ฅผ ํ™œ์šฉํ•ด ์‚ฌ์šฉ์ž์˜ ์‹๋‹จ ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
    • ์ฑ—๋ด‡์ด ๊ฐœ์ธ ๋งž์ถคํ˜• ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

[ํ”„๋กœ์ ํŠธ ์ผ์ •]

ํ”„๋กœ์ ํŠธ ์ผ์ •

2. ๊ธฐ์ˆ ์Šคํƒ ๋ฐ ๊ฐœ๋ฐœํ™˜๊ฒฝ

3. User Flow

FESP2 project figjam.png

4. UI ๊ตฌ์„ฑ ์š”์†Œ

UI PREVIEW.png

5. ํ•ต์‹ฌ ๊ธฐ๋Šฅ ๋ฐ ์ฝ”๋“œ

5-1. ChatGPT ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ

  • ์‚ฌ์šฉ์ž์˜ ์‹๋‹จ ๋ฐ์ดํ„ฐ๋ฅผ ์ž…๋ ฅ๋ฐ›์•„, ์ด๋ฅผ ๋ถ„์„ํ•˜๋Š” ๋ฐ ์ ํ•ฉํ•œ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ƒ์„ฑํ•˜๊ณ , OpenAI์˜ gpt-4o-mini ๋ชจ๋ธ์„ ์ด์šฉํ•ด ๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

chatAction.ts

// ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ
const newPrompt = `์˜ค๋Š˜ ๋‚ด๊ฐ€ ๋จน์€ ์‹๋‹จ์„ ๋ถ„์„ํ•ด์ค˜! ์‹๋‹จ์€ ${result} ์ด์•ผ. ๊ฒฐ๊ณผ์—์„œ ์‹๋‹จ ์š”์•ฝ์€ ๊ฐ„๋‹จํ•˜๊ฒŒ ์Œ์‹๋ช…๋งŒ ํ‘œ์‹œํ•˜๊ณ , ์˜์–‘์„ฑ๋ถ„ ๊ณ„์‚ฐ์€ ์ œ์™ธํ•ด์ค˜.`;

('use server');

import OpenAI from 'openai';
const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});

export async function getChatResponse(prompt: string) {
  try {
    const completion = await openai.chat.completions.create({
      model: 'gpt-4o-mini',
      messages: [
        {
          role: 'system',
          content:
            '๋‹น์‹ ์€ ์˜์–‘ ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค. ์ฃผ์–ด์ง„ ์‹๋‹จ์„ ๋ถ„์„ํ•˜์—ฌ ์˜์–‘ ์„ฑ๋ถ„, ๊ท ํ˜•, ๊ฑด๊ฐ•์— ๋Œ€ํ•œ ํ‰๊ฐ€๋ฅผ ์ œ๊ณตํ•˜๊ณ , ํ•„์š”ํ•  ๊ฒฝ์šฐ ๊ฐœ์„  ์‚ฌํ•ญ์„ ์ œ์‹œํ•˜์„ธ์š”. ๋‹ต๋ณ€์€ ํ•œ๊ตญ์–ด๋กœ ์ œ๊ณตํ•˜์„ธ์š”.',
        },
        { role: 'user', content: prompt },
      ],
    });

    return completion.choices[0].message.content;
  } catch (error) {
    console.error('Error fetching from OpenAI:', error);
  }
}

5-2. ์ž๋™ ๋นŒ๋“œ ๋ฐ ๋ฐฐํฌ(CI/CD)

  • GitHub Actions๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฝ”๋“œ๊ฐ€ ๋ฉ”์ธ ๋ธŒ๋žœ์น˜์— ํ‘ธ์‹œ๋  ๋•Œ ์ž๋™์œผ๋กœ ๋นŒ๋“œํ•˜๊ณ  AWS EC2์— ๋ฐฐํฌ๋˜๋Š” CI/CD ํŒŒ์ดํ”„๋ผ์ธ์„ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.

.github/workflows/depoly.yml

name: Deploy Next.js to EC2

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Set up Node.js
        uses: actions/setup-node@v2
        with:
          node-version: '20'

      - name: Install pnpm
        run: |
          curl -L https://github.com/pnpm/pnpm/releases/download/v7.18.1/pnpm-linux-x64 -o /usr/local/bin/pnpm
          chmod +x /usr/local/bin/pnpm

      - name: Install dependencies
        run: pnpm install

      - name: Build Next.js app
        env:
          NEXT_PUBLIC_API_SERVER: ${{ secrets.NEXT_PUBLIC_API_SERVER }}
          NEXT_PUBLIC_DELAY: ${{ secrets.NEXT_PUBLIC_DELAY }}
          NEXT_PUBLIC_LIMIT: ${{ secrets.NEXT_PUBLIC_LIMIT }}
          NEXT_PUBLIC_TRIAL_EMAIL: ${{ secrets.NEXT_PUBLIC_TRIAL_EMAIL }}
          NEXT_PUBLIC_TRIAL_PW: ${{ secrets.NEXT_PUBLIC_TRIAL_PW }}
          NEXT_PUBLIC_API_KEY: ${{ secrets.NEXT_NEXT_PUBLIC_API_KEY }}
          NEXT_PUBLIC_CLIENT_ID: ${{ secrets.NEXT_PUBLIC_CLIENT_ID }}

        run: pnpm run build

      - name: Add EC2 to known hosts
        run: |
          mkdir -p ~/.ssh
          ssh-keyscan -H ${{ secrets.EC2_HOST }} >> ~/.ssh/known_hosts

      - name: Compress build folder
        run: tar -czf next-app.tar.gz .next package.json node_modules public

      - name: Copy files to EC2
        env:
          EC2_HOST: ${{ secrets.EC2_HOST }}
          EC2_USER: ${{ secrets.EC2_USER }}
          EC2_KEY: ${{ secrets.EC2_KEY }}
        run: |
          echo "${{ secrets.EC2_KEY }}" > ec2_key.pem
          chmod 600 ec2_key.pem
          scp -i ec2_key.pem next-app.tar.gz ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}:/home/${{ secrets.EC2_USER }}/

      - name: Deploy on EC2
        env:
          NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
          NEXTAUTH_URL: ${{ secrets.NEXTAUTH_URL }}
          AUTH_TRUST_HOST: ${{ secrets.AUTH_TRUST_HOST }}
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          AUTH_GITHUB_CLIENT_ID: ${{ secrets.AUTH_GITHUB_CLIENT_ID }}
          AUTH_GITHUB_CLIENT_SECRET: ${{ secrets.AUTH_GITHUB_CLIENT_SECRET }}
          GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
          GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
          NAVER_CLIENT_ID: ${{ secrets.NAVER_CLIENT_ID }}
          NAVER_CLIENT_SECRET: ${{ secrets.NAVER_CLIENT_SECRET }}
          KAKAO_CLIENT_ID: ${{ secrets.KAKAO_CLIENT_ID }}
          KAKAO_CLIENT_SECRET: ${{ secrets.KAKAO_CLIENT_SECRET }}
          KAKAO_REDIRECT_URI: ${{ secrets.KAKAO_REDIRECT_URI }}
        run: |
          ssh -i ec2_key.pem ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} << 'EOF'
          cd /home/${{ secrets.EC2_USER }}
          tar -xzf next-app.tar.gz
          pnpm install --production
          echo "NEXTAUTH_SECRET=${{ secrets.NEXTAUTH_SECRET }}" >> .env
          echo "NEXTAUTH_URL=${{ secrets.NEXTAUTH_URL }}" >> .env
          echo "AUTH_TRUST_HOST=${{ secrets.AUTH_TRUST_HOST }}" >> .env
          echo "OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY }}" >> .env
          echo "AUTH_GITHUB_CLIENT_ID=${{ secrets.AUTH_GITHUB_CLIENT_ID }}" >> .env
          echo "AUTH_GITHUB_CLIENT_SECRET=${{ secrets.AUTH_GITHUB_CLIENT_SECRET }}" >> .env
          echo "GOOGLE_CLIENT_ID=${{ secrets.GOOGLE_CLIENT_ID }}" >> .env
          echo "GOOGLE_CLIENT_SECRET=${{ secrets.GOOGLE_CLIENT_SECRET }}" >> .env
          echo "NAVER_CLIENT_ID=${{ secrets.NAVER_CLIENT_ID }}" >> .env
          echo "NAVER_CLIENT_SECRET=${{ secrets.NAVER_CLIENT_SECRET }}" >> .env
          echo "KAKAO_CLIENT_ID=${{ secrets.KAKAO_CLIENT_ID }}" >> .env
          echo "KAKAO_CLIENT_SECRET=${{ secrets.KAKAO_CLIENT_SECRET }}" >> .env
          echo "KAKAO_REDIRECT_URI=${{ secrets.KAKAO_REDIRECT_URI }}" >> .env
          sudo npm install -g pm2
          pm2 stop all || true
          pm2 start npm --name "foomee-app" -- run start
          pm2 save
          EOF

5-3. Swiper ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ™œ์šฉํ•œ ๋ฌดํ•œํžˆ ๊ณผ๊ฑฐ๋กœ ๊ฐ€๋Š” ์ฐจํŠธ ์ƒ์„ฑ

์ถ”๊ฐ€ ์˜ˆ์ •

5-4. ๋ฌดํ•œ์Šคํฌ๋กค ์ปค์Šคํ…€ ํ›…

  • ๋ชจ๋ฐ”์ผ ์‚ฌ์šฉ์ž์˜ ๊ฒฝํ—˜์„ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•ด, ํŽ˜์ด์ง€๋„ค์ด์…˜ ๋Œ€์‹  ๋ฌดํ•œ ์Šคํฌ๋กค ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜์—ฌ ์‚ฌ์šฉ์ž์˜ ํŽธ์˜์„ฑ์„ ๋†’์˜€์Šต๋‹ˆ๋‹ค.
  • Intersection Observer API๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์Šคํฌ๋กค ์ด๋ฒคํŠธ๋กœ ์ธํ•œ ์„ฑ๋Šฅ ์ €ํ•˜๋ฅผ ๋ฐฉ์ง€ํ•˜๊ณ , ๋ถ€๋“œ๋Ÿฌ์šด ๋ฌดํ•œ ์Šคํฌ๋กค ๊ฒฝํ—˜์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

useInfiniteScroll.ts

import { useEffect, useRef, useCallback } from 'react';

interface UseInfiniteScrollProps {
  hasMore: boolean;
  loadMore: () => void;
}

const useInfiniteScroll = ({ hasMore, loadMore }: UseInfiniteScrollProps) => {
  const observerRef = useRef<IntersectionObserver | null>(null);
  const lastElementRef = useRef<HTMLDivElement | null>(null);

  const handleObserver = useCallback(
    (entries: IntersectionObserverEntry[]) => {
      const target = entries[0];
      if (target.isIntersecting && hasMore) {
        loadMore();
      }
    },
    [hasMore, loadMore],
  );

  useEffect(() => {
    if (observerRef.current) observerRef.current.disconnect();

    observerRef.current = new IntersectionObserver(handleObserver);
    if (lastElementRef.current) {
      observerRef.current.observe(lastElementRef.current);
    }

    return () => {
      if (observerRef.current) {
        observerRef.current.disconnect();
      }
    };
  }, [handleObserver]);

  return { lastElementRef };
};

export default useInfiniteScroll;

6. ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…

6-1. NextAuth ๋„ค์ด๋ฒ„ ์†Œ์…œ ๋กœ๊ทธ์ธ expires_in Type Error

NextAuth v5๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋„ค์ด๋ฒ„ ์†Œ์…œ ๋กœ๊ทธ์ธ์„ ๊ตฌํ˜„ํ•˜๋Š” ๊ณผ์ •์—์„œ expires_in ํ•„๋“œ์˜ ํƒ€์ž… ์˜ค๋ฅ˜๋กœ ์ธํ•ด OperationProcessingError๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ๋„ค์ด๋ฒ„์˜ OAuth2.0 ์‘๋‹ต์—์„œ expires_in ๊ฐ’์ด ๊ณต์‹ ๋ฌธ์„œ์™€ ๋‹ฌ๋ฆฌ ๋ฌธ์ž์—ด๋กœ ๋ฐ˜ํ™˜๋˜๋Š” ๋ฌธ์ œ์˜€์Šต๋‹ˆ๋‹ค. OAuth2.0 ์ŠคํŽ™์— ๋”ฐ๋ฅด๋ฉด expires_in ํ•„๋“œ๋Š” ์ˆซ์žํ˜•์ด์–ด์•ผ ํ•˜์ง€๋งŒ, ๋„ค์ด๋ฒ„๋Š” ์ด ๋ถ€๋ถ„์„ ๋ฌธ์ž์—ด๋กœ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์žˆ์—ˆ๊ณ , ์ด๋กœ ์ธํ•ด NextAuth์—์„œ ์ด๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค.

๋ฌธ์ œ ํ•ด๊ฒฐ์„ ์œ„ํ•ด ๋„ค์ด๋ฒ„ ๊ฐœ๋ฐœ์ž ํฌ๋Ÿผ์—์„œ ๊ด€๋ จ ์˜ค๋ฅ˜์— ๋Œ€ํ•œ ๋„ค์ด๋ฒ„์˜ ๋‹ต๋ณ€์„ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค. ๋„ค์ด๋ฒ„ ์ธก์€ ํ˜„์žฌ OAuth2.0 ์ŠคํŽ™๊ณผ ๋‹ค๋ฅด๊ฒŒ ๋™์ž‘ํ•˜๊ณ  ์žˆ์Œ์„ ์ธ์ •ํ•˜๋ฉด์„œ๋„, ๊ธฐ์กด ์ŠคํŽ™์„ ์ˆ˜์ •ํ•˜๊ธฐ๋Š” ์–ด๋ ต๋‹ค๋Š” ์ž…์žฅ์„ ๋ฐํ˜”์Šต๋‹ˆ๋‹ค. แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2024-08-28 แ„‹แ…ฉแ„Œแ…ฅแ†ซ 2.12.22.png

๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด expires_in ํ•„๋“œ๊ฐ€ ๋ฌธ์ž์—ด ํƒ€์ž…์ด์–ด๋„ ๋ฌธ์ œ์—†์ด ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” forked ๋ฒ„์ „์˜ oauth4webapi ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด pnpm์„ ์‚ฌ์šฉํ•˜์—ฌ oauth4webapi ํŒจํ‚ค์ง€๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋“œํ–ˆ์Šต๋‹ˆ๋‹ค.

// package.json
...
"pnpm": {
  "overrides": {
    "oauth4webapi": "npm:@jacobkim/oauth4webapi@^2.10.4"
  }
}

์ด ์„ค์ •์„ ํ†ตํ•ด ๋„ค์ด๋ฒ„์˜ ๋น„ํ‘œ์ค€ ์‘๋‹ต์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ์ปค์Šคํ„ฐ๋งˆ์ด์ฆˆ๋œ oauth4webapi ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด NextAuth์—์„œ ๋„ค์ด๋ฒ„ ๋กœ๊ทธ์ธ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์—ˆ๊ณ , ์ถ”๊ฐ€์ ์œผ๋กœ pnpm์„ ๋„์ž…ํ•˜๋ฉด์„œ ํŒจํ‚ค์ง€๋ฅผ ๋”์šฑ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

7. Branch ์ „๋žต

main - develop - feature

  • main : ํ”„๋กœ๋•์…˜์šฉ ๋ธŒ๋žœ์น˜ (๋ฐฐํฌ์šฉ)
  • develop : ํ†ตํ•ฉ ๋ฐ ํ…Œ์ŠคํŠธ์šฉ ๋ธŒ๋žœ์น˜ (๊ธฐ๋Šฅ ๋ณ‘ํ•ฉ)
  • feat/์ด์Šˆ๋ฒˆํ˜ธ-๊ธฐ๋Šฅ : ๊ธฐ๋Šฅ๋ณ„ ์ž‘์—…์šฉ ๋ธŒ๋žœ์น˜

์ปค๋ฐ‹ ์ปจ๋ฒค์…˜

keyword description
FEAT ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ ์ถ”๊ฐ€
FIX ๋ฒ„๊ทธ ์ˆ˜์ •
DOCS ๋ฌธ์„œ ์ž‘์—…
DELETE ๊ธฐ๋Šฅ์ด ์‚ญ์ œ๋  ๋•Œ
STYLE ์ฝ”๋“œ ํฌ๋งทํŒ…, ์„ธ๋ฏธ์ฝœ๋ก  ๋ˆ„๋ฝ, ์ฝ”๋“œ ๋ณ€๊ฒฝ์ด ์—†๋Š” ๊ฒฝ์šฐ
CHORE ๋นŒ๋“œ ์—…๋ฌด ์ˆ˜์ •, ํŒจํ‚ค์ง€ ๋งค๋‹ˆ์ € ์ˆ˜์ •
DESIGN CSS ๋“ฑ ์‚ฌ์šฉ์ž UI ๋””์ž์ธ ๋ณ€๊ฒฝ
REFACTOR ์ฝ”๋“œ ๋ฆฌํŒฉํ† ๋ง
TEST ํ…Œ์ŠคํŠธ
INIT ํ”„๋กœ์ ํŠธ ์ดˆ๊ธฐ ์ƒ์„ฑ
RENAME ํŒŒ์ผ ํ˜น์€ ํด๋”๋ช…์„ ์ˆ˜์ •ํ•˜๊ฑฐ๋‚˜ ์˜ฎ๊ธฐ๋Š” ์ž‘์—…
REMOVE ํŒŒ์ผ์„ ์‚ญ์ œํ•˜๋Š” ์ž‘์—…๋งŒ ์ˆ˜ํ–‰ํ•œ ๊ฒฝ์šฐ

8. ๋ฌธ์„œ

๐Ÿ–ฅ๏ธ ๊ฐœ๋ฐœ์ผ์ง€

๐Ÿ“‹ ๊ธฐ๋Šฅ ๋ช…์„ธ์„œ

๐ŸŽจ Figma | ์™€์ด์–ดํ”„๋ ˆ์ž„

๐ŸŽจ Figma | ๋””์ž์ธ

๐ŸŽจ Figma | ํ”„๋กœํ† ํƒ€์ž…

9. User Test

10. ํšŒ๊ณ 

[๊ฐœ๋ฐœ ๊ณผ์ •์—์„œ์˜ ๋…ธ๋ ฅ]

์ด๋ฒˆ ํ”„๋กœ์ ํŠธ๋Š” ๊ฐœ์ธ ํ”„๋กœ์ ํŠธ์ž„์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ , ์ตœ๋Œ€ํ•œ ํŒ€ ํ”„๋กœ์ ํŠธ์ฒ˜๋Ÿผ ์ฒด๊ณ„์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๊ณ ์ž ๋…ธ๋ ฅํ–ˆ์Šต๋‹ˆ๋‹ค. Eslint์™€ Prettier ์„ค์ •์„ ํ†ตํ•ด ์ฝ”๋“œ ์Šคํƒ€์ผ์„ ์ผ๊ด€๋˜๊ฒŒ ์œ ์ง€ํ•˜๊ณ , GitHub์˜ Issue, PR ํ…œํ”Œ๋ฆฟ, Git์˜ Commit ํ…œํ”Œ๋ฆฟ์„ ํ™œ์šฉํ•˜์—ฌ ์ฝ”๋“œ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋ช…ํ™•ํ•˜๊ฒŒ ๊ธฐ๋กํ•˜๊ณ  ์ถ”์ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ๊ฐœ๋ฐœ ๊ณผ์ •์„ ์ฒด๊ณ„์ ์œผ๋กœ ๋ฌธ์„œํ™”ํ•˜์—ฌ, ๋‹ค๋ฅธ ์‚ฌ๋žŒ์ด ํ”„๋กœ์ ํŠธ๋ฅผ ์ดํ•ดํ•˜๊ณ  ์ปค๋ฎค๋‹ˆ์ผ€์ด์…˜ ํ•  ์ˆ˜ ์žˆ๋„๋ก ์‹ ๊ฒฝ์จ์„œ ์ž‘์—…์„ ์ง„ํ–‰ํ–ˆ์Šต๋‹ˆ๋‹ค.

ํ”„๋ก ํŠธ์—”๋“œ์˜ ์—ญํ• ์€ ์‚ฌ์šฉ์ž๊ฐ€ ๊ณ ๋ฏผํ•˜์ง€ ์•Š๊ณ  ์“ธ ์ˆ˜ ์žˆ๋Š” ๊ฒฝํ—˜์„ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฒˆ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ ์‚ฌ์šฉ์ž๊ฐ€ ๊ณ ๋ฏผํ•˜์ง€ ์•Š๊ณ  ์ง๊ด€์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ๊ณผ ํ™”๋ฉด์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ๊ณ ๋ฏผํ–ˆ์Šต๋‹ˆ๋‹ค.

[๋ฆฌํŒฉํ† ๋ง ๊ณ„ํš]

  • ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ๊ธฐ๋Šฅ์˜ ๊ตฌํ˜„
  • ์œ ์ € ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ชจ๋ฐ”์ผ ์ตœ์ ํ™” ๋ฐ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ๊ฐœ์„ 
  • AI ๋ถ„์„ ๊ธฐ๋Šฅ ํ™•์žฅ: ์ฃผ๊ฐ„ ๋ฐ ์›”๊ฐ„ ๋ ˆํฌํŠธ ์ž‘์„ฑ ๊ธฐ๋Šฅ ์ถ”๊ฐ€
  • ๋…์ž์ ์ธ ์˜์–‘์„ฑ๋ถ„ DB ๊ตฌ์ถ•: ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ์˜์–‘์„ฑ๋ถ„ ์ž…๋ ฅํ•˜๋Š” ๊ธฐ๋Šฅ, ์Œ์‹ ๊ฒ€์ƒ‰ ์ตœ์ ํ™”

[ํ›„๊ธฐ]

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