@jialin.huang
FRONT-ENDBACK-ENDNETWORK, HTTPOS, COMPUTERCLOUD, AWS, Docker
To live is to risk it all Otherwise you are just an inert chunk of randomly assembled molecules drifting wherever the Universe blows you

© 2024 jialin00.com

Original content since 2022

back
RSS

Vue Nuxt Dynamic Path Resolution Issue

<img :src="'~assets/cat.png'" />

this one doesn’t work

When I wanted to assign an image path through a variable for the <img> element, I noticed some inconveniences. At first, I thought about simply putting the images into the public/ folder to solve the issue.

But after encountering the same problem a couple of times, I started to wonder why this was happening. That’s why I decided to write this article.

The Crux

the project structure:

└──assets
	  ├── cat.png
    └── dog.png
└── public
    ├── public-cat.png
    └── public-dog.png
..
└── utils

The configuration in .nuxt/tsconfig.json allows ~ to reach the project root directory:

{
  "compilerOptions": {
    "paths": {
	    // ...
      "~": [
        ".."
      ],
      "~/*": [
        "../*"
      ],
	    // ...

🟢 Both work:

<img src="~assets/cat.png" />

<img src="/public-cat.png" />

❌ Dynamic: When passing variables into :src, it doesn't work for assets/

<!-- failed -->
<img :src="'~assets/cat.png'" />


<!-- works -->
<img :src="'/public-cat.png'" />

How public/ Works

Resources under public/ are distributed to the root directory and can be accessed directly at runtime.

Static

No issues here, as it's copied to the output directory at build time.

Dynamic :src

This method doesn't resolve the path at build time, but since we're directly accessing the exposed public/, it's not a problem - we can still retrieve it at runtime.

How assets/ Works

Static

Resources are found at build time without issue. Webpack or Vite will compile them into accessible resource paths, usually becoming something like /_nuxt/assets/cat.png, with the _nuxt prefix added.

Dynamic :src

  • Build time skips this type. When the bundling tool sees <img :src="'~assets/cat.png'" />, it thinks

    "This is decided at runtime, so I'll ignore it."

  • At runtime, it starts to see :src and tries to access the path, but since ~ is determined at compile time, the runtime environment doesn't understand ~. This makes sense, as if it could understand these path rules, it would be able to access resources outside of public/, which sounds quite inappropriate.

Solutions

First import, explicitly indicating the resource

// way 1: import
import catImg from '@/assets/cat.png'

// way 2: URL
const catImg = new URL('@/assets/cat.png', import.meta.url).href

// then
<img :src="catImg" />
// static path with dynamic importing.
const catImg = await import('@/assets/cat.png?url')
// real dynamic
const catImg = await import(`@/assets/${imageName.value}?url`)

<img :src="catImg.default" />

Move directly to public/

The simplest solution.

Thought

Initially, I thought the difference was caused by the packaging method. My first approach was to switch between AOT/JIT modes to see if it could make a difference.

AOT is easy to understand - it optimizes packaging as much as possible, while JIT does just-in-time compilation at runtime.

This is the usual mode:

Nuxt (manager)
  ↓
strategy (AOT or JIT?)
  ↓
tool? (Webpack/Vite)
  ↓
do the build job

But in fact, it's just the difference in behavior between assets/ & public/, and Vue's dynamic binding :src is processed at runtime, so it can't recognize those compile-time path resolution rules.

EOF