Web/Next&React

Next Js

창욱씨 2022. 12. 29. 18:31
반응형

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) 등 대안 고려
반응형