Next.js 15 실전: 주말 하나로 프로덕션급 블로그 시스템을 만든 과정

들어가며
Next.js 15가 출시된 지 얼마 되지 않았을 때 공식 문서를 몇 번이나 읽고 튜토리얼 영상도 많이 봤지만, 계속 이론에만 머물러 있다는 느낌이 들었습니다. Server Actions와 App Router의 개념은 알았지만 실제 프로젝트에서 어떻게 써야 할지는 막막했습니다. 그래서 주말에는 튜토리얼을 그만 보고 실제 프로젝트, 즉 풀스택 블로그 시스템을 직접 만들기로 했습니다. 프론트엔드부터 백엔드, 데이터베이스, 배포까지 모두 다뤘고, 이틀 뒤 블로그를 Vercel에 성공적으로 배포해 Lighthouse 성능 점수 96점을 받았습니다.
이 글은 코드를 복사해 붙여 넣고 끝내는 튜토리얼이 아닙니다. 왜 이렇게 구현했는지, 어떤 문제를 겪었는지까지 전체 과정을 공유합니다. 기술 스택은 Next.js 15 + Server Actions + Prisma + PostgreSQL이며 모두 프로덕션 수준의 코드입니다.
왜 Next.js 15를 선택했을까?
기술 스택을 고르는 데 꽤 오래 고민했습니다.
Next.js 15가 막 나왔을 때 커뮤니티에서는 “또 업데이트됐네. 이제는 더 못 배우겠다”는 불평도 보였습니다. 솔직히 저도 처음에는 반감이 있었습니다. Next.js 14도 아직 완전히 이해하지 못했으니까요. 하지만 새 기능을 자세히 살펴보니 이번 업그레이드는 정말 매력적이었습니다.
Server Actions: 번거로운 API Routes와 작별하기
예전 풀스택 개발에서 가장 귀찮은 일은 API Routes를 작성하는 것이었습니다. api/posts/route.ts를 만들고 POST 메서드를 정의하고 요청 본문을 처리한 뒤 응답을 반환하는 똑같은 과정을 매번 반복해야 했습니다.
Server Actions는 이 방식을 완전히 바꿨습니다. 함수 앞에 'use server'만 추가한 뒤 컴포넌트에서 바로 호출하면 됩니다. 처음 이 기능을 봤을 때는 “백엔드 코드를 프론트엔드에 쓰는 것 아닌가?” 싶었지만, 한 번 써 보니 생각이 완전히 바뀌었습니다.
예를 들어 예전에는 블로그 글을 만들 때 다음처럼 작성해야 했습니다.
// 기존 방식: API Route를 만들어야 함
// app/api/posts/route.ts
export async function POST(request: Request) {
const body = await request.json()
// 여러 처리 로직...
}
// 프론트엔드에서도 fetch를 호출해야 함
const response = await fetch('/api/posts', {
method: 'POST',
body: JSON.stringify(data)
})
이제는 다음과 같이 바로 작성합니다.
// app/actions/post-actions.ts
'use server'
export async function createPost(formData: FormData) {
const title = formData.get('title')
const content = formData.get('content')
// 데이터베이스를 직접 조작
return await prisma.post.create({
data: { title, content }
})
}
컴포넌트에서 로컬 함수를 호출하듯 간단히 사용할 수 있습니다. 일상적인 비유를 들면, 예전 API Routes는 직접 식당에 가서 포장 음식을 찾아오는 방식이고 Server Actions는 집까지 배달받는 방식입니다.
Turbopack으로 개발 속도가 두 배 이상 빨라지다
Next.js 15에서는 Turbopack이 실험 단계를 벗어나 안정화됐습니다. 공식 발표에 따르면 로컬 서버 시작 속도는 76.7%, 코드 업데이트 속도는 96.3% 빨라졌습니다. 처음에는 마케팅용 수치라고 생각했지만 실제로 사용해 보니 과장이 아니었습니다.
제 프로젝트에는 컴포넌트가 30개 이상 있는데 Webpack에서는 시작에 7~8초가 걸렸고 Turbopack에서는 2초 이내로 줄었습니다. 코드를 수정하면 핫 리로드도 거의 즉시 완료됩니다. 개발 경험이 확실히 달라졌습니다.
이 기술 스택이 블로그에 적합한 이유
블로그에 Next.js를 선택한 이유는 크게 세 가지입니다.
SSR/SSG의 기본 장점 — 블로그에서 가장 중요한 요소 중 하나는 SEO입니다. Next.js의 서버 사이드 렌더링과 정적 생성은 이런 상황에 알맞게 설계됐습니다. Google 크롤러가 완성된 HTML을 바로 받을 수 있어 순수 클라이언트 렌더링 React보다 훨씬 유리합니다.
Prisma의 타입 안전성 — 예전에 Mongoose로 MongoDB를 사용했을 때는 타입 정의를 모두 수동으로 관리해야 했고 작은 실수도 버그로 이어지곤 했습니다. Prisma는 schema에서 TypeScript 타입을 바로 생성하므로 코드를 작성할 때 완전한 자동 완성을 제공하고 실수를 크게 줄여 줍니다.
Server Actions로 단순해진 개발 — API Routes를 작성할 필요가 없어 코드 양이 최소 30% 줄었습니다. 이 블로그 시스템을 기존 방식으로 만들었다면 route.ts 파일을 몇 개는 더 작성해야 했을 것입니다.
기술 스택 선정과 아키텍처 설계
선택 이유를 정리했으니 이제 구체적으로 어떻게 만들었는지 살펴보겠습니다.
전체 기술 스택
- 프론트엔드: Next.js 15 + TypeScript + Tailwind CSS
- 백엔드: Next.js Server Actions + NextAuth.js
- 데이터베이스: PostgreSQL + Prisma ORM
- 배포: Vercel
MongoDB를 쓰지 않은 이유가 궁금할 수 있습니다. 처음에는 저도 MongoDB의 유연성 때문에 고민했습니다. 하지만 Prisma가 PostgreSQL을 더 잘 지원하고, 블로그 글·사용자·댓글처럼 관계가 명확한 데이터를 다룰 때는 관계형 데이터베이스가 오히려 더 적합했습니다.
디렉터리 구조
my-blog/
├── app/
│ ├── (auth)/ # 인증 관련 페이지
│ ├── blog/ # 블로그 관련 페이지
│ │ └── [slug]/ # 동적 라우트
│ ├── dashboard/ # 사용자 대시보드
│ ├── actions/ # Server Actions를 모아 두는 위치
│ └── api/auth/ # NextAuth 설정
├── components/ # 재사용 컴포넌트
├── lib/
│ ├── prisma.ts # Prisma 싱글턴(매우 중요!)
│ └── utils.ts # 유틸리티 함수
├── prisma/
│ └── schema.prisma # 데이터베이스 Schema
└── public/ # 정적 리소스
이 구조를 확정하기까지 몇 번이나 수정했습니다. 처음에는 Server Actions를 각 페이지 파일에 흩어 놓았지만 유지보수가 너무 복잡해져 actions 디렉터리를 따로 만들고 한곳에서 관리했습니다. 훨씬 깔끔해졌습니다.
데이터베이스 Schema 설계
데이터베이스를 설계할 때 큰 시행착오를 겪었습니다. 첫 버전에는 태그, 카테고리, 조회 통계까지 넣어 지나치게 복잡하게 만들었습니다. 작업 중간에 그렇게 많은 테이블이 필요하지 않다는 사실을 깨닫고 반나절을 들여 다시 단순화했습니다.
최종 핵심 Schema는 다음과 같습니다.
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
name String?
image String?
posts Post[]
createdAt DateTime @default(now())
}
model Post {
id String @id @default(cuid())
title String
slug String @unique
content String @db.Text
published Boolean @default(false)
authorId String
author User @relation(fields: [authorId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([slug])
@@index([authorId])
}
마지막의 @@index 두 개가 성능 최적화의 핵심입니다. 블로그 목록 페이지는 slug로 자주 조회하고 사용자 페이지는 authorId로 필터링하므로 인덱스를 추가하면 쿼리 속도를 몇 배 높일 수 있습니다.
핵심 기능 구현 상세
아키텍처를 설계했으니 이제 가장 흥미로운 코딩 단계로 넘어가겠습니다.
3.1 환경 구성과 초기화
Next.js 15 프로젝트 생성은 매우 간단합니다.
npx create-next-app@latest my-blog
cd my-blog
npm install prisma @prisma/client zod next-auth
Prisma 설치 후 다음 명령으로 초기화합니다.
npx prisma init
그러면 prisma/schema.prisma와 .env 파일이 생성됩니다. .env에 PostgreSQL 연결을 설정합니다.
DATABASE_URL="postgresql://username:password@localhost:5432/myblog?schema=public"
로컬 개발에서는 Docker로 PostgreSQL을 실행했습니다. 명령 한 줄이면 됩니다.
docker run --name blog-postgres -e POSTGRES_PASSWORD=mypassword -p 5432:5432 -d postgres
3.2 Prisma 데이터베이스 통합
Schema 설정 후 migration을 실행합니다.
npx prisma migrate dev --name init
여기서 특히 주의할 점은 Prisma Client 싱글턴 패턴입니다. 처음 Vercel에 배포했을 때 이 설정을 완전히 잊었고, 반나절 만에 사이트에서 Too many connections 오류가 발생했습니다. 처음에는 DDoS 공격을 받은 줄 알았습니다.
원인은 Next.js 개발 환경의 핫 리로드였습니다. 리로드될 때마다 새로운 Prisma Client 인스턴스가 생성되어 연결 풀이 빠르게 소진된 것입니다. 올바른 방식은 다음과 같습니다.
// lib/prisma.ts
import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma
}
코드가 조금 복잡해 보이지만 개발 환경에서 Prisma 인스턴스가 하나만 존재하도록 보장하며 프로덕션 환경에는 영향을 주지 않습니다. Prisma가 공식적으로 권장하는 모범 사례이니 꼭 기억해 두세요.
3.3 Server Actions 실전 적용
블로그 글 생성은 핵심 기능입니다. Server Actions를 어떻게 작성하는지 살펴보겠습니다.
// app/actions/post-actions.ts
'use server'
import { prisma } from '@/lib/prisma'
import { revalidatePath } from 'next/cache'
import { z } from 'zod'
// Zod로 데이터를 검증해 타입 안전성을 극대화
const PostSchema = z.object({
title: z.string().min(1, '제목은 비워 둘 수 없습니다').max(100),
content: z.string().min(10, '내용은 최소 10자여야 합니다'),
slug: z.string().regex(/^[a-z0-9-]+$/, 'slug에는 소문자, 숫자, 하이픈만 사용할 수 있습니다')
})
export async function createPost(formData: FormData) {
// 데이터 검증
const validatedFields = PostSchema.safeParse({
title: formData.get('title'),
content: formData.get('content'),
slug: formData.get('slug')
})
if (!validatedFields.success) {
return { error: '데이터 검증에 실패했습니다' }
}
try {
const post = await prisma.post.create({
data: {
...validatedFields.data,
authorId: 'user-id-here' // 실제 프로젝트에서는 session에서 가져옴
}
})
// 캐시를 재검증해 새 글을 즉시 표시
revalidatePath('/blog')
return { success: true, post }
} catch (error) {
return { error: '글 생성에 실패했습니다' }
}
}
컴포넌트에서는 바로 사용합니다.
// app/dashboard/new-post/page.tsx
import { createPost } from '@/app/actions/post-actions'
export default function NewPost() {
return (
<form action={createPost}>
<input name="title" placeholder="제목" />
<textarea name="content" placeholder="내용" />
<input name="slug" placeholder="URL slug" />
<button type="submit">게시</button>
</form>
)
}
useState도, fetch도, 심지어 onSubmit 이벤트 처리도 필요 없습니다. 폼이 Server Action을 직접 호출하며 Next.js가 직렬화, 네트워크 요청, 오류 처리 같은 번거로운 작업을 자동으로 담당합니다.
저도 처음에는 Server Actions와 API Routes의 차이를 이해하기 어려웠습니다. Server Actions가 배달 음식을 집까지 받는 방식이라면 API Routes는 직접 식당에 가지러 가는 방식이라고 생각하면 쉽습니다. 전자는 편리하고 후자는 유연합니다. 대부분의 상황에서는 Server Actions만으로 충분합니다.
3.4 사용자 인증 시스템
인증에는 NextAuth.js를 사용했으며 설정은 꽤 간단합니다.
// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth'
import GitHubProvider from 'next-auth/providers/github'
import { PrismaAdapter } from '@auth/prisma-adapter'
import { prisma } from '@/lib/prisma'
export const authOptions = {
adapter: PrismaAdapter(prisma),
providers: [
GitHubProvider({
clientId: process.env.GITHUB_ID!,
clientSecret: process.env.GITHUB_SECRET!
})
]
}
const handler = NextAuth(authOptions)
export { handler as GET, handler as POST }
GitHub OAuth를 설정하려면 GitHub Developer Settings에서 애플리케이션을 만들고 Client ID와 Secret을 발급받아 .env에 입력하면 됩니다. 전체 과정은 10분 정도 걸렸고 JWT 인증을 직접 구현하는 것보다 훨씬 간편했습니다.
3.5 SSR/SSG 최적화 전략
이 부분이 성능 최적화의 핵심입니다. 처음에는 모든 페이지에 SSR(서버 사이드 렌더링)을 적용했지만, 블로그 목록을 열 때마다 데이터베이스를 조회할 필요는 없었습니다. 그래서 다음과 같은 혼합 전략으로 바꿨습니다.
블로그 목록 페이지 — SSG + ISR:
// app/blog/page.tsx
import { prisma } from '@/lib/prisma'
// 한 시간마다 재생성
export const revalidate = 3600
export default async function BlogList() {
const posts = await prisma.post.findMany({
where: { published: true },
orderBy: { createdAt: 'desc' }
})
return (
<div>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.content.slice(0, 150)}...</p>
</article>
))}
</div>
)
}
이 페이지는 빌드 시 정적 HTML로 생성되고 이후 한 시간마다 자동으로 재생성됩니다. 사용자는 정적 파일을 바로 받으므로 속도가 매우 빠릅니다.
블로그 상세 페이지 — 동적 SSR:
// app/blog/[slug]/page.tsx
export default async function Post({ params }: { params: { slug: string } }) {
const post = await prisma.post.findUnique({
where: { slug: params.slug }
})
if (!post) notFound()
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
)
}
이 페이지는 방문할 때마다 데이터베이스에서 최신 콘텐츠를 가져오므로 자주 업데이트되는 글에 적합합니다.
성능 비교 데이터:
차이가 분명합니다. 이것이 바로 Next.js가 블로그에 적합한 이유입니다. SEO에 유리하면서 성능도 뛰어납니다.
배포와 최적화
코드 작성이 끝나고 가장 설레는 배포 단계가 찾아왔습니다.
Vercel 배포 절차
코드를 GitHub에 푸시한 뒤 Vercel에서 저장소를 바로 가져왔습니다. Vercel은 Next.js 프로젝트를 감지해 모든 항목을 자동으로 설정했습니다. 수동으로 설정해야 하는 것은 환경 변수뿐이었습니다.
DATABASE_URL="프로덕션 데이터베이스 연결"
GITHUB_ID="OAuth Client ID"
GITHUB_SECRET="OAuth Secret"
NEXTAUTH_URL="https://your-domain.vercel.app"
NEXTAUTH_SECRET="임의의 문자열"
프로덕션 데이터베이스로는 Supabase의 무료 PostgreSQL을 사용했습니다. 월 500MB가 제공되어 개인 블로그에는 충분합니다.
Deploy를 클릭한 뒤 약 2분 만에 사이트가 공개됐습니다. 처음으로 제 도메인에서 사이트가 열리는 모습을 보고 몇 초 동안 화면만 바라보다가 결국 스크린샷을 찍어 지인들에게 공유했습니다.
프로덕션 환경 최적화
배포가 성공했다고 끝난 것은 아닙니다. 몇 가지 최적화 작업이 더 필요합니다.
Prisma 연결 풀 설정:
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
// Vercel Serverless 환경에서는 반드시 설정
directUrl = env("DIRECT_URL")
}
Serverless 환경에서는 함수 호출마다 새로운 연결이 만들어질 수 있으므로 directUrl을 설정해 연결 풀을 재사용합니다.
이미지 최적화:
import Image from 'next/image'
<Image
src="/avatar.jpg"
alt="사용자 프로필 이미지"
width={100}
height={100}
// Next.js에서 자동으로 압축 및 최적화
/>
Next.js의 Image 컴포넌트는 이미지를 WebP 형식으로 자동 변환하고 필요할 때만 로드해 성능을 크게 높여 줍니다.
최종 성능 점수:
Lighthouse 측정 결과 데스크톱 성능 96점, 모바일 92점, SEO 100점이 나왔습니다. 이 결과를 보니 정말 뿌듯했습니다. 이틀 만에 처음부터 배포까지 마치고 프로덕션급 성능까지 달성한 것입니다.
확장 방향과 학습 자료
현재 이 블로그 시스템은 MVP 버전이며 앞으로 추가할 수 있는 기능이 많습니다.
- Markdown 편집기: react-md-editor를 통합할 수 있습니다.
- 댓글 시스템: GitHub Discussions 기반의 Giscus 도입을 고려하고 있습니다.
- 검색 기능: Algolia를 사용하거나 Prisma 전문 검색을 직접 구현할 수 있습니다.
- 다크 모드: next-themes를 사용하면 쉽게 구현할 수 있습니다.
다음에는 우선 Markdown 편집기를 추가해 글쓰기를 더 편리하게 만들 생각입니다.
추천 학습 자료:
- Next.js 공식 문서 - 언제나 가장 신뢰할 수 있는 자료입니다.
- Prisma 문서 - Getting Started를 한 번 읽으면 충분합니다.
- 이 프로젝트의 전체 코드 - clone해서 바로 실행해 볼 수 있습니다.
문서를 열 번 읽는 것보다 한 번 직접 만들어 보는 편이 훨씬 도움이 됩니다. 이론을 배웠더라도 실제로 해 봐야 제대로 익힐 수 있습니다.
마무리
이번 주말 프로젝트에서 배운 점을 정리하면 다음과 같습니다.
- Server Actions의 강력함: 대부분의 API Routes를 실제로 대체할 수 있으며 개발 효율이 크게 향상됩니다.
- Prisma의 타입 안전성: 완전한 TypeScript 지원 덕분에 훨씬 안심하고 코드를 작성할 수 있습니다.
- SSG/SSR의 유연성: 상황에 맞는 렌더링 전략을 선택하면 성능과 SEO를 모두 챙길 수 있습니다.
- 간편한 Vercel 배포: 코드를 배포해 공개하기까지 5분도 걸리지 않았습니다.
솔직히 이 프로젝트에서 얻은 가장 큰 수확은 기술 자체보다 자신감이었습니다. 풀스택 개발도 생각만큼 어렵지 않았고, 핵심은 직접 시작하는 것이었습니다.
자신만의 블로그 시스템을 만들고 싶다면 망설이지 말고 지금 시작해 보세요. 문제가 생기는 것은 당연합니다. 저도 문서를 찾아가며 수없이 시행착오를 겪은 끝에 완성했습니다. 하지만 직접 만든 결과물이 공개되는 순간 그동안의 노력은 모두 보상받습니다.
이 글이 여러분의 시행착오를 조금이나마 줄이는 데 도움이 되길 바랍니다. 궁금한 점이 있다면 댓글로 이야기해 주세요. 가능한 한 답변하겠습니다.
여러분의 결과물도 기대하겠습니다!
Next.js 15로 프로덕션급 블로그 시스템을 구축하는 전체 과정
환경 구성부터 배포까지 Server Actions, Prisma, SSG/SSR 최적화 등 핵심 기능 구현을 포함한 전체 절차
Estimated time: PT16H
-
1
Step 1: 환경 구성 및 프로젝트 초기화
Next.js 15 프로젝트 생성: -
2
Step 2: Prisma 데이터베이스 Schema 설정
핵심 Schema 설계: -
3
Step 3: Server Actions 핵심 기능 구현
Server Actions 파일 생성: -
4
Step 4: 사용자 인증 시스템 설정
NextAuth.js 설치: -
5
Step 5: SSR/SSG 렌더링 전략 최적화
블로그 목록 페이지에는 SSG+ISR 사용: app/blog/page.tsx에서 export const revalidate = 3600을 설정해 한 시간마다 재생성하고, prisma.post.findMany로 게시된 글을 조회한 뒤 createdAt 내림차순으로 정렬합니다. 이 페이지는 빌드 시 정적 HTML로 생성된 후 한 시간마다 자동 재생성되며, 사용자는 정적 파일을 바로 받아 약 200ms 만에 로딩할 수 있습니다. 블로그 상세 페이지에는 동적 SSR 사용: app/blog/[slug]/page.tsx에서 방문할 때마다 데이터베이스에서 최신 콘텐츠를 가져오며 prisma.post.findUnique({ where: { slug: params.slug } })로 조회합니다. 자주 업데이트되는 글에 적합하고 로딩 시간은 약 800ms입니다. 성능 비교: SSG 약 200ms, SSR 약 800ms, 순수 CSR 약 1,500ms로 차이가 분명합니다. -
6
Step 6: Vercel 배포 및 최적화
코드를 GitHub에 푸시: 프로젝트 코드를 GitHub 저장소에 푸시합니다. Vercel에서 프로젝트 가져오기: Vercel에서 GitHub 저장소를 바로 가져오면 Next.js 프로젝트를 감지해 모든 항목을 자동으로 설정합니다. 환경 변수 설정: Vercel 프로젝트 설정에서 DATABASE_URL(프로덕션 데이터베이스 연결), GITHUB_ID, GITHUB_SECRET, NEXTAUTH_URL(https://your-domain.vercel.app), NEXTAUTH_SECRET(임의의 문자열)을 추가합니다. 프로덕션 데이터베이스로는 Supabase의 무료 PostgreSQL을 권장합니다(월 500MB로 개인 블로그에는 충분합니다). Prisma 연결 풀 설정: prisma/schema.prisma의 datasource에 directUrl = env(“DIRECT_URL”)을 추가합니다. Serverless 환경에서는 함수 호출마다 새 연결이 만들어질 수 있으므로 directUrl을 설정해 연결 풀을 재사용합니다. 이미지 최적화: Next.js의 Image 컴포넌트를 사용하면 WebP 형식 자동 변환과 지연 로딩으로 성능이 크게 향상됩니다. Deploy를 클릭하면 약 2분 뒤 사이트가 공개되며, 최종 Lighthouse 점수는 데스크톱 성능 96점, 모바일 92점, SEO 100점입니다.
FAQ
Next.js 15의 Server Actions와 API Routes는 무엇이 다르며, 각각 언제 사용해야 하나요?
Server Actions의 장점:
1) API Routes를 작성하지 않아도 되어 코드 양이 30% 줄어듭니다.
2) useState, fetch, onSubmit 이벤트 처리 없이 컴포넌트에서 바로 호출할 수 있습니다.
3) Next.js가 직렬화, 네트워크 요청, 오류 처리를 자동으로 담당합니다.
4) TypeScript 타입을 그대로 사용할 수 있어 타입 안전성이 보장됩니다.
사용 예: 함수 앞에 'use server'를 추가한 다음 컴포넌트에서 바로 호출합니다(<form action={createPost}>).
API Routes의 장점:
• 더 유연하며 복잡한 요청 로직을 처리할 수 있습니다.
• 미들웨어를 지원합니다.
• HTTP 응답을 직접 구성해야 하는 상황에 적합합니다.
대부분의 상황에서는 Server Actions만으로 충분하며, 복잡한 HTTP 처리가 필요할 때만 API Routes를 사용하면 됩니다.
Turbopack은 성능을 얼마나 높여 주며, 실제 사용 경험은 어떤가요?
공식 수치:
• 로컬 서버 시작 속도 76.7% 향상
• 코드 업데이트 속도 96.3% 향상
실제 사용 경험:
• 컴포넌트가 30개 이상인 제 프로젝트는 Webpack으로 시작할 때 7~8초가 걸렸지만 Turbopack에서는 2초 이내로 줄었습니다.
• 코드를 수정하면 핫 리로드가 거의 즉시 완료됩니다.
• 개발 경험이 확연히 좋아집니다.
Turbopack은 Rust로 작성되어 Webpack보다 훨씬 빠르며, 특히 대규모 프로젝트에서 장점이 더 뚜렷합니다. 아직 Webpack을 사용한다면 Next.js 15로 업그레이드해 Turbopack을 체험해 보길 강력히 권합니다.
Prisma Client 싱글턴 패턴은 왜 중요하며, 설정하지 않으면 어떤 문제가 생기나요?
문제가 발생하는 상황:
• Next.js 개발 환경은 핫 리로드될 때마다 새로운 Prisma Client 인스턴스를 만듭니다.
• 연결 풀이 빠르게 소진되어 'Too many connections' 오류가 발생합니다.
• 처음 Vercel에 배포했을 때 이 설정을 완전히 잊었고, 반나절 만에 사이트에서 이 오류가 나서 DDoS 공격을 받은 줄 알았습니다.
올바른 설정:
• lib/prisma.ts에서 전역 싱글턴을 구현합니다.
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined };
export const prisma = globalForPrisma.prisma ?? new PrismaClient();
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma;
}
이 코드는 개발 환경에서 Prisma 인스턴스가 하나만 존재하도록 보장하며 프로덕션 환경에는 영향을 주지 않습니다. Prisma가 공식적으로 권장하는 모범 사례이니 꼭 기억해 두세요.
SSG, SSR, ISR은 무엇이 다르며 어떻게 선택해야 하나요?
• 빌드 시 정적 HTML을 생성하며 속도가 가장 빠릅니다(~200ms).
• 콘텐츠가 자주 바뀌지 않는 페이지에 적합합니다(예: 블로그 목록, 소개 페이지).
SSR(Server-Side Rendering):
• 요청이 들어올 때마다 서버에서 HTML을 생성합니다.
• 실시간 데이터가 필요한 페이지에 적합합니다(예: 블로그 상세 페이지, 사용자 프로필).
• 로딩 시간은 약 800ms입니다.
ISR(Incremental Static Regeneration):
• SSG에 주기적 재생성을 더해 SSG의 속도와 SSR의 유연성을 결합합니다.
• 가끔 업데이트되는 페이지에 적합합니다(예: 한 시간마다 갱신되는 블로그 목록).
순수 CSR(Client-Side Rendering):
• 브라우저에서 렌더링하므로 SEO에 불리하고 로딩 시간은 약 1,500ms입니다.
• 블로그에는 권장하지 않습니다.
선택 전략:
• 블로그 목록 페이지에는 SSG+ISR을 사용합니다(export const revalidate = 3600).
• 블로그 상세 페이지에는 동적 SSR을 사용합니다.
• 소개 페이지에는 순수 SSG를 사용합니다.
성능 비교: SSG 약 200ms, SSR 약 800ms, 순수 CSR 약 1,500ms로 차이가 분명합니다.
MongoDB 대신 PostgreSQL을 선택한 이유는 무엇이며, Prisma의 PostgreSQL 지원은 어떤가요?
1) Prisma는 PostgreSQL의 타입 안전성, 마이그레이션 도구, 쿼리 최적화를 매우 잘 지원합니다.
2) 블로그 글, 사용자, 댓글처럼 관계가 명확한 데이터를 처리할 때는 관계형 데이터베이스가 더 적합합니다.
3) PostgreSQL의 전문 검색 기능은 블로그 검색에 적합합니다.
4) 프로덕션 환경에서 안정성이 높고 트랜잭션 지원도 충실합니다.
Prisma의 장점:
• schema에서 TypeScript 타입을 바로 생성하므로 코드를 작성할 때 완전한 자동 완성을 제공하고 실수를 크게 줄여 줍니다.
• 예전에 Mongoose로 MongoDB를 사용할 때는 타입 정의를 모두 수동으로 관리해야 해서 작은 실수도 버그로 이어지곤 했습니다.
• Prisma의 타입 안전성 덕분에 훨씬 안심하고 코드를 작성할 수 있었습니다.
복잡한 관계형 데이터를 다뤄야 한다면 PostgreSQL+Prisma는 매우 좋은 선택입니다.
Next.js 프로젝트를 Vercel에 배포할 때 무엇을 주의해야 하며, 프로덕션 환경은 어떻게 최적화하나요?
1) 코드를 GitHub에 푸시합니다.
2) Vercel에서 GitHub 저장소를 가져오면 Next.js 프로젝트를 감지해 모든 항목을 자동으로 설정합니다.
3) 환경 변수(DATABASE_URL, GITHUB_ID, GITHUB_SECRET, NEXTAUTH_URL, NEXTAUTH_SECRET)를 설정합니다.
4) Deploy를 클릭하면 약 2분 뒤 사이트가 공개됩니다.
프로덕션 환경 최적화:
1) Prisma 연결 풀 설정:
• prisma/schema.prisma의 datasource에 directUrl = env("DIRECT_URL")을 추가합니다.
• Serverless 환경에서는 함수 호출마다 새 연결이 만들어질 수 있으므로 directUrl을 설정해 연결 풀을 재사용합니다.
2) 이미지 최적화:
• Next.js의 Image 컴포넌트를 사용하면 WebP 형식 자동 변환과 지연 로딩으로 성능이 크게 향상됩니다.
3) 프로덕션 데이터베이스로 Supabase의 무료 PostgreSQL을 권장합니다(월 500MB로 개인 블로그에는 충분합니다).
최종 성능 점수:
• Lighthouse 측정 결과 데스크톱 성능 96점, 모바일 92점, SEO 100점이 나왔습니다.
• 이 결과를 보니 정말 뿌듯했습니다. 이틀 만에 처음부터 배포까지 마치고 프로덕션급 성능까지 달성한 것입니다.
4분 읽기 · 게시일: 2025년 11월 24일 · 수정일: 2026년 9월 4일
Next.js 완전 가이드
검색으로 들어왔다면 같은 시리즈의 이전 글이나 다음 글로 이동하는 것이 가장 빠릅니다.
이전
Next.js App Router 입문 가이드: 핵심 개념과 기본 사용법 완벽 정리
Next.js App Router 초보자를 위한 완벽 가이드입니다. Server Components, Client Components, 특수 파일 등 핵심 개념을 실전 예제와 함께 설명해 Next.js의 새로운 라우팅 시스템을 빠르게 익힐 수 있도록 돕습니다.
45편 중 1편
다음
Next.js Pages Router에서 App Router로 마이그레이션하는 실전 가이드: 점진적 전략과 함정 체크리스트
Next.js Pages Router에서 App Router로 처음부터 끝까지 마이그레이션하는 방법을 소개합니다. 점진적 마이그레이션 방안, 자주 만나는 7가지 함정의 해결법, 실제 프로덕션 프로젝트 경험을 바탕으로 Next.js 14로 안전하게 업그레이드할 수 있도록 돕습니다.
45편 중 3편


댓글
GitHub로 로그인하여 댓글을 남기세요