Skip to content

Latest commit

ย 

History

History
447 lines (338 loc) ยท 11.1 KB

File metadata and controls

447 lines (338 loc) ยท 11.1 KB

๐Ÿ“ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ๋ถ„์„

1. ๊ธฐ์ˆ  ์Šคํƒ

ํ•ต์‹ฌ ํ”„๋ ˆ์ž„์›Œํฌ

  • Next.js 15.5.5 - React 19.1.0 ๊ธฐ๋ฐ˜
  • TypeScript 5 - ์—„๊ฒฉํ•œ ํƒ€์ž… ์‹œ์Šคํ…œ
  • Tailwind CSS 4 - ์œ ํ‹ธ๋ฆฌํ‹ฐ ๊ธฐ๋ฐ˜ ์Šคํƒ€์ผ๋ง
  • Turbopack - Next.js์˜ ์ƒˆ๋กœ์šด ๋ฒˆ๋“ค๋Ÿฌ ์‚ฌ์šฉ

์ƒํƒœ ๊ด€๋ฆฌ & ๋ฐ์ดํ„ฐ ํŽ˜์นญ

  • Zustand - ๊ฒฝ๋Ÿ‰ ์ƒํƒœ ๊ด€๋ฆฌ (ํ† ํฐ & ์‚ฌ์šฉ์ž ์ •๋ณด)
  • React Query (TanStack Query) - ์„œ๋ฒ„ ์ƒํƒœ ๊ด€๋ฆฌ
  • Axios - HTTP ํด๋ผ์ด์–ธํŠธ

UI ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

  • shadcn/ui (New York ์Šคํƒ€์ผ) - Radix UI ๊ธฐ๋ฐ˜ ์ปดํฌ๋„ŒํŠธ
  • Lucide React - ์•„์ด์ฝ˜
  • class-variance-authority (cva) - ๋ณ€ํ˜• ์Šคํƒ€์ผ ๊ด€๋ฆฌ
  • Pretendard Variable - ํ•œ๊ธ€ ํฐํŠธ

ํผ ๊ด€๋ฆฌ & ๊ฒ€์ฆ

  • React Hook Form - ํผ ์ƒํƒœ ๊ด€๋ฆฌ
  • Zod - ์Šคํ‚ค๋งˆ ๊ฒ€์ฆ

๐Ÿ“ ํ”„๋กœ์ ํŠธ ์•„ํ‚คํ…์ฒ˜

ํด๋” ๊ตฌ์กฐ ์›์น™

src/
โ”œโ”€โ”€ app/                          # Next.js App Router
โ”‚   โ”œโ”€โ”€ (authorized)/            # ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž ์ „์šฉ ๋ผ์šฐํŠธ ๊ทธ๋ฃน
โ”‚   โ”‚   โ”œโ”€โ”€ creator/             # ํฌ๋ฆฌ์—์ดํ„ฐ ํŽ˜์ด์ง€
โ”‚   โ”‚   โ””โ”€โ”€ learner/             # ๋Ÿฌ๋„ˆ ํŽ˜์ด์ง€
โ”‚   โ”œโ”€โ”€ login/                   # ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€
โ”‚   โ”œโ”€โ”€ signup/                  # ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€
โ”‚   โ””โ”€โ”€ _components/             # ์•ฑ ๋ ˆ๋ฒจ ์ปดํฌ๋„ŒํŠธ
โ”‚
โ””โ”€โ”€ shared/                      # ๊ณต์œ  ๋ฆฌ์†Œ์Šค (์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅ)
    โ”œโ”€โ”€ components/ui/           # UI ์ปดํฌ๋„ŒํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
    โ”œโ”€โ”€ lib/                     # ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜
    โ”œโ”€โ”€ services/                # API ์„œ๋น„์Šค ๋ ˆ์ด์–ด
    โ”‚   โ””โ”€โ”€ [domain]/            # ๋„๋ฉ”์ธ๋ณ„ ๊ตฌ์„ฑ
    โ”‚       โ”œโ”€โ”€ *.service.ts     # API ํ˜ธ์ถœ ํ•จ์ˆ˜
    โ”‚       โ”œโ”€โ”€ *.hook.ts        # React Query ํ›…
    โ”‚       โ””โ”€โ”€ *.type.ts        # ํƒ€์ž… ์ •์˜
    โ”œโ”€โ”€ stores/                  # Zustand ์Šคํ† ์–ด
    โ”œโ”€โ”€ providers/               # React Context Providers
    โ””โ”€โ”€ types/                   # ์ „์—ญ ํƒ€์ž… ์ •์˜

