静态资源

Nuxt 提供两个放静态资源的目录,行为差别很大。选对了能省掉一大堆坏链、体积膨胀、缓存失效的问题。

public/ —— 原样托管

public/ 下的所有内容会以根路径 URL 原样 提供:

public/
├── favicon.ico
├── robots.txt
└── img/
    └── hero.png          → /img/hero.png

通过绝对路径引用:

<template>
  <img src="/img/hero.png" alt="Hero" />
</template>

适合放在 public/ 的:

  • favicon、robots.txt、OG 默认图、sitemap.xml
  • 需要稳定可预测 URL 的图片
  • 与营销 / CDN 配置共用的文件

这里的文件不会带指纹,浏览器可能永远缓存,修改后要改名或手动设置缓存头。

app/assets/ —— 由 Vite 处理

app/assets/ 下的文件会经过 Vite:压缩、打指纹、优化引用。用 ~/@/ 引用:

<template>
  <img src="~/assets/img/hero.png" alt="Hero" />
</template>

适合放在 app/assets/ 的:

  • CSS / SCSS / Less 文件
  • 需要内联或转换的 SVG
  • 需要打指纹与优化的图片
  • 随应用打包的字体

这里的文件不会在浏览器里以 /assets/… 访问,Vite 会改写成 /_nuxt/hero-abc123.png 这样的带哈希 URL。

快速对比

行为 public/ app/assets/
根 URL 访问 是(/foo.png 否(被 Vite 改写)
会压缩 / 处理
文件名带指纹
在 Vue 中如何引用 /img/foo.png ~/assets/img/foo.png
适合 稳定公共 URL 打包优化后的产物

图片进阶:<NuxtImg><NuxtPicture>

做认真的图片优化建议上 @nuxt/image

pnpm add -D @nuxt/image
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxt/image'],
  image: {
    quality: 80,
    format: ['webp', 'avif'],
  },
})

然后就可以用 <NuxtImg> / <NuxtPicture>

<template>
  <NuxtImg
    src="/img/hero.png"
    width="1200"
    height="630"
    loading="lazy"
    sizes="sm:100vw md:50vw lg:400px"
  />
</template>

它负责:

  • 响应式 srcset
  • 自动 WebP / AVIF
  • 懒加载
  • 各种 CDN Provider(Cloudinary、IPX、imgix、Vercel……)

字体

Nuxt 官方有 @nuxt/fonts 模块,能自托管 Google Fonts / 本地字体并避免 CLS:

export default defineNuxtConfig({
  modules: ['@nuxt/fonts'],
  fonts: {
    families: [
      { name: 'Inter', provider: 'google' },
    ],
  },
})

在 CSS 里直接用这个字体名即可,模块会自动注入 @font-face

想用本地字体?放进 public/fonts/ 然后:

/* app/assets/css/fonts.css */
@font-face {
  font-family: 'Display';
  src: url('/fonts/display.woff2') format('woff2');
  font-display: swap;
}

一句话指南

  • 需要稳定的公共 URL?public/
  • 需要打指纹 / 优化?app/assets/
  • 大量图片?@nuxt/image
  • 字体?@nuxt/fontspublic/fonts/

每类资源只挑一个位置,保持团队一致。

靜態資源

Nuxt 提供兩個擺放靜態資源的目錄,行為大不相同。選對可省下許多壞連結、體積膨脹、快取失效的麻煩。

public/ —— 原樣對外提供

public/ 的所有內容會以根 URL 原樣 提供:

public/
├── favicon.ico
├── robots.txt
└── img/
    └── hero.png          → /img/hero.png

以絕對路徑引用:

<template>
  <img src="/img/hero.png" alt="Hero" />
</template>

public/ 適合放:

  • favicon、robots.txt、OG 預設圖、sitemap.xml
  • 需要穩定、可預測 URL 的圖片
  • 與行銷/CDN 設定共用的檔案

此處檔案不帶指紋,瀏覽器可能永久快取,修改後需更名或自訂快取標頭。

app/assets/ —— 由 Vite 處理

app/assets/ 下的檔案會經 Vite:壓縮、加指紋、優化引用。以 ~/@/ 引用:

<template>
  <img src="~/assets/img/hero.png" alt="Hero" />
</template>

app/assets/ 適合放:

  • CSS / SCSS / Less 檔案
  • 需要內嵌或轉換的 SVG
  • 需要加指紋與優化的圖片
  • 隨應用打包的字型

此處檔案不會於瀏覽器以 /assets/… 存取,Vite 會改寫為 /_nuxt/hero-abc123.png 之類帶雜湊 URL。

快速對照

