Next Js
Next.js란?
Next.js는 Vercel이 개발한 React 기반의 풀스택 웹 프레임워크입니다. React만으로는 처리하기 어려운 라우팅, 서버 사이드 렌더링, 빌드 최적화 등을 프레임워크 차원에서 제공합니다.
핵심 특징
1. 다양한 렌더링 전략
| 전략 | 설명 | 사용 시점 |
|---|---|---|
| SSR (Server-Side Rendering) | 요청마다 서버에서 HTML 생성 | 실시간 데이터가 필요한 페이지 |
| SSG (Static Site Generation) | 빌드 시 HTML을 미리 생성 | 블로그, 문서 등 정적 콘텐츠 |
| ISR (Incremental Static Regeneration) | SSG + 주기적 재생성 | 자주 바뀌지 않는 동적 데이터 |
| CSR (Client-Side Rendering) | 브라우저에서 렌더링 | 사용자 인터랙션 중심 UI |
2. App Router (v13+)
Next.js 13부터 도입된 App Router는 파일 시스템 기반 라우팅을 더욱 강화했습니다.
app/
├── layout.tsx # 루트 레이아웃
├── page.tsx # "/" 경로
├── about/
│ └── page.tsx # "/about" 경로
├── blog/
│ ├── page.tsx # "/blog" 경로
│ └── [slug]/
│ └── page.tsx # "/blog/:slug" 동적 경로
└── api/
└── users/
└── route.ts # API 엔드포인트
3. Server Components vs Client Components
// 기본: Server Component (서버에서만 실행)
async function ProductPage() {
const products = await db.query('SELECT * FROM products'); // DB 직접 접근 가능
return <ProductList products={products} />;
}
// Client Component (브라우저에서 실행)
'use client';
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
| 구분 | Server Component | Client Component |
|---|---|---|
| 선언 | 기본값 (별도 선언 불필요) | 'use client' 지시어 필요 |
| 상태/이벤트 | ❌ useState, onClick 사용 불가 | ✅ 사용 가능 |
| 서버 리소스 | ✅ DB, 파일시스템 직접 접근 | ❌ API를 통해서만 접근 |
| 번들 크기 | JS 번들에 포함 안 됨 (RSC Payload로 결과만 전달) | JS 번들에 포함 |
Server Component의 "JS 번들에 포함되지 않는다"는 무슨 의미인가?
Server Component의 JavaScript 소스 코드(함수 정의, import 등)가 브라우저로 전송되지 않는다는 뜻입니다. 대신 서버에서 실행한 결과물만 RSC Payload라는 형태로 클라이언트에 전달됩니다.
// 이 코드 자체는 브라우저에 절대 전달되지 않음
async function ProductList() {
const products = await db.query('SELECT * FROM products'); // 서버에서 실행
return (
<ul>
{products.map(p => <li key={p.id}>{p.name}</li>)}
</ul>
);
}
브라우저가 받는 것은 이 코드가 아니라, 실행 결과인 RSC Payload입니다.
한마디로, Server Component는 "코드는 서버에, 결과물만 클라이언트에" 라는 개념입니다.
4. RSC Payload — Server Component의 렌더링 결과물
RSC Payload(React Server Component Payload)는 Server Component의 렌더링 결과를 직렬화한 스트리밍 가능한 데이터 포맷입니다.
// 실제 RSC Payload (간략화)
0:["$","ul",null,{"children":[
["$","li",1,{"children":"상품A"}],
["$","li",2,{"children":"상품B"}]
]}]
2:["$","$Lclient-counter",null,{}] // Client Component 참조
RSC Payload에는 다음이 포함됩니다:
- Server Component의 렌더링 결과 (가상 DOM 트리)
- Client Component가 들어갈 자리(placeholder)와 참조
- 서버에서 가져온 데이터
RSC Payload vs JS 번들 비교
| RSC Payload | JS 번들 | |
|---|---|---|
| 내용물 | Server Component의 렌더링 결과 (데이터) | Client Component의 소스 코드 (실행 가능한 JS) |
| 목적 | React 트리 복원 & DOM 업데이트 | 하이드레이션 & 인터랙션 활성화 |
| 생성 시점 | 요청마다 서버에서 실행 후 생성 | 빌드 시 번들링 |
| 포맷 | 직렬화된 스트리밍 데이터 | JavaScript 파일 (.js) |
RSC Payload는 "이미 실행된 결과를 전달하는 데이터", JS 번들은 "브라우저에서 실행할 코드" 입니다.
5. Server Component 렌더링 흐름
초기 페이지 요청 (Full Page Load)
서버는 세 가지를 내려줍니다:
브라우저 요청: GET /products
│
▼
┌─────────────────────────────────┐
│ Next.js 서버 │
│ │
│ Server Component 실행 │
│ → RSC Payload 생성 │
│ → HTML 생성 (RSC Payload 기반) │
└─────────────────────────────────┘
│
▼ 동시에 내려감
┌───────────────────────────────────────┐
│ ① HTML (빠른 첫 화면 표시용) │
│ → <ul><li>상품A</li>...</ul> │
│ │
│ ② RSC Payload (인라인 <script> 내) │
│ → React가 컴포넌트 트리를 │
│ 복원하기 위한 직렬화된 데이터 │
│ │
│ ③ Client Component JS 번들 │
│ → 'use client' 컴포넌트만 포함 │
│ → 하이드레이션에 필요 │
└───────────────────────────────────────┘
핵심: HTML은 "빠른 화면 표시"용이고, RSC Payload는 "React 트리 복원"용입니다. Server Component의 JS 소스 코드는 어디에도 포함되지 않습니다.
클라이언트 사이드 네비게이션 (이후 페이지 이동)
<Link>를 통해 다른 페이지로 이동할 때는 HTML 없이 RSC Payload만 받습니다:
사용자: /products 페이지에서 /about 링크 클릭
│
▼
┌─────────────────────────────────┐
│ Next.js 서버 │
│ │
│ /about의 Server Component 실행 │
│ → RSC Payload만 생성 │
│ (HTML은 생성하지 않음) │
└─────────────────────────────────┘
│
▼
┌───────────────────────────────────────┐
│ ① RSC Payload (fetch 응답) │
│ → React가 기존 트리와 비교(diff) │
│ → DOM을 부분 업데이트 │
│ │
│ ② /about의 Client Component JS 청크 │
│ → 아직 안 받은 것만 추가 로드 │
└───────────────────────────────────────┘
JS 번들의 코드 스플리팅
Next.js는 코드 스플리팅으로 JS를 여러 청크로 나눠서 전달하지만, 그 청크에 포함되는 것은 Client Component 코드뿐입니다.
초기 로드 시 다운로드되는 JS 청크들:
├── framework.js ← React 런타임, Next.js 런타임
├── layout-abc123.js ← 루트 레이아웃의 Client Component
├── page-def456.js ← 현재 페이지의 Client Component
└── commons-ghi789.js ← 공통 Client Component
❌ Server Component 코드는 어떤 청크에도 없음
네비게이션 시 추가로 받는 것:
/about 이동 시:
├── about-page-xyz.js ← /about의 Client Component 청크 (새로 필요한 것만)
└── RSC Payload ← /about의 Server Component 렌더링 결과 (JS가 아님)
정리
| 질문 | 답변 |
|---|---|
| Server Component가 HTML에 포함되나? | ✅ 초기 요청 시 HTML에 렌더링 결과가 포함됨 (+ RSC Payload도 함께) |
| 이후에는 받을 수 없나? | ❌ 클라이언트 네비게이션 시 RSC Payload로 받음 (HTML 없이) |
| JS 번들을 여러 번 받나? | ✅ 코드 스플리팅으로 여러 청크를 받지만, Client Component만 포함 |
| Server Component 코드는? | 서버에만 존재하고, 실행 결과(RSC Payload)만 전달됨 |
6. 데이터 페칭
App Router에서는 컴포넌트 내에서 직접 async/await로 데이터를 가져옵니다.
// app/posts/page.tsx
async function PostsPage() {
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 } // ISR: 1시간마다 재검증
});
const posts = await res.json();
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
7. 주요 내장 최적화
next/image— 이미지 자동 최적화 (WebP 변환, lazy loading, 반응형 크기)next/font— 폰트 최적화 (레이아웃 시프트 방지, 셀프 호스팅)next/link— 클라이언트 사이드 네비게이션 + 프리페칭- 코드 스플리팅 — 페이지별 자동 번들 분리
- Request Memoization — 동일 요청 자동 중복 제거
8. API Routes / Route Handlers
별도의 백엔드 서버 없이 API를 구축할 수 있습니다.
// app/api/users/route.ts
import { NextResponse } from 'next/server';
export async function GET() {
const users = await db.query('SELECT * FROM users');
return NextResponse.json(users);
}
export async function POST(request: Request) {
const body = await request.json();
const user = await db.insert('users', body);
return NextResponse.json(user, { status: 201 });
}
9. 미들웨어
요청이 완료되기 전에 인증, 리다이렉트, 헤더 조작 등을 수행할 수 있습니다.
// middleware.ts (프로젝트 루트)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const token = request.cookies.get('auth-token');
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*', '/settings/:path*'],
};
Next.js를 사용하는 이유
| 장점 | 설명 |
|---|---|
| SEO 최적화 | SSR/SSG로 크롤러가 완전한 HTML을 수집 가능 |
| 성능 | 자동 코드 스플리팅, 이미지/폰트 최적화 |
| 풀스택 | 프론트엔드 + API를 하나의 프로젝트에서 관리 |
| DX (개발 경험) | Fast Refresh, TypeScript 기본 지원, 파일 기반 라우팅 |
| 배포 용이성 | Vercel 원클릭 배포, Docker/Node.js 서버 자체 호스팅 가능 |
언제 Next.js를 선택할까?
- ✅ SEO가 중요한 마케팅 사이트, 블로그, 이커머스
- ✅ 풀스택 앱을 하나의 코드베이스로 관리하고 싶을 때
- ✅ 초기 로딩 성능이 중요한 서비스
- ❌ 단순한 SPA(싱글 페이지 앱)라면 Vite + React가 더 가벼움
- ❌ React를 사용하지 않는다면 Nuxt.js(Vue) 등 대안 고려