정적 내보내기
Next.js는 정적 사이트나 단일 페이지 애플리케이션(SPA)으로 시작한 다음, 나중에 선택적으로 서버가 필요한 기능을 사용하도록 업그레이드할 수 있게 해줍니다.
next build
를 실행할 때, Next.js는 각 라우트마다 HTML 파일을 생성합니다. 엄격한 SPA를 개별 HTML 파일로 분리함으로써, Next.js는 클라이언트 측에서 불필요한 JavaScript 코드 로딩을 피할 수 있어 번들 크기를 줄이고 더 빠른 페이지 로딩을 가능하게 합니다.
Next.js가 이러한 정적 내보내기를 지원하므로, HTML/CSS/JS 정적 자산을 제공할 수 있는 모든 웹 서버에 배포하고 호스팅할 수 있습니다.
구성
정적 내보내기를 활성화하려면 next.config.js
내의 출력 모드를 변경하세요:
/**
* @type {import('next').NextConfig}
*/
const nextConfig = {
output: "export",
// 선택사항: 링크를 `/me` -> `/me/`로 변경하고 `/me.html` -> `/me/index.html`로 내보냅니다
// trailingSlash: true,
// 선택사항: 자동 `/me` -> `/me/` 변환을 방지하고 대신 `href`를 보존합니다
// skipTrailingSlashRedirect: true,
// 선택사항: 출력 디렉토리를 `out` -> `dist`로 변경합니다
// distDir: 'dist',
};
module.exports = nextConfig;
next build
를 실행한 후, Next.js는 애플리케이션의 HTML/CSS/JS 자산을 포함하는 out
폴더를 생성합니다.
지원되는 기능
Next.js의 핵심은 정적 내보내기를 지원하도록 설계되었습니다.
서버 컴포넌트
정적 내보내기를 생성하기 위해 next build
를 실행할 때, app
디렉토리 내에서 사용되는 서버 컴포넌트는 빌드 중에 실행됩니다. 이는 전통적인 정적 사이트 생성과 유사합니다.
결과 컴포넌트는 초기 페이지 로드를 위한 정적 HTML로 렌더링되고, 라우트 간 클라이언트 네비게이션을 위한 정적 페이로드로 렌더링됩니다. 동적 서버 함수를 사용하지 않는 한, 정적 내보내기를 사용할 때 서버 컴포넌트에 대한 변경은 필요하지 않습니다.
export default async function Page() {
// 이 fetch는 `next build` 중에 서버에서 실행됩니다
const res = await fetch("https://api.example.com/...");
const data = await res.json();
return <main>...</main>;
}
클라이언트 컴포넌트
클라이언트에서 데이터 페칭을 수행하려면 SWR을 사용하는 클라이언트 컴포넌트를 사용하여 요청을 메모이제이션할 수 있습니다.
"use client";
import useSWR from "swr";
const fetcher = (url: string) => fetch(url).then((r) => r.json());
export default function Page() {
const { data, error } = useSWR(
`https://jsonplaceholder.typicode.com/posts/1`,
fetcher
);
if (error) return "Failed to load";
if (!data) return "Loading...";
return data.title;
}
"use client";
import useSWR from "swr";
const fetcher = (url) => fetch(url).then((r) => r.json());
export default function Page() {
const { data, error } = useSWR(
`https://jsonplaceholder.typicode.com/posts/1`,
fetcher
);
if (error) return "Failed to load";
if (!data) return "Loading...";
return data.title;
}
라우트 전환이 클라이언트 측에서 일어나기 때문에, 이는 전통적인 SPA처럼 동작합니다. 예를 들어, 다음 인덱스 라우트는 클라이언트에서 다른 포스트로 네비게이션할 수 있게 해줍니다:
import Link from "next/link";
export default function Page() {
return (
<>
<h1>Index Page</h1>
<hr />
<ul>
<li>
<Link href="/post/1">Post 1</Link>
</li>
<li>
<Link href="/post/2">Post 2</Link>
</li>
</ul>
</>
);
}
import Link from "next/link";
export default function Page() {
return (
<>
<h1>Index Page</h1>
<p>
<Link href="/other">Other Page</Link>
</p>
</>
);
}
이미지 최적화
next/image
를 통한 이미지 최적화는 next.config.js
에서 사용자 정의 이미지 로더를 정의하여 정적 내보내기와 함께 사용할 수 있습니다. 예를 들어, Cloudinary와 같은 서비스로 이미지를 최적화할 수 있습니다:
/** @type {import('next').NextConfig} */
const nextConfig = {
output: "export",
images: {
loader: "custom",
loaderFile: "./my-loader.ts",
},
};
module.exports = nextConfig;
이 사용자 정의 로더는 원격 소스에서 이미지를 가져오는 방법을 정의합니다. 예를 들어, 다음 로더는 Cloudinary의 URL을 구성합니다:
export default function cloudinaryLoader({
src,
width,
quality,
}: {
src: string;
width: number;
quality?: number;
}) {
const params = ["f_auto", "c_limit", `w_${width}`, `q_${quality || "auto"}`];
return `https://res.cloudinary.com/demo/image/upload/${params.join(
","
)}${src}`;
}
export default function cloudinaryLoader({ src, width, quality }) {
const params = ["f_auto", "c_limit", `w_${width}`, `q_${quality || "auto"}`];
return `https://res.cloudinary.com/demo/image/upload/${params.join(
","
)}${src}`;
}
그런 다음 애플리케이션에서 next/image
를 사용할 수 있으며, Cloudinary의 이미지에 대한 상대 경로를 정의할 수 있습니다:
import Image from "next/image";
export default function Page() {
return <Image alt="turtles" src="/turtles.jpg" width={300} height={300} />;
}
import Image from "next/image";
export default function Page() {
return <Image alt="turtles" src="/turtles.jpg" width={300} height={300} />;
}
라우트 핸들러
라우트 핸들러는 next build
를 실행할 때 정적 응답을 렌더링합니다. GET
HTTP 메서드만 지원됩니다. 이는 캐시된 데이터나 캐시되지 않은 데이터에서 정적 HTML, JSON, TXT 또는 다른 파일을 생성하는 데 사용할 수 있습니다. 예를 들어:
export async function GET() {
return Response.json({ name: "Lee" });
}
export async function GET() {
return Response.json({ name: "Lee" });
}
위의 app/data.json/route.ts
파일은 next build
중에 정적 파일로 렌더링되어 { name: 'Lee' }
를 포함하는 data.json
을 생성합니다.
들어오는 요청에서 동적 값을 읽어야 하는 경우, 정적 내보내기를 사용할 수 없습니다.
브라우저 API
클라이언트 컴포넌트는 next build
중에 HTML로 사전 렌더링됩니다. Web API와 같은 window
, localStorage
, navigator
는 서버에서 사용할 수 없기 때문에, 브라우저에서 실행될 때만 이러한 API에 안전하게 접근해야 합니다. 예를 들어:
'use client';
import { useEffect } from 'react';
export default function ClientComponent() {
useEffect(() => {
// 이제 `window`에 접근할 수 있습니다
console.log(window.innerHeight);
}, [])
return ...;
}
지원되지 않는 기능
Node.js 서버가 필요하거나 빌드 프로세스 중에 계산할 수 없는 동적 로직이 필요한 기능은 지원되지 않습니다:
dynamicParams: true
를 사용하는 동적 라우트generateStaticParams()
가 없는 동적 라우트- Request에 의존하는 라우트 핸들러
- 쿠키
- 리라이트
- 리다이렉트
- 헤더
- 미들웨어
- 증분 정적 재생성
- 기본
loader
를 사용한 이미지 최적화 - 초안 모드
- 서버 액션
next dev
로 이러한 기능을 사용하려고 하면 루트 레이아웃에서 dynamic
옵션을 error
로 설정한 것과 유사한 오류가 발생합니다.
export const dynamic = "error";
배포
정적 내보내기를 사용하면 Next.js를 HTML/CSS/JS 정적 자산을 제공할 수 있는 모든 웹 서버에 배포하고 호스팅할 수 있습니다.
next build
를 실행할 때, Next.js는 out
폴더에 정적 내보내기를 생성합니다. 예를 들어, 다음과 같은 라우트가 있다고 가정해 봅시다:
/
/blog/[id]
next build
를 실행한 후, Next.js는 다음 파일들을 생성합니다:
/out/index.html
/out/404.html
/out/blog/post-1.html
/out/blog/post-2.html
Nginx와 같은 정적 호스트를 사용하는 경우, 들어오는 요청을 올바른 파일로 리라이트하도록 구성할 수 있습니다:
server {
listen 80;
server_name acme.com;
root /var/www/out;
location / {
try_files $uri $uri.html $uri/ =404;
}
# 이는 `trailingSlash: false`일 때 필요합니다.
# `trailingSlash: true`일 때는 이를 생략할 수 있습니다.
location /blog/ {
rewrite ^/blog/(.*)$ /blog/$1.html break;
}
error_page 404 /404.html;
location = /404.html {
internal;
}
}
버전 이력
버전 | 변경사항 |
---|---|
v14.0.0 | next export 가 제거되고 "output": "export" 로 대체됨 |
v13.4.0 | App Router (안정화)가 React 서버 컴포넌트 및 라우트 핸들러를 포함한 향상된 정적 내보내기 지원을 추가함 |
v13.3.0 | next export 가 더 이상 사용되지 않고 "output": "export" 로 대체됨 |