---
# System prepended metadata

title: Next.js Standalone 模式下 static assets not found 問題解決
tags: [next.js]

---

# Next.js Standalone 模式下 static assets not found 問題解決

## 前言

在使用 Next.js 時，出於**輕量化部署**或**提升可攜帶性**的考量，不少人會選擇將 `output` 設定為 `standalone`。  
然而，在我第一次以 standalone 模式完成建置並啟動服務後，卻發現頁面顯示異常：樣式錯亂、資料缺失，整體看起來就像支離破碎的頁面

下意識按下 **F12** 開啟開發者工具，切換到 **Network tab**，映入眼簾的是一整片 **404 Not Found** 的請求列表，幾乎所有靜態資源都載入失敗

![A screenshot that all resource not found in standalone output](https://hackmd.io/_uploads/BycxrqIQ-l.png)

這篇文章記錄了這個問題產生的原因，以及在不使用 CDN 的前提下，如何快速解決

## 原因

在 Network tab 中檢查失敗的請求後，可以發現這些 404 幾乎全部指向同一個路徑前綴：`_next/static` 

進一步查找資料後，我在 Next.js 的一個 Pull Request 中找到了明確說明： 
[All static assets (js/css/media) in Standalone mode become 404](https://github.com/vercel/next.js/pull/72432)

其中提到：

> This minimal server does not copy the `public` or `.next/static` folders by default as these should ideally be handled by a CDN instead.

也就是說，**standalone 模式下產生的 server.js 本質上是一個「最小化的 Node.js Server」**，它的設計目標並沒有提供完整的靜態資源服務

在官方的設想中：
- `public` 與 `.next/static` **應交由 CDN 處理**
- standalone server 只負責 SSR 與 API 邏輯
- 因此建置時不會自動複製靜態資源

這也正是為什麼在 standalone 模式下直接啟動服務時，所有指向 `_next/static` 的請求都會回傳 404

不過在實際情境中，無論是本地開發階段，或第一次嘗試 standalone 部署時，大多數人**並不會事先準備 CDN**，也很容易忽略這層設計前提，於是就踩進了這個坑。

## 解決方法

### 方法一：直接複製靜態資源 (最簡單)

如果暫時不考慮使用 CDN，最直接的解決方式就是： 
**手動將 `public` 與 `.next/static` 複製到 standalone 的輸出資料夾中**。

官方文件中也明確給出了做法：

```bash
cp -r public .next/standalone/ && cp -r .next/static .next/standalone/.next/
```

### 方法二：使用 CDN（官方推薦方向）

既然官方明確提到，理想情況下靜態資源應由 CDN 提供，那也可以順著這個設計方向來進行設定。

一個直覺上的疑問是：
Next.js 預設請求靜態資源的路徑為：`http://localhost:3000/_next/static/*`

如果改用 CDN，總不可能仍然從相同的網域載入吧？

這點其實在官方文件的 [next.config.js assetPrefix - Set up a CDN](https://nextjs.org/docs/app/api-reference/config/next-config-js/assetPrefix#set-up-a-cdn) 有提及

透過設定 assetPrefix，可以讓 Next.js 在 production 模式下，將所有靜態資源請求導向指定的 CDN 網域，例如：
```js=
// @ts-check
import { PHASE_DEVELOPMENT_SERVER } from 'next/constants'
 
export default (phase) => {
  const isDev = phase === PHASE_DEVELOPMENT_SERVER
  /**
   * @type {import('next').NextConfig}
   */
  const nextConfig = {
    assetPrefix: isDev ? undefined : 'https://cdn.mydomain.com',
  }
  return nextConfig
}
```

如此一來，就會讓原本的 request:
```text
/_next/static/chunks/4b9b41aaa062cbbfeff4add70f256968c51ece5d.4d708494b3aed70c04f
```
變成:
```text
https://cdn.mydomain.com/_next/static/chunks/4b9b41aaa062cbbfeff4add70f256968c51ece5d.4d708494b3aed70c04f0.js
```

## 參考資料

- [All static assets (js/css/media) in Standalone mode become 404](https://github.com/vercel/next.js/pull/72432)
- [docs(output): update standalone docs with cp and start commands](https://github.com/vercel/next.js/pull/72432)
- [next.config.js assetPrefix - Set up a CDN](https://nextjs.org/docs/app/api-reference/config/next-config-js/assetPrefix#set-up-a-cdn)



