로딩 중...
애매한 요청을 정확한 엔지니어드 지시로 변환
❌ 안티패턴
버튼을 예쁘게 만들어줘
✅ 권장패턴
버튼에 Tailwind CSS로 bg-blue-500, hover:bg-blue-600, text-white, px-4 py-2, rounded-lg 클래스를 적용해줘. 포커스 시 ring-2 ring-blue-300도 추가해줘.
📝 예시
<button type="button" className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg focus:ring-2 focus:ring-blue-300">클릭</button>❌ 안티패턴
데이터 가져와줘
✅ 권장패턴
/api/posts에서 GET 요청으로 게시글 목록을 가져와줘. 로딩 상태는 <Skeleton />로 표시하고, 에러 시 토스트로 "데이터 로딩 실패" 메시지를 보여줘. TanStack Query의 useQuery를 사용해줘.
📝 예시
const { data, isLoading, error } = useQuery({ queryKey: ["posts"], queryFn: () => fetch("/api/posts").then(r => r.json()) })❌ 안티패턴
폼 만들어줘
✅ 권장패턴
React Hook Form과 Zod를 사용해서 이메일(필수, 유효한 형식), 비밀번호(필수, 최소 8자) 필드가 있는 로그인 폼을 만들어줘. 에러는 필드 아래에 빨간색 텍스트로 표시하고, submit 버튼은 유효성 검사 통과 시에만 활성화해줘.
📝 예시
const schema = z.object({ email: z.string().email(), password: z.string().min(8) })
const { register, handleSubmit, formState: { errors } } = useForm({ resolver: zodResolver(schema) })❌ 안티패턴
모바일에서도 보이게 해줘
✅ 권장패턴
Tailwind의 반응형 유틸리티를 사용해서 모바일(기본)에서는 1열, 태블릿(md:)에서는 2열, 데스크탑(lg:)에서는 3열 그리드 레이아웃을 만들어줘. gap-4를 적용하고 px-4로 양쪽 여백도 추가해줘.
📝 예시
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-3 gap-4 px-4">...</div>❌ 안티패턴
API 만들어줘
✅ 권장패턴
Next.js App Router의 /app/api/users/route.ts에 POST 엔드포인트를 만들어줘. Zod로 name(string, 필수), email(email, 필수) 검증하고, 실패 시 400 에러와 에러 메시지를 JSON으로 반환해줘. 성공 시 201과 생성된 유저 데이터를 반환해줘.
📝 예시
export async function POST(req: Request) {
const body = await req.json()
const result = schema.safeParse(body)
if (!result.success) return NextResponse.json({ error: result.error }, { status: 400 })
// DB 저장 로직
return NextResponse.json(user, { status: 201 })
}❌ 안티패턴
타입 좀 고쳐줘
✅ 권장패턴
User 타입을 정의할 때 id는 string, name은 string, email은 string, createdAt은 Date 타입으로 명시해줘. optional 필드는 ?를 사용하고, Partial<User>나 Pick<User, "id" | "name"> 같은 유틸리티 타입도 활용해줘. any는 절대 사용하지 말아줘.
📝 예시
type User = { id: string; name: string; email: string; createdAt: Date; avatar?: string }
type UserUpdate = Partial<Pick<User, "name" | "email">>❌ 안티패턴
로딩 중일 때 뭔가 보여줘
✅ 권장패턴
isLoading 상태가 true일 때 Skeleton 컴포넌트를 표시하고, 데이터가 로드되면 실제 컨텐츠를 렌더링해줘. Skeleton은 animate-pulse 클래스로 애니메이션을 추가하고, 실제 컨텐츠와 동일한 레이아웃을 유지해줘.
📝 예시
{isLoading ? (
<div className="animate-pulse space-y-4">
<div className="h-4 bg-gray-200 rounded w-3/4"></div>
<div className="h-4 bg-gray-200 rounded w-1/2"></div>
</div>
) : <ActualContent data={data} />}❌ 안티패턴
에러 처리 좀
✅ 권장패턴
try-catch로 에러를 잡고, 에러 타입별로 다른 메시지를 표시해줘. 네트워크 에러는 "인터넷 연결을 확인해주세요", 401은 "로그인이 필요합니다", 403은 "권한이 없습니다", 500은 "서버 오류가 발생했습니다"로 처리해줘. toast 라이브러리로 사용자에게 알려줘.
📝 예시
try {
const res = await fetch("/api/data")
if (!res.ok) {
if (res.status === 401) throw new Error("로그인이 필요합니다")
if (res.status === 403) throw new Error("권한이 없습니다")
throw new Error("서버 오류")
}
} catch (err) {
toast.error(err.message)
}❌ 안티패턴
SEO 좀 해줘
✅ 권장패턴
Next.js의 Metadata API를 사용해서 페이지 제목, 설명, OG 이미지, 트위터 카드를 설정해줘. title은 "페이지명 | 사이트명" 형식으로, description은 150자 이내로, openGraph에 이미지 URL과 사이트 정보를 포함해줘.
📝 예시
export const metadata: Metadata = {
title: "프롬프트 단어장 | BION",
description: "커서 AI 프롬프트 작성 가이드",
openGraph: { title: "...", description: "...", images: ["/og.png"] }
}❌ 안티패턴
성능 최적화해줘
✅ 권장패턴
불필요한 리렌더링을 방지하기 위해 React.memo로 컴포넌트를 감싸고, useMemo로 계산 비용이 큰 값을 메모이제이션하고, useCallback으로 함수를 메모이제이션해줘. 의존성 배열을 정확하게 명시하고, React DevTools Profiler로 성능을 측정해줘.
📝 예시
const MemoizedComponent = React.memo(({ data }) => { ... })
const expensiveValue = useMemo(() => heavyCalculation(data), [data])
const handleClick = useCallback(() => { ... }, [dependency])❌ 안티패턴
접근성 추가해줘
✅ 권장패턴
버튼에 aria-label로 의미있는 레이블을 추가하고, 키보드 네비게이션을 위해 tabIndex를 설정하고, 모달에는 role="dialog"와 aria-modal="true"를 추가해줘. 포커스 트랩을 구현하고, ESC 키로 닫을 수 있게 해줘.
📝 예시
<button
type="button" aria-label="메뉴 닫기" onClick={close}>
<X />
</button>
<div role="dialog" aria-modal="true" aria-labelledby="dialog-title">...</div>❌ 안티패턴
API 키 어떻게 써?
✅ 권장패턴
클라이언트에서 접근해야 하는 환경변수는 NEXT_PUBLIC_ 접두사를 붙여서 .env.local에 저장해줘. 서버 전용 변수는 접두사 없이 저장하고, process.env.VARIABLE_NAME으로 접근해줘. API 키나 비밀키는 절대 클라이언트에 노출하지 말아줘.
📝 예시
// .env.local
NEXT_PUBLIC_API_URL=https://api.example.com
DATABASE_URL=postgresql://...
// 사용
const apiUrl = process.env.NEXT_PUBLIC_API_URL
const dbUrl = process.env.DATABASE_URL // 서버에서만❌ 안티패턴
이미지 최적화해줘
✅ 권장패턴
Next.js의 Image 컴포넌트를 사용해서 이미지를 자동 최적화해줘. width, height를 명시하고, priority 속성으로 LCP 이미지를 우선 로드하고, loading="lazy"로 나머지는 지연 로드해줘. alt 텍스트는 필수로 추가해줘.
📝 예시
import Image from "next/image"
<Image
src="/hero.jpg"
alt="메인 배너"
width={1200}
height={600}
priority
/>
<Image src="/thumbnail.jpg" alt="썸네일" width={300} height={200} loading="lazy" />❌ 안티패턴
전역 상태 관리해줘
✅ 권장패턴
Zustand를 사용해서 전역 스토어를 만들어줘. create 함수로 스토어를 정의하고, set으로 상태를 업데이트하고, persist 미들웨어로 localStorage에 자동 저장해줘. TypeScript 타입도 정확하게 지정해줘.
📝 예시
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
type Store = { count: number; increase: () => void }
const useStore = create<Store>()(persist((set) => ({
count: 0,
increase: () => set((state) => ({ count: state.count + 1 }))
}), { name: 'my-store' }))❌ 안티패턴
애니메이션 추가해줘
✅ 권장패턴
Framer Motion을 사용해서 페이드인 + 슬라이드 애니메이션을 추가해줘. initial, animate, exit을 정의하고, transition으로 duration과 easing을 설정해줘. AnimatePresence로 마운트/언마운트 애니메이션도 처리해줘.
📝 예시
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3, ease: "easeOut" }}
>
컨텐츠
</motion.div>❌ 안티패턴
무한 스크롤 만들어줘
✅ 권장패턴
TanStack Query의 useInfiniteQuery와 Intersection Observer를 사용해서 무한 스크롤을 구현해줘. getNextPageParam으로 다음 페이지 번호를 계산하고, fetchNextPage로 데이터를 로드하고, isFetchingNextPage로 로딩 상태를 표시해줘.
📝 예시
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery({
queryKey: ['items'],
queryFn: ({ pageParam = 1 }) => fetchItems(pageParam),
getNextPageParam: (lastPage) => lastPage.nextPage
})
// Intersection Observer로 스크롤 감지
useEffect(() => {
if (inView && hasNextPage) fetchNextPage()
}, [inView])❌ 안티패턴
서버 액션 만들어줘
✅ 권장패턴
Next.js 14의 Server Actions를 사용해서 "use server" 지시어를 추가하고, 폼 데이터를 처리해줘. revalidatePath로 캐시를 무효화하고, redirect로 페이지 이동하고, Zod로 입력값을 검증해줘. 에러는 try-catch로 처리해줘.
📝 예시
'use server'
import { revalidatePath } from 'next/cache'
export async function createPost(formData: FormData) {
const data = { title: formData.get('title'), content: formData.get('content') }
const validated = schema.parse(data)
await db.post.create({ data: validated })
revalidatePath('/posts')
}❌ 안티패턴
데이터베이스 연결해줘
✅ 권장패턴
Prisma Client를 싱글톤 패턴으로 초기화하고, schema.prisma에 모델을 정의하고, npx prisma generate로 타입을 생성해줘. 트랜잭션이 필요하면 prisma.$transaction을 사용하고, 에러는 PrismaClientKnownRequestError로 처리해줘.
📝 예시
// lib/prisma.ts
const prisma = globalThis.prismaGlobal ?? new PrismaClient()
if (process.env.NODE_ENV !== 'production') globalThis.prismaGlobal = prisma
// 사용
const user = await prisma.user.create({ data: { name, email } })
const users = await prisma.user.findMany({ where: { active: true } })❌ 안티패턴
테스트 작성해줘
✅ 권장패턴
Vitest와 Testing Library를 사용해서 유닛 테스트를 작성해줘. describe로 테스트 그룹을 만들고, it 또는 test로 개별 케이스를 작성하고, expect로 assertion을 추가해줘. render, screen, fireEvent를 사용해서 컴포넌트를 테스트해줘.
📝 예시
import { render, screen, fireEvent } from '@testing-library/react'
import { describe, it, expect } from 'vitest'
describe('Button', () => {
it('calls onClick when clicked', () => {
const onClick = vi.fn()
render(<Button onClick={onClick}>Click</Button>)
fireEvent.click(screen.getByText('Click'))
expect(onClick).toHaveBeenCalledTimes(1)
})
})❌ 안티패턴
로그인 처리해줘
✅ 권장패턴
NextAuth.js를 사용해서 인증을 구현해줘. [...nextauth]/route.ts에서 providers를 설정하고, session callback으로 JWT에 커스텀 데이터를 추가하고, middleware.ts로 보호된 라우트를 설정해줘. getServerSession으로 서버에서 세션을 확인해줘.
📝 예시
import NextAuth from 'next-auth'
export const { handlers, auth } = NextAuth({
providers: [Google, Credentials],
callbacks: {
session: ({ session, token }) => ({ ...session, user: { ...session.user, id: token.sub } })
}
})