우와한 개발자

[Web] Next.js 문법 — 라우팅, 서버/클라이언트 컴포넌트, CSR/SSR/SSG/ISR

by 우와한개발자

Web

1. Next.js란?

  • React 기반의 풀스택 웹 프레임워크
  • React가 UI를 그리는 라이브러리라면, Next.js는 라우팅, SSR, API, 빌드 등 앱 전체를 관리한다
  • 파일 기반 라우팅, 서버사이드 렌더링, API Route를 내장으로 제공한다

 

1) React vs Next.js

구분 React Next.js
정의 UI를 만드는 라이브러리 React 기반 프레임워크
역할 화면을 그리는 도구 라우팅, SSR, API, 빌드 등 앱 전체 관리
렌더링 클라이언트 렌더링(CSR) 서버/클라이언트 모두 가능
라우팅 react-router-dom 직접 설치 파일 기반 라우팅 내장
데이터 패칭 fetch(), SWR, React Query 직접 사용 내장 fetch(), 서버 컴포넌트 활용
설정 Webpack, Babel 등 직접 구성 자동 구성

 

2. 설치 및 프로젝트 생성

1) 사전 준비

  • Node.js 18 이상 설치 필요
  • 설치 확인
node -v
npm -v

 

2) 프로젝트 생성

npx create-next-app@latest 프로젝트명
  • 실행하면 아래 옵션을 물어본다.
Would you like to use TypeScript? → No
Would you like to use ESLint? → Yes
Would you like to use Tailwind CSS? → No
Would you like to use `src/` directory? → No
Would you like to use App Router? → Yes   ← 반드시 Yes
Would you like to customize the default import alias? → No

 

3) 개발 서버 실행

cd 프로젝트명
npm run dev
  • 브라우저에서 http://localhost:3000 접속

 

4) 주요 명령어

명령어 설명
npm run dev 개발 서버 실행
npm run build 프로덕션 빌드
npm run start 빌드된 앱 실행

 

3. 폴더 구조와 라우팅

  • Next.js는 파일 위치가 곧 URL 경로가 되는 파일 기반 라우팅을 사용한다
  • app/ 폴더 안에 page.js 파일을 만들면 자동으로 해당 경로의 페이지가 된다
  • 폴더를 만들면 URL 경로가 생기고, 그 안에 page.js를 만들면 페이지가 렌더링된다
app/
├─ page.js              # '/' 경로
├─ about/
│   └─ page.js          # '/about' 경로
├─ blog/
│   └─ [id]/
│       └─ page.js      # '/blog/123' 동적 라우트
└─ api/
    └─ hello/
        └─ route.js     # '/api/hello' API 엔드포인트

 

1) 기본 페이지

  • export default로 React 컴포넌트를 내보내면 자동으로 페이지가 된다.
  • 별도 라우터 설정 없이 파일만 만들면 된다.
// app/page.js → '/' 경로
export default function Home() {
  return <h1>Hello Next.js!</h1>;
}

 

2) 동적 라우트

  • URL에 변수가 필요할 때 폴더명을 [변수명]으로 만든다.
  • 예를 들어 블로그 게시글처럼 ID마다 다른 페이지가 필요할 때 사용한다.
app/blog/[id]/page.js → /blog/1, /blog/2, /blog/123 모두 이 파일이 처리
// app/blog/[id]/page.js
export default async function BlogPost({ params }) {
  const { id } = await params;  // URL에서 id 값 추출
  return <h1>게시글 번호: {id}</h1>;
}

 

3) API Route

  • Next.js에서 백엔드 API를 별도 서버 없이 만들 수 있는 기능이다.
  • app/api/ 폴더 안에 route.js 파일을 만들면 자동으로 API 엔드포인트가 된다.
  • React 컴포넌트가 아니라 서버에서 실행되는 함수다.
app/api/hello/route.js → GET /api/hello
app/api/user/route.js  → GET /api/user

