---
title: "Assets"
description: "Nuxt offers two options for your assets."
canonical_url: "https://nuxt.com/docs/4.x/getting-started/assets"
---
# Assets

> Nuxt offers two options for your assets.

Nuxt uses two directories to handle assets like stylesheets, fonts or images.

- The [`public/`](https://nuxt.com/docs/4.x/directory-structure/public) directory content is served at the server root as-is.
- The [`app/assets/`](https://nuxt.com/docs/4.x/directory-structure/app/assets) directory contains by convention every asset that you want the build tool (Vite or webpack) to process.

## Public Directory

The [`public/`](https://nuxt.com/docs/4.x/directory-structure/public) directory is used as a public server for static assets publicly available at a defined URL of your application.

You can get a file in the [`public/`](https://nuxt.com/docs/4.x/directory-structure/public) directory from your application's code or from a browser by the root URL `/`.

### Example

For example, referencing an image file in the `public/img/` directory, available at the static URL `/img/nuxt.png`:

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

## Assets Directory

Nuxt uses [Vite](https://vite.dev/guide/assets) (default) or [webpack](https://webpack.js.org/guides/asset-management/) to build and bundle your application. The main function of these build tools is to process JavaScript files, but they can be extended through [plugins](https://vite.dev/plugins/) (for Vite) or [loaders](https://webpack.js.org/loaders/) (for webpack) to process other kinds of assets, like stylesheets, fonts or SVGs. This step transforms the original file, mainly for performance or caching purposes (such as stylesheet minification or browser cache invalidation).

By convention, Nuxt uses the [`app/assets/`](https://nuxt.com/docs/4.x/directory-structure/app/assets) directory to store these files but there is no auto-scan functionality for this directory, and you can use any other name for it.

In your application's code, you can reference a file located in the [`app/assets/`](https://nuxt.com/docs/4.x/directory-structure/app/assets) directory by using the `~/assets/` path.

### Example

For example, referencing an image file that will be processed if a build tool is configured to handle this file extension:

```vue [app/app.vue]
<template>
  <img
    src="~/assets/img/nuxt.png"
    alt="Discover Nuxt"
  >
</template>
```

<note>

Nuxt won't serve files in the [`app/assets/`](https://nuxt.com/docs/4.x/directory-structure/app/assets) directory at a static URL like `/assets/my-file.png`. If you need a static URL, use the [`public/`](https://nuxt.com/docs/4.x/getting-started/assets#public-directory) directory.

</note>

### Static vs. Dynamic `src`

When an `src` is a static string literal in your template, the build tool rewrites it into a runtime helper that resolves the final URL. A public path such as `/img/nuxt.png` is wrapped so that your [`app.baseURL`](https://nuxt.com/docs/4.x/api/nuxt-config#baseurl) is applied when the page renders, and a bundled path such as `~/assets/img/nuxt.png` additionally becomes an import that resolves to the hashed output file.

```vue
<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>
```

Because `app.baseURL` is applied at runtime, a static public path works even when the base URL is only known at deploy time (for example set via `NUXT_APP_BASE_URL`), and it works whether or not the file is processed by the build. This resolution only happens for literal paths the build tool can see.

A bound `:src` whose value is assembled at runtime is opaque to the build tool, so none of that rewriting happens. The string is used exactly as written:

```vue
<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>
```

A runtime-built public path like `/img/${name}.png` is therefore **not** prefixed with [`app.baseURL`](https://nuxt.com/docs/4.x/api/nuxt-config#baseurl). If your application is deployed below the origin root, prefix it yourself with [`useRuntimeConfig().app.baseURL`](https://nuxt.com/docs/4.x/api/composables/use-runtime-config) (for example via [`joinURL`](https://github.com/unjs/ufo#joinurl)).

The sections below cover how to handle each case when the path is only known at runtime.

#### Public Assets

If the files do not need to be processed or hashed, put them in the [`public/`](https://nuxt.com/docs/4.x/directory-structure/public) directory and reference them by URL:

```vue [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>
```

Files in `public/` keep their original filenames.

#### Bundled Assets with Vite

The approaches below are specific to Vite, Nuxt's default builder.

When the possible files are known, list their imports explicitly:

```vue [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>
```

Each import has a literal path, so Vite can find both files at build time while loading only the selected module at runtime.

When many files share a directory and extension, use a [variable dynamic import](https://vite.dev/guide/features.html#dynamic-import) instead of listing every file:

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

Only the filename can be dynamic in this example. Keeping the directory and extension in the import lets Vite find the possible files at build time.

For a broader pattern or an explicit map of available files, use [`import.meta.glob`](https://vite.dev/guide/features.html#glob-import):

```ts
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 imports are lazy by default. Add `eager: true` if the URLs must be available synchronously:

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

Every matching asset is still included in the build output. Lazy imports load each match on demand, while an eager glob loads all matches up front and can increase the initial JavaScript size or inline small assets.

<warning>

Await a lazy import before using its URL in server-rendered markup. Vite's [`new URL(..., import.meta.url)` pattern](https://vite.dev/guide/assets.html#new-url-url-import-meta-url) does not work with SSR.

</warning>


## Sitemap

See the full [sitemap](https://nuxt.com/sitemap.md) for all pages.
