본문으로 건너뛰기

Cursor를 활용한 풀스택 Next.js 개발

Next.js는 App Router, 서버 컴포넌트, 통합 API 라우트가 포함된 풀스택 React 프레임워크로 발전했습니다. Cursor의 AI 기능은 프로젝트 스캐폴드부터 Vercel 배포까지 Next.js 개발의 모든 측면을 가속화합니다. 이 가이드에서는 프로덕션 Next.js 애플리케이션 구축을 위한 실무 워크플로우를 다룹니다.

Next.js development in Cursor

Cursor로 프로젝트 설정​

새 프로젝트 스캐폴드​

Cursor에게 최신 Next.js 프로젝트를 설정하도록 요청하는 것으로 시작하세요:

# Ask Cursor: "Generate the command to create a Next.js 14+ app with TypeScript and Tailwind"
npx create-next-app@latest my-app --typescript --tailwind --eslint --app --src-dir --import-alias "@/*"

생성 후, Cursor가 필수 파일을 구성하도록 하세요:

// app/layout.tsx
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import './globals.css';

const inter = Inter({ subsets: ['latin'] });

export const metadata: Metadata = {
title: 'My Next.js App',
description: 'Built with Cursor AI',
};

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
);
}
팁

Cursor에게 Next.js 코드를 요청할 때 항상 "App Router"를 지정하세요. Pages Router는 다른 패턴을 가지며, Cursor가 이전 스타일로 기본 설정할 수 있습니다.

디렉토리 구조​

Cursor에게 확장 가능한 폴더 구조를 생성하도록 요청하세요:

my-app/
├── app/
│ ├── api/ # API 라우트
│ ├── (auth)/ # 라우트 그룹 (URL 세그먼트 없음)
│ ├── dashboard/ # 대시보드 페이지
│ ├── layout.tsx # 루트 레이아웃
│ ├── page.tsx # 홈 페이지
│ ├── loading.tsx # 로딩 UI
│ └── error.tsx # 오류 경계
├── components/
│ ├── ui/ # 재사용 가능한 UI 컴포넌트
│ └── forms/ # 폼 컴포넌트
├── lib/
│ ├── db.ts # 데이터베이스 연결
│ ├── auth.ts # 인증 도우미
│ └── utils.ts # 유틸리티 함수
├── types/
│ └── index.ts # 전역 TypeScript 타입
├── hooks/
│ └── useUser.ts # 커스텀 React 훅
└── public/
└── images/ # 정적 자산

App Router 패턴​

서버 컴포넌트​

App Router는 기본적으로 서버 컴포넌트를 사용합니다. Cursor를 사용하여 효율적인 서버 사이드 코드를 생성하세요:

// app/users/page.tsx - 서버 컴포넌트
import { Suspense } from 'react';
import { UserList } from '@/components/UserList';
import { UserListSkeleton } from '@/components/UserListSkeleton';

// 서버에서 실행됨
async function getUsers() {
const res = await fetch('https://api.example.com/users', {
next: { revalidate: 60 } // 60초마다 재검증
});
if (!res.ok) throw new Error('Failed to fetch users');
return res.json();
}

export default async function UsersPage() {
const usersPromise = getUsers();

return (
<main className="p-8">
<h1 className="text-2xl font-bold mb-4">Users</h1>
<Suspense fallback={<UserListSkeleton />}>
<UserList usersPromise={usersPromise} />
</Suspense>
</main>
);
}
정보

Cursor에게 "proper error handling and loading states를 포함하여 데이터를 가져오는 서버 컴포넌트를 생성해주세요"라고 요청하면 최상의 결과를 얻을 수 있습니다.

클라이언트 컴포넌트​

상호작용이 필요할 때, 컴포넌트에 'use client'를 표시하세요:

// components/SearchForm.tsx
'use client';

import { useState } from 'react';
import { useRouter } from 'next/navigation';

export function SearchForm() {
const [query, setQuery] = useState('');
const router = useRouter();

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
router.push(`/search?q=${encodeURIComponent(query)}`);
};

return (
<form onSubmit={handleSubmit} className="flex gap-2">
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
className="border rounded px-3 py-2"
/>
<button type="submit" className="bg-blue-500 text-white px-4 py-2 rounded">
Search
</button>
</form>
);
}

라우트 그룹 및 레이아웃​

라우트 그룹을 사용하여 URL에 영향을 주지 않고 관련 페이지를 구성하세요:

app/
├── (marketing)/
│ ├── layout.tsx # 마케팅 레이아웃 (다른 nav, footer)
│ ├── page.tsx # / (홈페이지)
│ ├── about/
│ │ └── page.tsx # /about
│ └── pricing/
│ └── page.tsx # /pricing
├── (dashboard)/
│ ├── layout.tsx # 대시보드 레이아웃 (사이드바, 인증 확인)
│ ├── dashboard/
│ │ └── page.tsx # /dashboard
│ └── settings/
│ └── page.tsx # /dashboard/settings
└── api/
└── ...

API 라우트​

RESTful API 엔드포인트​

Cursor를 사용하여 타입 안전한 API 라우트를 생성하세요:

// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';

const createUserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
});

export async function GET() {
try {
const users = await db.user.findMany();
return NextResponse.json({ users });
} catch (error) {
return NextResponse.json(
{ error: 'Failed to fetch users' },
{ status: 500 }
);
}
}

export async function POST(request: NextRequest) {
try {
const body = await request.json();
const validated = createUserSchema.parse(body);

const user = await db.user.create({
data: validated,
});

return NextResponse.json({ user }, { status: 201 });
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: 'Invalid input', details: error.errors },
{ status: 400 }
);
}
return NextResponse.json(
{ error: 'Failed to create user' },
{ status: 500 }
);
}
}

