Ресурсы (Assets)

В Nuxt для ресурсов предусмотрены два варианта размещения.

Nuxt использует две директории для ресурсов: стилей, шрифтов и изображений.

  • Содержимое public/ отдаётся с корня сервера как есть.
  • В app/assets/ по соглашению лежат ресурсы, которые должен обрабатывать сборщик (Vite или webpack).

Директория Public

Директория public/ используется как публичный каталог статики, доступной по фиксированному URL приложения.

Файлы из public/ доступны в коде приложения и в браузере по корневому URL /.

Пример

Изображение в public/img/ доступно по статическому URL /img/nuxt.png:

app/app.vue
<template>
  <img
    src="/img/nuxt.png"
    alt="Discover Nuxt"
  >
</template>

Директория Assets

Nuxt использует Vite (по умолчанию) или webpack для сборки приложения. Эти инструменты в первую очередь обрабатывают JavaScript, но через плагины (Vite) или лоадеры (webpack) можно обрабатывать и другие ресурсы — стили, шрифты, SVG. На этом этапе файлы преобразуются (минификация, инвалидация кэша и т.п.).

По соглашению для таких файлов используется директория app/assets/; автоматического сканирования этой папки нет, имя можно изменить.

В коде приложения файлы из app/assets/ подключаются через путь ~/assets/.

Пример

Подключение изображения, которое будет обработано сборщиком, если для данного расширения настроена обработка:

app/app.vue
<template>
  <img
    src="~/assets/img/nuxt.png"
    alt="Discover Nuxt"
  >
</template>
Nuxt не раздаёт файлы из app/assets/ по статическому URL вроде /assets/my-file.png. Для статического URL используйте директорию public/.

Статический и динамический src

Если в шаблоне src — строковый литерал, сборщик переписывает его в рантайм-хелпер, который вычисляет итоговый URL. Публичный путь вроде /img/nuxt.png оборачивается так, чтобы при рендере применился app.baseURL. Путь из бандла вроде ~/assets/img/nuxt.png дополнительно превращается в import с хэшированным выходным файлом.

<template>
  <!-- Static paths are rewritten: app.baseURL is applied at runtime, and the bundled file is hashed. -->
  <img src="/img/nuxt.png">
  <img src="~/assets/img/nuxt.png">
</template>

app.baseURL применяется в рантайме, поэтому статический публичный путь работает, даже если base URL известен только при деплое (например через NUXT_APP_BASE_URL), и не зависит от того, обрабатывает ли сборка файл. Переписывание срабатывает только для литеральных путей, которые сборщик видит на этапе сборки.

Привязанный :src, значение которого собирается в рантайме, для сборщика непрозрачен: переписывания не будет, строка используется как есть:

<template>
  <!-- This does not work: the path is built at runtime, so Vite never sees it as an import. -->
  <img :src="`~/assets/img/${name}.png`">
</template>

Публичный путь, собранный в рантайме, например /img/${name}.png, не получает префикс app.baseURL. Если приложение развёрнуто ниже корня origin, добавьте префикс сами через useRuntimeConfig().app.baseURL (например через joinURL).

Ниже — как обрабатывать каждый случай, когда путь известен только в рантайме.

Публичные ресурсы

Если файлы не нужно обрабатывать или хэшировать, положите их в public/ и ссылайтесь по URL:

app/app.vue
<script setup lang="ts">
const props = defineProps<{
  name: string
}>()

const imageUrl = computed(() => `/img/${props.name}.png`)
</script>

<template>
  <img
    :src="imageUrl"
    :alt="props.name"
  >
</template>

Файлы в public/ сохраняют исходные имена.

Ресурсы в бандле с Vite

Подходы ниже относятся к Vite, сборщику Nuxt по умолчанию.

Если набор файлов известен заранее, перечислите import явно:

app/app.vue
<script setup lang="ts">
const props = defineProps<{
  theme: 'light' | 'dark'
}>()

const logos = {
  light: () => import('./assets/img/logo-light.png?url'),
  dark: () => import('./assets/img/logo-dark.png?url'),
}

const logoUrl = (await logos[props.theme]()).default
</script>

<template>
  <img
    :src="logoUrl"
    alt="Nuxt"
  >
</template>

У каждого import литеральный путь: Vite находит оба файла при сборке, а в рантайме загружает только выбранный модуль.

Если много файлов в одной директории с одним расширением, используйте переменный динамический import вместо перечисления каждого файла:

async function getImageUrl (name: string) {
  const image = await import(`./assets/img/${name}.png?url`)
  return image.default
}

В этом примере динамичным может быть только имя файла. Директорию и расширение оставьте в import, чтобы Vite нашёл возможные файлы при сборке.

Для более широкого шаблона или явной карты доступных файлов используйте import.meta.glob:

const images = import.meta.glob<string>('./assets/img/*.{png,jpg,svg}', {
  query: '?url',
  import: 'default',
})

async function getImageUrl (name: string) {
  const load = images[`./assets/img/${name}.png`]

  if (!load) {
    throw new Error(`Unknown image: ${name}`)
  }

  return await load()
}

Glob-import по умолчанию ленивый. Добавьте eager: true, если URL нужны синхронно:

const images = import.meta.glob<string>('./assets/img/*.{png,jpg,svg}', {
  query: '?url',
  import: 'default',
  eager: true,
})

Все подходящие ресурсы всё равно попадают в сборку. Ленивые import загружают совпадения по запросу; eager glob подтягивает всё сразу и может увеличить начальный размер JavaScript или инлайнить мелкие ресурсы.

Перед использованием URL из ленивого import в серверной разметке дождитесь его через await. Паттерн Vite new URL(..., import.meta.url) не работает с SSR.