行為 public/ app/assets/
根 URL 存取 是(/foo.png 否(由 Vite 改寫)
會壓縮/處理
檔名帶指紋
於 Vue 中如何引用 /img/foo.png ~/assets/img/foo.png
適合 穩定的公共 URL 優化後的建置產物

圖片進階:<NuxtImg><NuxtPicture>

認真處理圖片建議採用 @nuxt/image

pnpm add -D @nuxt/image
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxt/image'],
  image: {
    quality: 80,
    format: ['webp', 'avif'],
  },
})

然後即可使用 <NuxtImg> / <NuxtPicture>

<template>
  <NuxtImg
    src="/img/hero.png"
    width="1200"
    height="630"
    loading="lazy"
    sizes="sm:100vw md:50vw lg:400px"
  />
</template>

它負責:

  • 響應式 srcset
  • 自動 WebP / AVIF
  • 延遲載入
  • 各大 CDN Provider(Cloudinary、IPX、imgix、Vercel……)

字型

Nuxt 官方提供 @nuxt/fonts 模組,可自代管 Google Fonts/本地字型並避免 CLS:

export default defineNuxtConfig({
  modules: ['@nuxt/fonts'],
  fonts: {
    families: [
      { name: 'Inter', provider: 'google' },
    ],
  },
})

於 CSS 直接使用該字型名稱即可,模組會自動注入 @font-face

想用本地字型?置於 public/fonts/ 後:

/* app/assets/css/fonts.css */
@font-face {
  font-family: 'Display';
  src: url('/fonts/display.woff2') format('woff2');
  font-display: swap;
}

一句話指南

  • 需要穩定公共 URL?public/
  • 需要加指紋/優化?app/assets/
  • 大量圖片?@nuxt/image
  • 字型?@nuxt/fontspublic/fonts/

每類資源只挑一個位置,讓團隊慣例一致。

Assets

Nuxt gives you two directories for assets and they behave very differently. Pick the right one and you save yourself broken URLs, big bundles, and cache-busting headaches.

public/ — served as-is

Everything inside public/ is served at the root URL without processing:

public/
├── favicon.ico
├── robots.txt
└── img/
    └── hero.png          → /img/hero.png

Reference it with an absolute path:

<template>
  <img src="/img/hero.png" alt="Hero" />
</template>

Use public/ for:

  • Favicons, robots.txt, OG defaults, sitemap.xml
  • Images you want at stable, predictable URLs
  • Files shared with marketing / CDN config

There is no fingerprinting. Browsers may cache these forever, so rename on change or set custom cache headers.

app/assets/ — processed by Vite

Everything inside app/assets/ goes through Vite: minification, hashing, optimized imports. Reference it with ~/ or @/:

<template>
  <img src="~/assets/img/hero.png" alt="Hero" />
</template>

Use app/assets/ for:

  • CSS / SCSS / Less files
  • SVGs you want inlined or transformed
  • Images you want fingerprinted and optimized
  • Fonts bundled with the app

Files here are not available at /assets/… in the browser. Vite rewrites them to a hashed URL like /_nuxt/hero-abc123.png.

Quick comparison

Behavior public/ app/assets/
Served at root URL Yes (/foo.png) No (rewritten by Vite)
Processed / minified No Yes
Filename fingerprinted No Yes
Reference from Vue /img/foo.png ~/assets/img/foo.png
Good for Stable public URLs Optimized build artifacts

Images in depth: <NuxtImg> and <NuxtPicture>

For serious image work, install @nuxt/image:

pnpm add -D @nuxt/image
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxt/image'],
  image: {
    quality: 80,
    format: ['webp', 'avif'],
  },
})

Then use <NuxtImg> / <NuxtPicture> anywhere:

<template>
  <NuxtImg
    src="/img/hero.png"
    width="1200"
    height="630"
    loading="lazy"
    sizes="sm:100vw md:50vw lg:400px"
  />
</template>

It handles:

  • Responsive srcset
  • Automatic WebP / AVIF
  • Lazy loading
  • Third-party providers (Cloudinary, IPX, imgix, Vercel, …)

Fonts

Nuxt has a dedicated @nuxt/fonts module that self-hosts Google Fonts and local fonts while preventing CLS:

export default defineNuxtConfig({
  modules: ['@nuxt/fonts'],
  fonts: {
    families: [
      { name: 'Inter', provider: 'google' },
    ],
  },
})

Then just use the family in CSS — the module injects the @font-face rules for you.

Prefer local fonts? Drop them in public/fonts/ and declare:

/* app/assets/css/fonts.css */
@font-face {
  font-family: 'Display';
  src: url('/fonts/display.woff2') format('woff2');
  font-display: swap;
}

Rule of thumb

  • Stable, public URL?public/
  • Need hashing / optimization?app/assets/
  • Images at scale?@nuxt/image
  • Fonts?@nuxt/fonts or public/fonts/

Pick once per asset type and stick to the convention.