(1) 기본 구조

  • HTTP 메서드 이름과 동일한 함수를 export하면 해당 메서드 요청을 처리한다.
// app/api/hello/route.js
export async function GET() {
  return Response.json({ message: 'Hello API!' });
}
// GET /api/hello 요청 시 → { "message": "Hello API!" } 반환

 

(2) HTTP 메서드별 처리

export async function GET() { ... }     // 데이터 조회
export async function POST(req) { ... } // 데이터 생성
export async function PUT(req) { ... }  // 데이터 수정
export async function DELETE() { ... }  // 데이터 삭제

 

(3) 요청 데이터 받기

  • Query String — URL에 ?key=value 형태로 전달하는 방식
// GET /api/hello?name=홍길동
export async function GET(req) {
  const { searchParams } = new URL(req.url);
  const name = searchParams.get('name');  // "홍길동"
  return Response.json({ message: `안녕하세요 ${name}님` });
}
  • Request Body — POST 요청 시 JSON으로 데이터를 전달하는 방식
// POST /api/user
export async function POST(req) {
  const body = await req.json();  // { name: "홍길동", age: 30 }
  return Response.json({ success: true, data: body });
}
  • Path Variable — URL 경로에 변수를 포함하는 방식
// app/api/user/[id]/route.js
// GET /api/user/123
export async function GET(req, { params }) {
  const { id } = await params;  // "123"
  return Response.json({ userId: id });
}

 

(4) 응답 상태코드 설정

export async function GET() {
  return Response.json(
    { message: "Not Found" },
    { status: 404 }
  );
}

 

4) 페이지 이동 (Navigation)

  • React에서는 react-router-dom을 직접 설치해서 사용하지만, Next.js는 Link 컴포넌트와 useRouter 훅을 내장으로 제공한다.

(1) Link 컴포넌트

  • 일반적인 페이지 이동. <a> 태그 대신 사용한다.
import Link from 'next/link';

export default function Nav() {
  return <Link href="/about">About으로 이동</Link>;
}

 

(2) useRouter 훅

  • 버튼 클릭이나 조건에 따라 코드로 페이지를 이동할 때 사용한다.
  • 클라이언트 컴포넌트에서만 사용 가능하다.
'use client';
import { useRouter } from 'next/navigation';

export default function MyButton() {
  const router = useRouter();
  return (
    <button onClick={() => router.push('/about')}>
      About으로 이동
    </button>
  );
}

 

4. 서버 컴포넌트 vs 클라이언트 컴포넌트

  • Next.js 13부터 컴포넌트는 기본적으로 서버에서 실행된다.
  • 서버에서 실행하면 데이터베이스나 외부 API에 직접 접근할 수 있고, 브라우저로 전송되는 JavaScript 양도 줄어든다.
  • 단, 브라우저 API(useState, useEffect, 클릭 이벤트 등)는 서버에서 실행할 수 없기 때문에 그런 경우에만 클라이언트 컴포넌트로 전환한다.
구분 서버 컴포넌트 클라이언트 컴포넌트
선언 없음 (기본값) 파일 맨 위에 'use client'
실행 위치 서버 브라우저
데이터 패칭 async/await 직접 사용 가능 useEffect로 처리
useState/useEffect 사용 불가 사용 가능
이벤트 핸들러 사용 불가 사용 가능

 

1) 서버 컴포넌트

  • 기본값. 별도 선언 없이 사용한다
  • async/await으로 데이터를 직접 가져올 수 있어서 별도 상태 관리가 필요 없다
  • useState, useEffect, 클릭 이벤트 등 브라우저 API는 사용할 수 없다
// 서버에서 실행 → API 직접 호출 가능
export default async function Page() {
  const data = await fetch('https://api.example.com/posts').then(r => r.json());
  return <pre>{JSON.stringify(data, null, 2)}</pre>;
}

 

2) 클라이언트 컴포넌트

  • 파일 맨 위에 'use client'를 선언한다
  • useState, useEffect, 클릭 이벤트 등 브라우저에서 동작하는 기능이 필요할 때 사용한다