๋ผ์šฐํŠธ ๊ทธ๋ฃน ์ „๋žต

  • (authorized) - ์ธ์ฆ ํ•„์š”ํ•œ ํŽ˜์ด์ง€ ๊ทธ๋ฃนํ™”
  • (mypage) - ๋งˆ์ดํŽ˜์ด์ง€ ๊ด€๋ จ ๋ผ์šฐํŠธ ๊ทธ๋ฃน
  • URL์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์œผ๋ฉด์„œ ๋ ˆ์ด์•„์›ƒ ๊ณต์œ 

๐ŸŽจ ๋””์ž์ธ ์‹œ์Šคํ…œ

์ปฌ๋Ÿฌ ์‹œ์Šคํ…œ

Primary Colors (๋ธŒ๋žœ๋“œ ๊ทธ๋ฆฐ)

--color-primary-green-50: #f6fdf1 --color-primary-green-100: #e8fbd9 --color-primary-green-200: #d1f8b4
  --color-primary-green-300: #b2f381 --color-primary-green-400: #9bef5b --color-primary-green-500: #7dea29
  /* ๋ฉ”์ธ ์ปฌ๋Ÿฌ */ --color-primary-green-600: #67d215 /* ๋ฒ„ํŠผ ๊ธฐ๋ณธ */ --color-primary-green-700: #51a611 /* ๋ฒ„ํŠผ ํ˜ธ๋ฒ„ */
  --color-primary-green-800: #3b780c --color-primary-green-900: #244a08;

์‹œ๋งจํ‹ฑ ์ปฌ๋Ÿฌ (oklch ์ƒ‰์ƒ ๊ณต๊ฐ„ ์‚ฌ์šฉ)

  • Background: ํฐ์ƒ‰ (Light) / ์–ด๋‘์šด ํšŒ์ƒ‰ (Dark)
  • Primary: ๊ธฐ๋ณธ ๋ฒ„ํŠผ ๋ฐ ๊ฐ•์กฐ ์ƒ‰์ƒ
  • Secondary: ๋ณด์กฐ ๋ฒ„ํŠผ
  • Muted: ๋น„ํ™œ์„ฑํ™” ์š”์†Œ
  • Destructive: ์‚ญ์ œ/๊ฒฝ๊ณ  ์•ก์…˜

ํƒ€์ดํฌ๊ทธ๋ž˜ํ”ผ

  • ํฐํŠธ: Pretendard Variable (๊ฐ€๋ณ€ ํฐํŠธ)
  • ํด๋ฐฑ: Apple SD Gothic Neo, Noto Sans KR, Malgun Gothic

Border Radius

--radius: 0.625rem (10px) --radius-sm: 6px --radius-md: 8px --radius-lg: 10px --radius-xl: 14px;

๋‹คํฌ๋ชจ๋“œ ์ง€์›

  • CSS ๋ณ€์ˆ˜ ๊ธฐ๋ฐ˜ ํ…Œ๋งˆ ์ „ํ™˜
  • .dark ํด๋ž˜์Šค๋กœ ์ œ์–ด

๐Ÿ’ป ์ฝ”๋“œ ์ปจ๋ฒค์…˜

1. ํŒŒ์ผ๋ช… ๊ทœ์น™

ํƒ€์ž… ๊ทœ์น™ ์˜ˆ์‹œ
์ปดํฌ๋„ŒํŠธ PascalCase Button.tsx, ScrollAnimation.tsx
ํŽ˜์ด์ง€ page.tsx app/login/page.tsx
๋ ˆ์ด์•„์›ƒ layout.tsx app/(authorized)/layout.tsx
์„œ๋น„์Šค domain.service.ts auth.service.ts
ํ›… domain.hook.ts auth.hook.ts
ํƒ€์ž… domain.type.ts auth.type.ts
์œ ํ‹ธ kebab-case.ts cookie-storage.ts

