Skip to content

资源管理

Nuxt 中有两种资源目录:app/assets/public/,它们的处理方式截然不同。理解两者区别,才能正确组织项目文件。

assets/ vs public/ ——核心区别

特性app/assets/public/
构建处理✅ Vite 处理(压缩、哈希)❌ 原样复制到输出目录
文件名哈希✅ 内容哈希(logo.a1b2c3.png❌ 保持原名
Tree-shaking✅ 未引用不打包❌ 全部复制
引用方式~/assets/.../...(绝对路径)
版本控制✅ 哈希自动更新缓存❌ 手动管理缓存
适用CSS、需要优化的图片、SVGfavicon、robots.txt

一句话总结

assets/ 是"需要处理的资源",public/ 是"原样使用的资源"。

选择原则

  • 需要优化/哈希/Tree-shaking → assets/
  • 必须保持固定路径 → public/

assets/ 目录

存放需要构建工具处理的资源,会被 Vite 处理(压缩、哈希、Tree-shaking)。

引用方式

vue
<template>
  <!-- 模板中:使用 ~/ 别名 -->
  <img src="~/assets/images/logo.png" alt="Logo" />
</template>

<style scoped>
/* CSS 中:使用 ~/ 别名 */
.hero {
  background-image: url('~/assets/images/hero-bg.jpg');
}
</style>

<script setup>
// JS 中:使用 ~/ 或 @/ 别名
const logoUrl = '@/assets/images/logo.png'
</script>

路径别名说明

别名指向适用场景
~/项目根目录推荐,最常用
@/项目根目录~/ 等价
~~/项目根目录Nuxt 4 中与 ~/ 相同
#imports自动导入目录用于显式导入自动导入的模块

适合放 assets/ 的文件

文件类型原因示例
CSS / SCSS需要编译处理main.css_variables.scss
需要优化的图片需要压缩、哈希产品图片、背景图
SVG 图标需要 Tree-shaking图标文件
字体文件需要哈希避免缓存问题.woff2 字体

assets 的构建处理流程

text
源码中引用 ~/assets/images/logo.png

Vite 构建时处理

1. 如果是图片:压缩 → 添加内容哈希 → 输出为 logo.a1b2c3.png
2. 如果是 CSS:编译 → 添加内容哈希 → 输出为 main.d4e5f6.css
3. 如果是 SVG:可以内联或作为文件引用

HTML/CSS 中引用路径自动替换为构建后的路径

内容哈希的好处

文件内容变化时哈希会变,浏览器会自动获取新版本。内容不变时哈希不变,浏览器使用缓存。这是长期缓存策略的基础。

public/ 目录

存放不需要处理的静态资源,构建时原样复制到输出目录。

引用方式

vue
<template>
  <!-- 绝对路径,不需要别名 -->
  <img src="/images/og-image.png" alt="OG" />
  <link rel="icon" href="/favicon.svg" />
</template>

适合放 public/ 的文件

文件类型原因示例
favicon必须在根路径可访问favicon.icofavicon.svg
robots.txt搜索引擎需要固定路径robots.txt
sitemap.xml搜索引擎需要固定路径sitemap.xml
社交分享图片Open Graph 需要绝对 URLog-image.png
不需要处理的字体已优化好的字体已子集化的字体
manifest.jsonPWA 需要manifest.json

INFO

public/ 中的文件会全部复制到输出目录 即使没有被引用。不要把大量无用文件放在 public/ 中,会增大部署包体积

动态图片引用

动态引用 assets 中的图片需要特殊处理,因为 Vite 需要在构建时分析路径:

方式 1:new URL(推荐)

vue
<script setup>
const props = defineProps<{ name: string }>()

// ✅ 使用 new URL——Vite 能正确分析
const getImageUrl = (name: string) => {
  return new URL(`../assets/images/${name}.png`, import.meta.url).href
}
</script>

<template>
  <img :src="getImageUrl(name)" :alt="name" />
</template>

new URL 的原理

import.meta.url 是当前模块的 URL,Vite 在构建时会将相对路径解析为正确的资源路径。这是 Vite 官方推荐的动态导入方式。

方式 2:import.meta.glob

vue
<script setup>
// 一次性导入所有图片
const images = import.meta.glob('~/assets/images/*.png', {
  eager: true,
  import: 'default',
})

// 使用
const getImageUrl = (name: string) => {
  return images[`/assets/images/${name}.png`]
}
</script>

import.meta.glob 的优缺点

  • 优点:一次性处理所有图片,代码简洁
  • 缺点:所有图片都会被打包(即使没用到),不适合大量图片

适用场景

图片数量少(< 20)且大多数都会用到的情况。

方式 3:静态映射

vue
<script setup>
// 图片数量少时,手动映射最安全
const imageMap: Record<string, string> = {
  logo: '~/assets/images/logo.png',
  hero: '~/assets/images/hero.png',
  banner: '~/assets/images/banner.png',
}

const props = defineProps<{ type: string }>()
const imgSrc = computed(() => imageMap[props.type])
</script>

方式 4:放在 public/ 目录

vue
<script setup>
// 如果图片在 public/ 中,直接拼接路径
const imgSrc = computed(() => `/images/${name}.png`)
</script>

INFO

放在 public/ 的缺点:不会经过 Vite 优化(无哈希、无压缩) 不适合大量图片

服务端静态资源

server/ 目录中引用静态资源:

ts
// server/api/download.ts
export default defineEventHandler((event) => {
  // 读取 public/ 中的文件
  const filePath = `${process.cwd()}/public/files/document.pdf`
  return sendStream(event, createReadStream(filePath))
})

服务端不能使用 ~/assets/ 路径

assets 目录是 Vite 的概念,只在客户端构建时存在。服务端只能访问 public/ 目录或文件系统中的文件。

用户上传文件

用户上传的文件不应放在 public/assets/,因为:

  1. 这些目录在构建时处理,运行时写入的文件不会自动生效
  2. 服务器重启/重新部署后文件会丢失

推荐方案

ts
// server/api/upload.ts
export default defineEventHandler(async (event) => {
  const files = await readMultipartFormData(event)

  // 方案 1:使用对象存储(推荐生产环境)
  // await uploadToOSS(file)

  // 方案 2:使用 Nitro 存储层
  await useStorage('uploads').setItem(file.filename, file.data)

  // 方案 3:写入服务端临时目录(开发用)
  // const filePath = path.join('/tmp', file.filename)
  // await fs.writeFile(filePath, file.data)
})

生产环境推荐对象存储

(如阿里云 OSS、AWS S3),具有高可用、CDN 加速、权限控制等优势。

资源引用常见问题

1. 为什么图片不显示?

vue
<!-- ❌ 错误:动态路径不会被 Vite 处理 -->
<img :src="`~/assets/images/${name}.png`" />

<!-- ✅ 正确:使用 new URL 或放在 public/ -->
<img :src="getImageUrl(name)" />

2. CSS 中的背景图不生效?

css
/* ❌ 错误:路径不正确 */
.hero {
  background-image: url('assets/images/hero.jpg');
}

/* ✅ 正确:使用 ~/ 别名 */
.hero {
  background-image: url('~/assets/images/hero.jpg');
}

3. SVG 如何使用?

vue
<!-- 方式 1:作为图片(不能修改颜色) -->
<img src="~/assets/icons/logo.svg" alt="Logo" />

<!-- 方式 2:内联 SVG(可以修改颜色,推荐) -->
<!-- 安装 vite-svg-loader 或使用 @nuxt/icon -->
<Icon name="logo" />

<!-- 方式 3:CSS 背景图 -->
<style scoped>
.icon {
  background-image: url('~/assets/icons/logo.svg');
}
</style>

知识脉络

text
CSS 与 SCSS → 你在这里:资源管理

                ├─→ 下一步:图片优化

                └─→ 相关:目录结构 → public 目录

基于 Nuxt 4 官方文档整理编写