๐ ์๋น์ค ๋งํฌ: https://ku-check.vercel.app
แแ ฒแแ ฆแจ แแ ฉแแ ข ๐จ ์๋น์ค ์ค๋ช ํ์ฒต์ ํ์์ฆ ์ด์์ ํฉ์ด์ ธ ์๋ ์ถ๊ฒฐ, ์๋ฒ์ , ๊ณต์ง, ๋ถ์ฐธ์ฌ์ ์ ์ ์ถ์ ํ๋๋ก ํตํฉํด ํํ ์ด์์ ๋ ์ ํํ๊ณ ๊ฐํธํ๊ฒ ๋ง๋๋ ์ ์ฉ ๊ด๋ฆฌ ์๋น์ค์ ๋๋ค. ์ด์์ง์ ๋ฐ๋ณต์ ์ธ ์๊ธฐ ํ์ ์ ์ค์ผ ์ ์๊ณ , ํํ์์ ๋ด ํ๋ ํํฉ๊ณผ ์ด๋ฒ ์ฃผ ํต์ฌ ์ ๋ณด๋ฅผ ํ ๊ณณ์์ ํ์ธํ๋ฉฐ ๋ถ์ฐธ์ฌ์ ์๊น์ง ์ฑ์์ ๋ฐ๋ก ์ ์ถํ ์ ์์ต๋๋ค.
| Frontend (FE) | |
|---|---|
![]() ํฉ์ ๋ฆผ Frontend Lead |
![]() ์ง์ฑ์ Frontend |
| ๊ธฐ์ / ๋๊ตฌ | ์ ํ์ด์ |
|---|---|
| Next.js | Next.js๋ฅผ ๋์ ํ ํต์ฌ ๋ชฉํ๋ ์ฌ์ฉ์ ์ฑ๋ฅ ํฅ์์ ๋๋ค. SSR/SSG๋ฅผ ํตํ ๋น ๋ฅธ ์ด๊ธฐ ๋ก๋ฉ ์๋ ํ๋ณด, ์๋ ์ด๋ฏธ์ง ์ต์ ํ, ์ ์ฐํ ๋ ์ด์์ ์์คํ ๋ฑ Next.js๊ฐ ์ ๊ณตํ๋ ๊ฐ๋ ฅํ ์ต์ ํ ์ด์ ๋ค์ ์ ๊ทน ํ์ฉํ์ฌ ์ฌ์ฉ์ ๊ฒฝํ(UX)์ ๊ทน๋ํํ๊ณ ์ ํ์ต๋๋ค. |
| TypeScript | TypeScript๋ฅผ ๋์ ํ ๊ฐ์ฅ ํฐ ์ด์ ๋ ์ ์ ํ์ ๊ฒ์ฌ๋ฅผ ํตํด ๋ฐํ์ ์๋ฌ๋ฅผ ๊ฐ๋ฐ ๋จ๊ณ์์ ๋ฏธ๋ฆฌ ์ฐจ๋จํ์ฌ ์๋น์ค์ ์์ ์ฑ์ ๊ทน๋ํํ๊ธฐ ์ํด์์ ๋๋ค. ๋ํ, ๋ช ํํ ํ์ ์ถ๋ก ๊ณผ ๊ฐ๋ ฅํ ์๋ ์์ฑ ๊ธฐ๋ฅ์ ์ฝ๋์ ๊ฐ๋ ์ฑ๊ณผ ๊ฐ๋ฐ ์์ฐ์ฑ์ ๋์ฌ์ฃผ์์ผ๋ฉฐ, ์ ์ง๋ณด์์ฑ์ ํ๋ณดํ๋ ๋ฐ ๊ฒฐ์ ์ ์ธ ์ญํ ์ ํ์ต๋๋ค. |
| pnpm | pnpm์ ๋์ ํ ๊ฐ์ฅ ํฐ ์ด์ ๋ ์๋์ ์ธ ๋์คํฌ ๊ณต๊ฐ ํจ์จ์ฑ๊ณผ ๋น ๋ฅธ ์ค์น ์๋ ๋๋ฌธ์ ๋๋ค. npm/yarn๊ณผ ๋ฌ๋ฆฌ node_modules์ ํจํค์ง๋ฅผ ๋ณต์ ํ์ง ์๊ณ ์ ์ญ ์คํ ์ด์์ ํ๋ ๋งํฌ(Hard Link) ๋ฐฉ์์ผ๋ก ํ์ผ์ ๊ฐ์ ธ์, ๋์คํฌ ์ฌ์ฉ๋์ ํ๊ธฐ์ ์ผ๋ก ์ค์ด๊ณ ์ค๋ณต ์ค์น๋ฅผ ๋ฐฉ์งํฉ๋๋ค. ๋ํ, package.json์ ๋ช ์๋ ์์กด์ฑ์๋ง ์ ๊ทผํ ์ ์๋ ์๊ฒฉํ node_modules ๊ตฌ์กฐ๋ฅผ ๊ตฌํํ์ฌ, '์ ๋ น ์์กด์ฑ(Phantom Dependencies)' ๋ฌธ์ ๋ฅผ ์์ฒ์ ์ผ๋ก ์ฐจ๋จํ๊ณ ํ๋ก์ ํธ์ ์์ ์ฑ์ ๋์ฌ์ค๋๋ค. |
| TailwindCSS | TailwindCSS๋ '์ ํธ๋ฆฌํฐ ํผ์คํธ(Utility-First)' ์ ๊ทผ ๋ฐฉ์์ ๋ฏธ๋ฆฌ ์ ์๋ ํด๋์ค๋ฅผ JSX/HTML ๋ด์์ ์ง์ ์กฐํฉํ๊ฒ ํ์ฌ, CSS ํ์ผ๊ณผ ์ฝ๋๋ฅผ ์ค๊ฐ๋ ์ปจํ ์คํธ ์ค์์นญ ๋น์ฉ์ ์์ ํ ์ ๊ฑฐํ์ต๋๋ค. ์ง๊ด์ ์ธ ๋ฐ์ํ ๋์์ธ๊ณผ ์ํ ๊ด๋ฆฌ๋ฅผ ํตํด ๊ฐ๋ฐ ์์ฐ์ฑ์ ๋์ผ ์ ์์์ต๋๋ค. |
| Zustand | Zustand๋ ๋งค์ฐ ๊ฐ๊ฒฐํ ๋ฌธ๋ฒ๊ณผ ์ต์ํ์ ์ค์ ์ผ๋ก ์ํ ๊ด๋ฆฌ๋ฅผ ๊ฐ๋ฅํ๊ฒ ํด ๊ฐ๋ฐ ์์ฐ์ฑ์ ๊ทน๋ํํฉ๋๋ค. ๋ํ, Provider๋ก ๊ฐ์ ํ์๊ฐ ์๊ณ ์ํ์ ํ์ํ ๋ถ๋ถ๋ง ๊ตฌ๋ ํ์ฌ ๋ถํ์ํ ๋ฆฌ๋ ๋๋ง์ ์๋์ผ๋ก ๋ฐฉ์งํ๊ธฐ ๋๋ฌธ์ ์ฑ๋ฅ ์ต์ ํ์ ์ ๋ฆฌํ์ฌ ์ ํํ๊ฒ ๋์์ต๋๋ค. |
| E2E | E2E ํ ์คํธ๋ฅผ ๋์ ํ ์ด์ ๋ '์ค์ ์ฌ์ฉ์' ๊ด์ ์์ ํต์ฌ ๊ธฐ๋ฅ(ํ์๊ฐ์ , ํํฝ, ๋ถ์ฐธ์ฌ์ ์ ์์ฑ)์ ์ ์ฒด ํ๋ฆ์ ์๋ฎฌ๋ ์ด์ ํ์ฌ, ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฌด๊ฒฐ์ฑ์ ๋ณด์ฅํ๊ธฐ ์ํด์์ ๋๋ค. ์ด๋ ์ ๋/ํตํฉ ํ ์คํธ๊ฐ ๋ฐ๊ฒฌํ๊ธฐ ์ด๋ ค์ด ํ๋ก ํธ์๋, ๋ฐฑ์๋ API, DB๋ฅผ ์์ฐ๋ฅด๋ ๋ณตํฉ์ ์ธ ์ํธ์์ฉ์์ ๋ฐ์ํ๋ ๋ฒ๊ทธ๋ฅผ ์ก์๋ด๋ฉฐ, ๋ฐฐํฌ ์ ๊ฐ์ฅ ๋์ ์์ค์ ์๋น์ค ์์ ์ฑ์ ํ๋ณดํ๊ฒ ํด์ค๋๋ค. |
KuCheck-Front/
โโโ src/
โ โโโ app/ # Next.js App Router - ํ์ด์ง ๋ฐ ๋ ์ด์์
โ โ โโโ layout.tsx # ๋ฃจํธ ๋ ์ด์์
โ โ โโโ page.tsx # ํํ์ด์ง
โ โ โโโ (auth)/ # ์ธ์ฆ ๊ด๋ จ ๋ผ์ฐํธ ๊ทธ๋ฃน
โ โ โโโ (manager)/ # ๋งค๋์ ๊ธฐ๋ฅ ๋ผ์ฐํธ ๊ทธ๋ฃน
โ โ โโโ (member)/ # ๋ฉค๋ฒ ๊ธฐ๋ฅ ๋ผ์ฐํธ ๊ทธ๋ฃน
โ โ
โ โโโ components/ # ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ React ์ปดํฌ๋ํธ
โ โ โโโ common/ # ๊ณตํต ์ปดํฌ๋ํธ (Header, Footer, Sidebar ๋ฑ)
โ โ โโโ ui/ # UI ์์ ์ปดํฌ๋ํธ (Button, Input, Modal ๋ฑ)
โ โ โโโ manager/ # ๋งค๋์ ์ ์ฉ ์ปดํฌ๋ํธ
โ โ โ โโโ QRCodeScanner/
โ โ โ โโโ AttendanceList/
โ โ โ โโโ ...
โ โ โโโ member/ # ๋ฉค๋ฒ ์ ์ฉ ์ปดํฌ๋ํธ
โ โ โโโ CheckIn/
โ โ โโโ AbsenceForm/
โ โ โโโ ...
โ โ
โ โโโ hooks/ # ์ปค์คํ
React Hooks
โ โ โโโ useAuth.ts # ์ธ์ฆ ๊ด๋ จ ํ
โ โ โโโ useAttendance.ts # ์ถ์ ๊ด๋ จ ํ
โ โ โโโ useFetch.ts # API ์์ฒญ ๊ด๋ฆฌ ํ
โ โ โโโ ...
โ โ
โ โโโ lib/ # ์ ํธ๋ฆฌํฐ ๋ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์
โ โ โโโ api/ # API ํด๋ผ์ด์ธํธ
โ โ โ โโโ axiosInstance.ts # Axios ์ค์
โ โ โ โโโ endpoints.ts # API ์๋ํฌ์ธํธ ์ ์
โ โ โโโ utils/ # ๊ณตํต ์ ํธ๋ฆฌํฐ ํจ์๋ค
โ โ โ โโโ imageOptimizer.ts # ์ด๋ฏธ์ง ์ต์ ํ
โ โ โ โโโ dateFormatter.ts # ๋ ์ง ํฌ๋งทํ
โ โ โ โโโ ...
โ โ โโโ ...
โ โ
โ โโโ store/ # Zustand ์ํ ๊ด๋ฆฌ
โ โ โโโ authStore.ts # ์ธ์ฆ ์ํ
โ โ โโโ attendanceStore.ts # ์ถ์ ์ํ
โ โ โโโ ...
โ โ
โ โโโ types/ # TypeScript ํ์
์ ์
โ โ โโโ attendance.ts # ์ถ์ ๊ด๋ จ ํ์
โ โ โโโ user.ts # ์ฌ์ฉ์ ๊ด๋ จ ํ์
โ โ โโโ api.ts # API ์๋ต ํ์
โ โ โโโ ...
โ โ
โ โโโ constants/ # ์์ ์ ์
โ โ โโโ routes.ts # ๋ผ์ฐํธ ๊ฒฝ๋ก
โ โ โโโ messages.ts # ์๋ฌ/์ฑ๊ณต ๋ฉ์์ง
โ โ โโโ ...
โ โ
โ โโโ assets/ # icons
โ โ โโโ images/
โ โ โโโ icons/
โ โ โโโ fonts/
โ โ
โ โโโ utils/ # ์ ์ญ ์ ํธ๋ฆฌํฐ ํจ์
โ โ โโโ validators.ts # ์
๋ ฅ ๊ฒ์ฆ ํจ์
โ โ โโโ errorHandler.ts # ์๋ฌ ์ฒ๋ฆฌ
โ โ โโโ ...
โ โ
โ โโโ middleware.ts # Next.js ๋ฏธ๋ค์จ์ด (์ธ์ฆ ์ฒดํฌ ๋ฑ)
โ
โโโ tests/ # E2E ํ
์คํธ (Playwright)
โ โโโ signup.spec.ts # ํ์๊ฐ์
ํ๋ก์ฐ ํ
์คํธ
โ โโโ attendance.spec.ts # ์ถ์ ๊ธฐ๋ฅ ํ
์คํธ
โ โโโ ...
โ
โโโ public/ # ์ ์ ํ์ผ (์ด๋ฏธ์ง, ํฐํธ ๋ฑ)
โ
โโโ package.json # ํ๋ก์ ํธ ๋ฉํ๋ฐ์ดํฐ ๋ฐ ์์กด์ฑ
โโโ tsconfig.json # TypeScript ์ค์
โโโ next.config.ts # Next.js ์ค์
โโโ tailwind.config.ts # Tailwind CSS ์ค์
โโโ eslint.config.mjs # ESLint ์ค์
โโโ .prettierrc # Prettier ์ค์
Next.js์ App Router ๊ธฐ๋ฐ ๋ผ์ฐํ
์์คํ
์
๋๋ค. ํด๋ ๊ตฌ์กฐ๊ฐ ๊ณง URL ๊ฒฝ๋ก๊ฐ ๋์ด ์ง๊ด์ ์ธ ํ์ด์ง ๊ด๋ฆฌ๊ฐ ๊ฐ๋ฅํฉ๋๋ค. ๋ผ์ฐํธ ๊ทธ๋ฃน((auth), (manager), (member))์ ์ฌ์ฉํ์ฌ ๊ณตํต ๋ ์ด์์์ ๊ณต์ ํ๋ฉด์๋ URL ๊ฒฝ๋ก์ ํฌํจ๋์ง ์๊ฒ ๊ด๋ฆฌํฉ๋๋ค.
์ฌ์ฌ์ฉ ๊ฐ๋ฅํ React ์ปดํฌ๋ํธ๋ฅผ ๊ธฐ๋ฅ๊ณผ ์ญํ ์ ๋ฐ๋ผ ๋ถ๋ฅํ์ต๋๋ค.
- common: ๋ชจ๋ ํ์ด์ง์์ ๊ณตํต์ผ๋ก ์ฌ์ฉ๋๋ Header, Footer, Navigation ๋ฑ
- ui: shadcn/ui ๊ธฐ๋ณธ UI ์์๋ค
- manager/member: ๊ฐ ์ฌ์ฉ์ ์ญํ ๋ณ ์ ์ฉ ์ปดํฌ๋ํธ
React์ Hooks๋ฅผ ํ์ฉํ ๋ก์ง ์ฌ์ฌ์ฉ์ ๋๋ค. API ํธ์ถ, ์ํ ๊ด๋ฆฌ, ํผ ์ฒ๋ฆฌ ๋ฑ ๋ณต์กํ ๋ก์ง์ ๋ถ๋ฆฌํ์ฌ ์ปดํฌ๋ํธ์ ์์์ฑ์ ์ ์งํ๊ณ ํ ์คํธ ๊ฐ๋ฅ์ฑ์ ๋์ ๋๋ค.
๊ณตํต ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฐ ๋๊ตฌ๋ค์ ๊ด๋ฆฌํฉ๋๋ค.
- client: CSR API ์๋ํฌ์ธํธ ๊ด๋ฆฌ
- server: SSR API ์๋ํฌ์ธํธ ๊ด๋ฆฌ
Zustand๋ฅผ ์ฌ์ฉํ ์ ์ญ ์ํ ๊ด๋ฆฌ์ ๋๋ค. ๊ฐ ๋๋ฉ์ธ๋ณ๋ก ์คํ ์ด๋ฅผ ๋ถ๋ฆฌํ์ฌ ํ์ํ ์ํ๋ง ๊ตฌ๋ ํ๊ณ ๋ถํ์ํ ๋ฆฌ๋ ๋๋ง์ ๋ฐฉ์งํฉ๋๋ค.
TypeScript์ ์ธํฐํ์ด์ค์ ํ์ ์ ์ค์์ง์ค์์ผ๋ก ๊ด๋ฆฌํฉ๋๋ค. API ์๋ต, ์ปดํฌ๋ํธ Props, ๋๋ฉ์ธ ๋ชจ๋ธ ๋ฑ์ ๋ช ํํ๊ฒ ์ ์ํ์ฌ ๊ฐ๋ฐ ์์ ์ฑ์ ๋์ ๋๋ค.
๋งค์ง ์คํธ๋ง(magic string)๊ณผ ํ๋์ฝ๋ฉ๋ ๊ฐ๋ค์ ์ ๊ฑฐํฉ๋๋ค. ๋ผ์ฐํธ ๊ฒฝ๋ก, ๋ฉ์์ง, ์ค์ ๊ฐ ๋ฑ์ ํ๊ณณ์์ ๊ด๋ฆฌํ์ฌ ์ ์ง๋ณด์์ฑ์ ํฅ์์ํต๋๋ค.
์ ์ญ ์ ํธ๋ฆฌํฐ ํจ์๋ก, ์ ๋ ฅ ๊ฒ์ฆ, ์๋ฌ ์ฒ๋ฆฌ, ๋ฐ์ดํฐ ๋ณํ ๋ฑ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฒด์์ ํ์ํ ๊ธฐ๋ฅ๋ค์ ๋ด๋นํฉ๋๋ค.
Playwright๋ฅผ ์ฌ์ฉํ E2E ํ ์คํธ์ ๋๋ค. ์ค์ ์ฌ์ฉ์ ๊ด์ ์์ ํ์๊ฐ์ , ์ถ์ ์ฒดํฌ์ธ, ๋ถ์ฐธ์ฌ์ ์ ์ ์ถ ๋ฑ ํต์ฌ ๊ธฐ๋ฅ ํ๋ฆ์ ์๋ํํ์ฌ ๋ฐฐํฌ ์ ํ์ง์ ๊ฒ์ฆํฉ๋๋ค.
- ๋๋ฉ์ธ ๊ธฐ๋ฐ ๊ตฌ์กฐ: ๊ธฐ๋ฅ๋ณ๋ก ํด๋๋ฅผ ๊ตฌ๋ถํ์ฌ ํ์ฅ์ฑ๊ณผ ์ ์ง๋ณด์์ฑ์ ํ๋ณด
- ๋จ์ผ ์ฑ ์ ์์น: ๊ฐ ํ์ผ/ํด๋๋ ํ๋์ ๋ช ํํ ์ฑ ์์ ๊ฐ์ง
- ํ์ ์์ ์ฑ: TypeScript๋ฅผ ์ฒ ์ ํ ํ์ฉํ์ฌ ๋ฐํ์ ์๋ฌ ์ฌ์ ๋ฐฉ์ง
- ์ฑ๋ฅ ์ต์ ํ: Next.js์ ์ด๋ฏธ์ง ์ต์ ํ, ๋์ import, ์ฝ๋ ๋ถํ ํ์ฉ
- ํ ์คํธ ๊ฐ๋ฅ์ฑ: ๋ก์ง์ ์ปดํฌ๋ํธ์์ ๋ถ๋ฆฌํ์ฌ E2E ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง ๊ทน๋ํ
ํด๋ผ์ด์ธํธ โ Route Handler โ ๋ฐฑ์๋ (ํ ํฐ์ ์๋ฒ์ ๋ณด๊ด, XSS ๊ณต๊ฒฉ ๋ฐฉ์ง)
export async function GET() {
const { data, error } = await apiCallServer('/v1/endpoint', {
method: 'GET',
})
if (error) return Response.json({ error }, { status: 400 })
return Response.json({ success: true, data })
}export async function POST(request: Request) {
const body = await request.json()
if (!body.id) return Response.json({ error: 'id required' }, { status: 400 })
const { data, error } = await apiCallServer('/v1/endpoint', {
method: 'POST',
body: JSON.stringify(body),
})
if (error) return Response.json({ error }, { status: 400 })
return Response.json({ success: true, data })
}export async function GET(
{ params }: { params: { id: string } }
) {
if (!params.id) return Response.json({ error: 'id required' }, { status: 400 })
const { data, error } = await apiCallServer(`/v1/endpoint/${params.id}`, {
method: 'GET',
})
if (error) return Response.json({ error }, { status: 400 })
return Response.json(data)
}// โ
์ฌ๋ฐ๋ฅธ ์ฌ์ฉ
const res = await fetch('/api/absence', { method: 'POST' })
// โ ์ ๋ ๊ธ์ง
import { apiCallServer } from '@/lib/api.server' // ์๋ฌ!src/app/api/
โโโ absence/route.ts
โโโ absence/manage/route.ts
โโโ absence/manage/[sessionId]/route.ts
โโโ attendance/...
โโโ auth/cookies/route.ts
โโโ points/manage/...
src/lib/
โโโ api.server.ts # ํต์ฌ ํจ์
โโโ auth.server.ts # ํ ํฐ ๊ด๋ฆฌ