2. ๋ช…๋ช… ๊ทœ์น™

API ํ•จ์ˆ˜ ๋ช…๋ช… (HTTP ๋ฉ”์„œ๋“œ ์ ‘๋‘์‚ฌ)

// โœ… ์˜ฌ๋ฐ”๋ฅธ ์˜ˆ์‹œ
export const POST_login = async (data: LoginRequest) => {};
export const GET_profile = async () => {};
export const PUT_profile = async (data: UserRequest) => {};
export const DELETE_withdraw = async () => {};

Hook ๋ช…๋ช…

// โœ… ์˜ฌ๋ฐ”๋ฅธ ์˜ˆ์‹œ
export const usePostLogin = () => {};
export const useGetProfile = () => {};

ํƒ€์ž… ๋ช…๋ช…

// Request/Response ์ ‘๋ฏธ์‚ฌ ์‚ฌ์šฉ
export type LoginRequest = { ... }
export type LoginResponse = { ... }
export type UserResponse = { ... }

Enum ๋ช…๋ช…

// ํƒ€์ž…๊ณผ Enum์„ ํ•จ๊ป˜ export
export type UserType = 'LEARNER' | 'CREATOR';
export const enum UserTypeEnum {
  LEARNER = 'LEARNER',
  CREATOR = 'CREATOR',
}

3. Import ์ˆœ์„œ (Prettier ์ž๋™ ์ •๋ ฌ)

// 1. ์ƒ๋Œ€ ๊ฒฝ๋กœ import (CSS ์ œ์™ธ)
import { LoginRequest } from './auth.type';

// 2. CSS/SCSS
import './globals.css';

// 3. React
import { useState } from 'react';

// 4. Next.js
import Link from 'next/link';

// 6. @/ ๊ฒฝ๋กœ (์•ŒํŒŒ๋ฒณ ์ˆœ)
import { Button } from '@/shared/components/ui/button';
import { useAuthStore } from '@/shared/stores/auth';
// 5. Third-party ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
import { useMutation } from '@tanstack/react-query';
import axios from 'axios';

4. TypeScript ์„ค์ •

{
  "strict": true, // ์—„๊ฒฉ ๋ชจ๋“œ
  "target": "ES2017",
  "module": "esnext",
  "moduleResolution": "bundler",
  "paths": {
    "@/*": ["./src/*"] // ์ ˆ๋Œ€ ๊ฒฝ๋กœ alias
  }
}

๐Ÿ—๏ธ ์•„ํ‚คํ…์ฒ˜ ํŒจํ„ด

1. ์„œ๋น„์Šค ๋ ˆ์ด์–ด ํŒจํ„ด (3๋‹จ ๊ตฌ์กฐ)

โ‘  Service Layer (*.service.ts)

// API ํ˜ธ์ถœ ๋กœ์ง๋งŒ ๋‹ด๋‹น
export const POST_login = async (data: LoginRequest): Promise<ApiResponse<LoginResponse>> => {
  const response = await api.post('/api/v1/auth/login', data);
  return response.data;
};

โ‘ก Hook Layer (*.hook.ts)

// React Query๋ฅผ ์‚ฌ์šฉํ•œ ์ƒํƒœ ๊ด€๋ฆฌ
export const usePostLogin = () => {
  return useMutation({
    mutationKey: [POST_login.name],
    mutationFn: (data: LoginRequest) => POST_login(data),
  });
};

โ‘ข Component Layer

// ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์‹คํ–‰
const { mutateAsync: postLogin } = usePostLogin();

const onSubmit = async (data: LoginRequest) => {
  await postLogin(data).then((response) => {
    // ์„ฑ๊ณต ์ฒ˜๋ฆฌ
  });
};

2. ๊ณตํ†ต API Response ํƒ€์ž…

export interface ApiResponse<T> {
  success: boolean;
  code: string;
  message: string;
  data: T;
}