'use client';
import { useState } from 'react';

export default function Counter() {
  const [n, setN] = useState(0);
  return <button onClick={() => setN(n+1)}>Clicked {n}</button>;
}

 

5. 렌더링 방식 — CSR / SSR / SSG / ISR

  • Next.js는 페이지마다 다른 렌더링 방식을 선택할 수 있다.
  • fetch() 옵션 하나로 방식이 결정된다.
렌더링 방식 렌더 위치 데이터 시점 특징
CSR 브라우저 실시간 SPA (React 기본)
SSR 서버 요청 시 SEO 좋음
SSG 서버 빌드 시점 속도 최고
ISR 서버 (백그라운드) 일정 주기 빠르고 갱신 가능

 

1) CSR (Client-Side Rendering)

  • 서버는 빈 HTML만 보내고, 브라우저가 JavaScript를 실행해서 데이터를 가져와 화면을 그리는 방식
  • 'use client' 선언 + useEffect로 구현한다
  • 초기 로딩 시 Loading... 화면이 보이고, 데이터가 오면 렌더링된다
  • 새로고침할 때마다 최신 데이터를 가져온다
'use client';
import { useEffect, useState } from 'react';

export default function CSRPage() {
  const [data, setData] = useState();

  useEffect(() => {
    fetch('/api/data').then(r => r.json()).then(setData);
  }, []);

  return (
    <main>
      <h1>CSR Example</h1>
      {data ? <p>Time: {data.time}</p> : <p>Loading...</p>}
    </main>
  );
}

 

2) SSR (Server-Side Rendering)

  • 요청이 들어올 때마다 서버에서 데이터를 가져와 완성된 HTML을 브라우저에 전달하는 방식
  • 페이지를 열 때마다 최신 데이터가 반영된다
  • cache: 'no-store'로 캐시를 비활성화해서 매 요청마다 새 데이터를 가져온다
  • SEO가 중요하거나 실시간 데이터가 필요한 페이지에 적합하다
export default async function SSRPage() {
  const res = await fetch('<http://localhost:3000/api/data>', { cache: 'no-store' });
  const data = await res.json();
  return (
    <main>
      <h1>SSR Example</h1>
      <p>Time: {data.time}</p>
    </main>
  );
}

 

3) SSG (Static Site Generation)

  • 빌드 시 한 번만 데이터를 가져와 정적 HTML로 만들어두는 방식
  • 서버가 매번 데이터를 가져오지 않아도 되기 때문에 응답 속도가 가장 빠르다
  • cache: 'force-cache'로 캐시를 강제 사용한다
  • 새로고침해도 값이 바뀌지 않으면 정상 동작이다
  • 자주 바뀌지 않는 콘텐츠(공지사항, 소개 페이지 등)에 적합하다
export default async function SSGPage() {
  const res = await fetch('<http://localhost:3000/api/data>', { cache: 'force-cache' });
  const data = await res.json();
  return (
    <main>
      <h1>SSG Example</h1>
      <p>Time: {data.time}</p>
    </main>
  );
}

 

4) ISR (Incremental Static Regeneration)

  • SSG처럼 정적으로 빌드하되, 일정 시간마다 백그라운드에서 데이터를 갱신하는 방식
  • SSG의 빠른 속도와 SSR의 최신 데이터를 모두 얻을 수 있다
  • next: { revalidate: 초 }로 갱신 주기를 설정한다
  • 예를 들어 revalidate: 10이면 10초마다 새 데이터로 페이지를 다시 만든다
export default async function ISRPage() {
  const res = await fetch('<http://localhost:3000/api/data>', { next: { revalidate: 10 } });
  const data = await res.json();
  return (
    <main>
      <h1>ISR Example (revalidate: 10s)</h1>
      <p>Time: {data.time}</p>
    </main>
  );
}

블로그의 정보

우와한개발자 님의 블로그

우와한개발자

활동하기