React 풀스택의
사실상 표준, Next.js
Vercel이 만들고 커뮤니티가 키운 React 풀스택 프레임워크. SSR, SSG, ISR, 서버 컴포넌트, API 라우트까지. React로 실제 웹 앱을 만든다면 Next.js는 가장 자연스러운 선택이다.
순수 React 앱은 클라이언트에서 JavaScript를 다운로드한 뒤에야 화면을 그린다. 검색 엔진은 빈 HTML을 보고, 첫 화면은 느리다. Next.js는 이 문제를 해결하면서 React 개발의 표준 방식으로 자리잡았다.
Pages Router vs App Router
Next.js는 현재 두 가지 라우터를 지원한다. 전통적인 Pages Router와, Next.js 13에서 도입된 App Router다. 신규 프로젝트라면 App Router가 권장된다.
Pages Router (안정)
pages/ 폴더 기반. getServerSideProps, getStaticProps로 데이터 페칭. 성숙하고 생태계가 풍부하다.
App Router (권장)
app/ 폴더 기반. React 서버 컴포넌트 완전 지원. async 컴포넌트로 데이터 페칭. 더 강력하고 유연하다.
App Router 파일 구조
서버 컴포넌트로 데이터 페칭
App Router에서 모든 컴포넌트는 기본적으로 서버 컴포넌트다. async/await를 컴포넌트 안에서 직접 사용해 데이터를 가져올 수 있다.
// app/blog/[slug]/page.tsx — 서버 컴포넌트 (기본값)
import { notFound } from 'next/navigation';
import { db } from '@/lib/db';
interface Props {
params: { slug: string };
}
export default async function BlogPost({ params }: Props) {
// DB에 직접 접근 (서버에서만 실행)
const post = await db.post.findUnique({
where: { slug: params.slug }
});
if (!post) notFound();
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
'use client'; // 클라이언트 컴포넌트가 필요한 경우
import { useState } from 'react';
export function LikeButton({ postId }: { postId: string }) {
const [liked, setLiked] = useState(false);
return (
<button onClick={() => setLiked(!liked)}>
{liked ? '❤️ 좋아요 취소' : '🤍 좋아요'}
</button>
);
}
캐싱과 렌더링 전략
Next.js App Router는 fetch 함수의 옵션으로 렌더링 전략과 캐싱을 세밀하게 제어한다.
// SSR — 매 요청마다 서버에서 새로 렌더링
const data = await fetch(url, {
cache: 'no-store'
});
// SSG — 빌드 타임에 정적으로 생성 (기본값)
const data = await fetch(url);
// ISR — N초마다 백그라운드에서 재생성
const data = await fetch(url, {
next: { revalidate: 3600 } // 1시간마다 갱신
});
// 태그 기반 온디맨드 재검증
const data = await fetch(url, {
next: { tags: ['posts'] }
});
태그 기반 재검증: revalidateTag('posts')를 서버 액션에서 호출하면 해당 태그로 캐시된 모든 데이터가 즉시 무효화된다. CMS에서 글이 발행될 때 특정 페이지만 선택적으로 갱신하는 패턴에 매우 유용하다.
Server Actions
Next.js 14에서 안정화된 Server Actions는 클라이언트 컴포넌트에서 서버 함수를 직접 호출할 수 있게 한다. 별도 API 라우트를 만들지 않아도 된다.
// app/actions.ts
'use server';
import { revalidatePath } from 'next/cache';
import { db } from '@/lib/db';
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
const content = formData.get('content') as string;
await db.post.create({ data: { title, content } });
revalidatePath('/blog'); // 블로그 페이지 캐시 갱신
}
// 클라이언트 컴포넌트에서 직접 사용
'use client';
import { createPost } from './actions';
export function PostForm() {
return (
<form action={createPost}>
<input name="title" />
<textarea name="content" />
<button>발행</button>
</form>
);
}
Next.js가 제공하는 최적화
Image 컴포넌트
자동 WebP 변환, 레이지 로딩, 사이즈 최적화, CLS 방지까지 <Image> 컴포넌트 하나로 처리한다.
Font 최적화
next/font로 Google Fonts를 빌드 타임에 다운로드해 외부 요청 없이 빠르게 로드한다.
Script 최적화
next/script로 서드파티 스크립트의 로딩 전략(lazyOnload, afterInteractive)을 제어한다.
메타데이터 API
페이지별 SEO 메타 태그를 metadata 객체나 generateMetadata 함수로 선언적으로 관리한다.
Middleware
Next.js의 미들웨어는 요청이 페이지에 도달하기 전에 실행된다. 인증 확인, 리디렉션, A/B 테스트, 로케일 감지 등에 활용한다.
// middleware.ts (루트에 위치)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const token = request.cookies.get('session');
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*']
};
Next.js는 React 생태계에서 가장 완성도 높은 풀스택 프레임워크다. Vercel에서 만들어 배포 최적화가 뛰어나지만, Node.js 서버나 Docker로도 완전히 자유롭게 운영할 수 있다. React를 선택했다면 Next.js를 함께 배우는 것이 현실적으로 가장 빠른 길이다.
댓글