동적 라우트 핸들러​

// app/api/users/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server';

export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const user = await db.user.findUnique({
where: { id: params.id },
});

if (!user) {
return NextResponse.json(
{ error: 'User not found' },
{ status: 404 }
);
}

return NextResponse.json({ user });
}

인증​

NextAuth.js로 인증 구현​

Cursor에게 인증 설정을 요청하세요:

// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
import { PrismaAdapter } from '@auth/prisma-adapter';
import { db } from '@/lib/db';

const handler = NextAuth({
adapter: PrismaAdapter(db),
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
],
callbacks: {
session({ session, user }) {
if (session.user) {
session.user.id = user.id;
}
return session;
},
},
});

export { handler as GET, handler as POST };

라우트 보호​

// middleware.ts
import { withAuth } from 'next-auth/middleware';

export default withAuth({
pages: {
signIn: '/login',
},
});

export const config = {
matcher: ['/dashboard/:path*', '/api/protected/:path*'],
};

데이터베이스 통합​

Prisma 설정​

// lib/db.ts
import { PrismaClient } from '@prisma/client';

const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};

export const db = globalForPrisma.prisma ?? new PrismaClient();

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = db;

서버 액션​

폼 제출 및 변경을 위해 서버 액션 사용:

// app/actions.ts
'use server';

import { revalidatePath } from 'next/cache';
import { z } from 'zod';

const createPostSchema = z.object({
title: z.string().min(1),
content: z.string().min(1),
});

export async function createPost(formData: FormData) {
const data = Object.fromEntries(formData);
const validated = createPostSchema.parse(data);

const post = await db.post.create({
data: validated,
});

revalidatePath('/posts');
return { success: true, post };
}
// app/posts/new/page.tsx
import { createPost } from '@/app/actions';

export default function NewPostPage() {
return (
<form action={createPost}>
<input name="title" placeholder="Title" required />
<textarea name="content" placeholder="Content" required />
<button type="submit">Create Post</button>
</form>
);
}
경고

서버 액션에서 항상 입력을 검증하세요. 클라이언트가 제출한 데이터를 절대 신뢰하지 마세요.

AI 지원 코드 생성​

페이지 생성​

필요한 것을 설명하면 Cursor가 전체 페이지를 생성합니다:

Prompt: "Create a Next.js page at /products that displays a grid of product cards. Each card should show an image, name, price, and an 'Add to Cart' button. Use Server Components for data fetching and Tailwind for styling."

API 라우트 생성​

Prompt: "Create a CRUD API for a Todo model with Prisma. Include GET all, GET by id, POST create, PATCH update, and DELETE endpoints. Add Zod validation and proper error handling."

컴포넌트 생성​

Prompt: "Generate a reusable DataTable component with sorting, filtering, and pagination. Use TypeScript and accept a generic row type."

Vercel에 배포​

배포 전 체크리스트​

확인명령어
타입 확인npx tsc --noEmit
린트npm run lint
로컬 빌드npm run build
테스트npm test

환경 변수​

Vercel에서 프로덕션 환경 변수 구성:

# .env.local (개발)
DATABASE_URL="postgresql://..."
NEXTAUTH_SECRET="your-secret"
NEXTAUTH_URL="http://localhost:3000"
GOOGLE_CLIENT_ID="..."
GOOGLE_CLIENT_SECRET="..."

배포 구성​

// vercel.json
{
"buildCommand": "prisma generate && next build",
"installCommand": "npm install",
"framework": "nextjs",
"regions": ["iad1"]
}
팁

Cursor에게 "environment setup을 포함하여 이 Next.js 앱을 Vercel에 배포하기 위한 완전한 가이드를 생성해주세요"라고 요청하여 프로젝트별 지침을 얻으세요.

성능 최적화​

이미지 최적화​

import Image from 'next/image';

// 자동 최적화
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority
className="rounded-lg"
/>

스트리밍 및 서스펜스​

import { Suspense } from 'react';
import { ProductList } from '@/components/ProductList';
import { ProductListSkeleton } from '@/components/ProductListSkeleton';

export default function ShopPage() {
return (
<div>
<h1>Shop</h1>
{/* 즉시 렌더링 */}
<CategoryNav />

{/* 준비되면 스트리밍 */}
<Suspense fallback={<ProductListSkeleton />}>
<ProductList />
</Suspense>
</div>
);
}

캐싱 전략​

전략사용 사례구현
정적변경되지 않는 콘텐츠export const dynamic = 'force-static'
재검증준정적 콘텐츠next: { revalidate: 60 }
동적사용자별 콘텐츠export const dynamic = 'force-dynamic'
온디맨드이벤트에서 변경되는 콘텐츠revalidatePath() / revalidateTag()

요약​

Cursor를 사용한 풀스택 Next.js 애플리케이션 구축은 다음을 포함합니다:

  • AI를 활용한 현대적 패턴으로 프로젝트 스캐폴드
  • 효율적인 데이터 가져오기를 위한 서버 컴포넌트 활용
  • 타입 안전한 API 라우트 및 서버 액션 생성
  • NextAuth.js로 인증 구현
  • 적절한 구성으로 Vercel에 원활하게 배포
  • 스트리밍, 서스펜스, 캐싱으로 성능 최적화

Cursor의 Next.js 규칙에 대한 이해는 자연어로 기능을 설명하고 프레임워크 모범 사례를 따르는 프로덕션 준비 코드를 받을 수 있음을 의미합니다.


마지막 업데이트: 2025년 7월. Next.js 기능은 설치된 버전에 따라 다릅니다. App Router는 Next.js 13+가 필요합니다.