😅

[Vue.js]imgタグでパスを動的に変更した時、デプロイ後に表示されない問題

2024/01/31に公開

imgタグでsrcを動的に変更し表示するプログラムを実装した際に、
ローカル環境では問題なく表示されるのにデプロイ後の環境では表示されないということがありました。

対応に困ったので、備忘録として残します。

結論:解決方法

結論から言うと、以下のようにURLコンストラクター new URL()を使うことで解決できました。

sample.js
const getImageUrl = (fileName: string) => {
  return new URL(`../assets/images/${fileName}.png`, import.meta.url).href
}

第一引数に画像のパス、第二引数にimport.meta.urlを入れます。
import.meta.urlはモジュール(コンポーネント)の現在のURLを表しています。
URLコンストラクターと組み合わせることで、静的なフルパスを取得することができるという仕組みです。

この時注意するのは、第一引数には変数のみで入れないことです。
以下のように変数だけを入れると、ビルド時にViteが解析できずエラーになるため、変数のみを渡すことはできません。

// NG
const imgUrl = new URL(imagePath, import.meta.url).href

また絶対パスを使用する場合はエイリアス@は使用できず、srcと書く必要があります。

// absolute path
const getImageUrl = (fileName: string) => {
  return new URL(`src/assets/images/${fileName}.png`, import.meta.url).href
}

原因

ローカルでは表示されるのに、デプロイ後に表示ができなくなる原因はviteによるものでした。
viteではビルド時にassetsフォルダ内のファイル名を変更して、_assetsフォルダに格納します。静的パスの場合は、処理されそれに応じてパスが変更されます。

static.vue
<img src="../assets/images/img.png"/>

例えばこの場合だと、開発中は /img.png となり、本番用ビルドでは /assets/img.2d8efhg.pngのような形に。

dynamic.vue
<img :src="`../assets/images/${fileName}.png`"/>

一方で動的パスの場合は定数として受け取るだけで、変更を行いません。
そのため開発中では/img.pngですが、本番用ビルドでは/assets/img.pngと認識されます。

よって、本番用のビルドではファイルが見つからずデプロイ後には画像が表示されないということが起きてしまようです。

requireではできなかった

解決策を探していたところ、require()を使えばいいという記事がたくさん出てきました。
実際にやってみるとrequire is not definedというエラーがコンソールに出て画面が動かない。。

sample.vue
<img :src="require(`../assets/images/${fileName}.png`)"/>

調べてみたところ、 viteではrequireは使えないそうです。
見ていた記事も2、3年前だったのでvue-cli時代の解決策が多く記事に残っていたのだと思います。

今はVue3系でviteを使っている方も多いと思うので、この記事が役に立てば幸いです。

参考

https://dev.to/kareemsulaimon/how-to-dynamically-import-image-in-vue-vite-4g3a
https://vitejs.dev/guide/assets.html#new-url-url-import-meta-url
https://stackoverflow.com/questions/66419471/vue-3-vite-dynamic-image-src

Discussion