靜態匯出的 Next.js 網站就是一個資料夾裡的 HTML、CSS、JavaScript 和靜態資源。你可以把它放到任何靜態託管上——Cloudflare Pages、GitHub Pages、S3、Netlify 或一臺 Nginx 伺服器——不需要執行、維護或付費的 Node.js 伺服器。這個網站就是這樣建置的。
本指南介紹靜態匯出在 App Router 中的工作方式、你會失去什麼,以及每個限制的繞過方法。
開啟靜態匯出
從 Next.js 14 開始,next export 命令已被移除,靜態匯出改為配置項:
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
// Optional: /about → /about/index.html instead of /about.html
trailingSlash: true,
images: { unoptimized: true },
}
export default nextConfig現在 next build 會把網站輸出到 out/ 目錄。用任意靜態伺服器預覽:
npx serve out可以用的功能
比很多人想象的多:
- 服務端元件(Server Components)。 它們在建置時執行一次。獲取資料、讀取檔案、呼叫 API——結果直接寫進 HTML。
- 客戶端元件(Client Components)、hooks、context 以及所有客戶端庫。
- 動態路由,前提是用
generateStaticParams列出所有路徑。 - 只有
GET的 Route Handlers,會生成靜態檔案(很適合feed.json、sitemap.xml、robots.txt)。 - Metadata API:標題、描述、Open Graph、canonical 連結。
- 使用
<Link>的客戶端導航和預載入。
不能用的功能,以及替代方案
| 功能 | 失效原因 | 替代方案 |
|---|---|---|
| 預設圖片優化 | 需要伺服器 | images.unoptimized: true、預先裁好尺寸,或自訂 loader(Cloudinary、imgix、Cloudflare Images) |
沒有 generateStaticParams 的動態路由 |
頁面必須在建置時確定 | 列出所有參數,並設定 dynamicParams = false |
cookies()、headers() 等請求時資料 |
執行時沒有請求 | 在客戶端讀取,或把邏輯移到 API |
| Server Actions | 需要伺服器 | POST 到無伺服器函式(Workers、Lambda) |
| Middleware | 需要伺服器 | 託管平台層面的規則:_redirects、_headers、邊緣函式 |
配置中的 redirects、rewrites、headers |
由 Next 伺服器執行 | 同上:在託管平台配置 |
ISR / revalidate |
沒有伺服器來重新生成 | 內容變化時重新建置(Git 推送或 webhook) |
| 內建 i18n 路由 | Pages Router 的功能 | 使用語言路由段:app/[lang]/... 配合 generateStaticParams |
正確處理動態路由
每個動態路由段都需要在建置時提供所有取值:
import { getAllPosts, getPost } from '@/lib/blog'
import { notFound } from 'next/navigation'
export const dynamicParams = false // unknown slugs are a 404, not a runtime render
export async function generateStaticParams() {
const posts = await getAllPosts()
return posts.map((post) => ({ slug: post.slug }))
}
export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params
const post = await getPost(slug)
if (!post) notFound()
return <article dangerouslySetInnerHTML={{ __html: post.html }} />
}注意:在 Next.js 15 中 params 是一個 Promise,需要 await。
一個坑:如果 generateStaticParams 返回空陣列,建置會失敗,因為這個路由不會生成任何頁面。如果集合可能為空(比如剛建的部落格),就返回一個佔位參數,讓它渲染 404。
圖片
有三種合理的選擇:
unoptimized: true,自己匯出合適尺寸的圖片(WebP 或 AVIF,顯示寬度的 1–2 倍)。只要你足夠自律,這種方式簡單又快。- 自訂 loader,指向圖片 CDN,按需返回不同尺寸:
export default function loader({ src, width, quality }) {
return `https://images.example.com/${src}?w=${width}&q=${quality || 75}`
}const nextConfig = {
output: 'export',
images: { loader: 'custom', loaderFile: './image-loader.js' },
}- 普通
<img>,配合srcset、loading="lazy"以及明確的width/height,避免佈局偏移。
表單和其他服務端功能
靜態網站依然可以有表單、搜尋和登入——只是服務端部分放在別處。常見做法是一個小型無伺服器函式,頁面透過 fetch 呼叫它。我把本站使用的方案寫在了用 Cloudflare Workers 和 D1 搭建無伺服器聯絡表單。
重定向和響應頭
在託管平台配置。在 Cloudflare Pages 和 Netlify 上,public/ 裡的 _redirects 檔案會被複制到 out/:
/old-page /new-page 301
/blog/* /journal/:splat 301用 _headers 配置快取和安全響應頭:
/_next/static/*
Cache-Control: public, max-age=31536000, immutable沒有伺服器如何更新內容
因為沒有 ISR,內容變化需要重新建置。這其實沒有聽起來那麼麻煩:把託管平台連線到 Git 儲存庫,每次推送都會在一兩分鐘內自動建置和部署。如果內容放在 CMS 裡,把 CMS 的 webhook 指向託管平台的部署鉤子即可。對部落格來說,把文章作為 Markdown 放在儲存庫裡是最簡單的方案——見不用 CMS,在 Next.js 中搭建 Markdown 部落格。
什麼時候不適合靜態匯出
- 依賴登入使用者、需要在服務端渲染的頁面。
- 成千上萬、每幾分鐘就變化的頁面(建置時間隨頁面數增長)。
- 確實需要請求時渲染的場景,比如個性化定價。
對於官網、作品集、文件、部落格和落地頁,靜態匯出通常是最快、最便宜也最穩定的方案。
檢查清單
-
next.config中設定output: 'export' - 每個動態路由都有
generateStaticParams和dynamicParams = false - 圖片已處理(unoptimized、自訂 loader 或
<img>) - 沒有使用
cookies()、headers()、Server Actions 或 middleware - 重定向和響應頭在託管平台配置
- 部署由 Git 推送或 CMS webhook 觸發
- 首次部署前在本地預覽過
out/
下一步:確保搜尋引擎能理解你匯出的頁面——Next.js App Router 技術 SEO 清單。