3. Axios ์ธํ„ฐ์…‰ํ„ฐ ํŒจํ„ด

Request Interceptor

  • Zustand ์Šคํ† ์–ด์—์„œ accessToken ๊ฐ€์ ธ์˜ค๊ธฐ
  • Authorization ํ—ค๋” ์ž๋™ ์ถ”๊ฐ€

Response Interceptor

  • 401 ์—๋Ÿฌ ์‹œ ํ† ํฐ ์ž๋™ ๊ฐฑ์‹ 
  • Refresh Token ๊ธฐ๋ฐ˜ ์žฌ์ธ์ฆ
  • Queue ํŒจํ„ด์œผ๋กœ ๋™์‹œ ์š”์ฒญ ์ฒ˜๋ฆฌ

4. ์ƒํƒœ ๊ด€๋ฆฌ ์ „๋žต

Zustand (ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ)

// ํ† ํฐ์€ ์ฟ ํ‚ค์— persist
export const useAuthStore = create(
  persist<AuthState & AuthActions>(
    (set) => ({ ... }),
    {
      name: '@insty-app.token',
      storage: createJSONStorage(() => cookieStorage),
    }
  )
);

// ์‚ฌ์šฉ์ž ์ •๋ณด๋Š” ๋ฉ”๋ชจ๋ฆฌ์—๋งŒ ์ €์žฅ
export const useUserStore = create<UserState & UserActions>((set) => ({ ... }));

React Query (์„œ๋ฒ„ ์ƒํƒœ)

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 60 * 1000, // 1๋ถ„
    },
  },
});

โœจ Prettier ์„ค์ •

{
  "singleQuote": true, // ์ž‘์€๋”ฐ์˜ดํ‘œ ์‚ฌ์šฉ
  "semi": true, // ์„ธ๋ฏธ์ฝœ๋ก  ํ•„์ˆ˜
  "tabWidth": 2, // ๋“ค์—ฌ์“ฐ๊ธฐ 2์นธ
  "trailingComma": "all", // ํ›„ํ–‰ ์‰ผํ‘œ
  "printWidth": 120, // ์ค„ ๊ธธ์ด 120์ž
  "arrowParens": "always", // ํ™”์‚ดํ‘œ ํ•จ์ˆ˜ ๊ด„ํ˜ธ ํ•ญ์ƒ
  "plugins": ["@trivago/prettier-plugin-sort-imports", "prettier-plugin-tailwindcss"]
}

๐ŸŽฏ UI ์ปดํฌ๋„ŒํŠธ ํŒจํ„ด

1. shadcn/ui ๊ธฐ๋ฐ˜ ์ปดํฌ๋„ŒํŠธ

Button ์ปดํฌ๋„ŒํŠธ ๋ณ€ํ˜•

const buttonVariants = cva('base-styles', {
  variants: {
    variant: {
      default: 'bg-primary-green-600 text-primary-foreground hover:bg-primary-green-700',
      destructive: 'bg-destructive text-white hover:bg-destructive/90',
      outline: 'border bg-background shadow-xs hover:bg-accent',
      secondary: 'bg-secondary text-secondary-foreground',
      ghost: 'hover:bg-accent hover:text-accent-foreground',
      link: 'text-primary underline-offset-4 hover:underline',
    },
    size: {
      default: 'h-9 px-4 py-2',
      sm: 'h-8 rounded-md gap-1.5 px-3',
      lg: 'h-10 rounded-md px-6',
      icon: 'size-9',
    },
  },
});

2. ํผ ํŒจํ„ด (React Hook Form)

const form = useForm({
  defaultValues: { email: '', password: '' },
});

<Form {...form}>
  <form onSubmit={form.handleSubmit(onSubmit)}>
    <FormField
      control={form.control}
      name="email"
      rules={{
        required: { value: true, message: '์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.' },
        pattern: { value: emailReg, message: '์ด๋ฉ”์ผ ํ˜•์‹์ด ์ž˜๋ชป๋˜์—ˆ์Šต๋‹ˆ๋‹ค.' },
      }}
      render={({ field }) => (
        <FormItem>
          <FormLabel>์ด๋ฉ”์ผ</FormLabel>
          <FormControl>
            <Input {...field} placeholder="์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”." />
          </FormControl>
          <FormMessage />
        </FormItem>
      )}
    />
  </form>
