[Web] Next.js 문법 — 라우팅, 서버/클라이언트 컴포넌트, CSR/SSR/SSG/ISR
by 우와한개발자
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>
);
}
블로그의 정보
우와한개발자 님의 블로그
우와한개발자