</Form>

๐Ÿ”’ ๋ณด์•ˆ ํŒจํ„ด

1. ์ฟ ํ‚ค ๊ธฐ๋ฐ˜ ํ† ํฐ ์ €์žฅ

storage: createJSONStorage(() => cookieStorage);

2. ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋ถ„๋ฆฌ

  • NEXT_PUBLIC_BACK_BASE_URL - API ๊ธฐ๋ณธ URL

3. API Proxy ํŒจํ„ด

// next.config.ts
rewrites: async () => {
  return [
    {
      source: '/api/:path*',
      destination: process.env.NEXT_PUBLIC_BACK_BASE_URL + '/api/:path*',
    },
  ];
};

๐Ÿ“ฑ ๋ผ์šฐํŒ… ํŒจํ„ด

์‚ฌ์šฉ์ž ํƒ€์ž…๋ณ„ ๋ถ„๋ฆฌ

  • /creator - ํฌ๋ฆฌ์—์ดํ„ฐ ์ „์šฉ
  • /learner - ๋Ÿฌ๋„ˆ ์ „์šฉ
  • /login/[userType] - ๋™์  ๋ผ์šฐํŠธ

๋ ˆ์ด์•„์›ƒ ์ค‘์ฒฉ

RootLayout (์ „์—ญ)
  โ””โ”€ AuthorizedLayout (์ธ์ฆ ํ•„์š”)
       โ”œโ”€ Header
       โ”œโ”€ Main Content
       โ””โ”€ Footer

๐Ÿ› ๏ธ ๊ฐœ๋ฐœ ๋„๊ตฌ ์„ค์ •

ESLint

  • next/core-web-vitals ํ™•์žฅ
  • next/typescript ํ™•์žฅ

PostCSS

  • @tailwindcss/postcss - Tailwind v4 ํ”Œ๋Ÿฌ๊ทธ์ธ

์• ๋‹ˆ๋ฉ”์ด์…˜

  • tw-animate-css - Tailwind ์• ๋‹ˆ๋ฉ”์ด์…˜ ํ™•์žฅ

๐Ÿ“ ์ฃผ์š” ํŠน์ง• ์š”์•ฝ

  1. ๋ชจ๋“ˆํ™”๋œ ์„œ๋น„์Šค ๋ ˆ์ด์–ด: ๋„๋ฉ”์ธ๋ณ„๋กœ service/hook/type 3๋‹จ ๊ตฌ์กฐ
  2. ํƒ€์ž… ์•ˆ์ •์„ฑ: ๋ชจ๋“  API Response์— ์ œ๋„ค๋ฆญ ํƒ€์ž… ์ ์šฉ
  3. ์ž๋™ ํ† ํฐ ๊ฐฑ์‹ : Interceptor ํŒจํ„ด์œผ๋กœ ๋ฌด์ค‘๋‹จ ์ธ์ฆ
  4. ์ปดํฌ๋„ŒํŠธ ์žฌ์‚ฌ์šฉ์„ฑ: shadcn/ui + cva๋กœ ๋ณ€ํ˜• ๊ด€๋ฆฌ
  5. ์ผ๊ด€๋œ ์ฝ”๋”ฉ ์Šคํƒ€์ผ: Prettier + ESLint ์ž๋™ ํฌ๋งทํŒ…
  6. ์‚ฌ์šฉ์ž ํƒ€์ž… ๋ถ„๋ฆฌ: Creator/Learner ๋ณ„๋„ ๋ผ์šฐํŒ…
  7. ๋‹คํฌ๋ชจ๋“œ ์ง€์›: CSS ๋ณ€์ˆ˜ ๊ธฐ๋ฐ˜ ํ…Œ๋งˆ ์‹œ์Šคํ…œ
  8. ์ตœ์‹  Next.js: App Router + Turbopack