image icon indicating copy to clipboard operation
image copied to clipboard

Setting fallback image on 404 respons after fetch

Open danielohling opened this issue 1 year ago • 7 comments

Hi

I'm not really sure how this is supposed to work? There is nothing in the documentation that shows how to do it. It says that you can use Native events such as load or error.

I have tried add a fallback image when the response comes back as 404 direct on the NuxtImg components like so:

<NuxtImg :src="item.ImageURL" width="120" height="120" @error="$event.target.src = fallbackImg" />

but without luck 😢

I've also tried making a function that returns the url to another fallback image but no luck.

It feels like the error event is emitted long after all the images are looped? Or Is there a way to catch it before?

danielohling avatar Oct 02 '23 11:10 danielohling

Hey @danielohling

Have you found a solution, I'm facing the same issue. Still exploring

Thanks

xlanex6 avatar Dec 04 '23 16:12 xlanex6

Same issue here

vasilistotskas avatar Dec 21 '23 12:12 vasilistotskas

Hey @danielohling

Have you found a solution, I'm facing the same issue. Still exploring

Thanks

This is my solution:

<script lang="ts">
import { defineComponent } from 'vue'
import { imgProps } from '@nuxt/image/dist/runtime/components/nuxt-img'

export default defineComponent({
  props: imgProps,

  setup(props) {
    const { fallbackImage } = useAppConfig()

    const src = ref(props.src)

    watch(
      () => props.src,
      value => {
        src.value = value
      },
    )

    return () =>
      h(resolveComponent('NuxtImg'), {
        ...props,
        src: src.value,
        onError: () => {
          src.value = fallbackImage
        },
      })
  },
})
</script>

vuthanhbayit avatar Jan 09 '24 08:01 vuthanhbayit

You could use

<NuxtImg :src="original.png" :placeholder="placeholder.png" />

as the placeholder acts like a fallback too if the image fails to load. (Got it from the Nuxt Discord)

imnaK avatar Jan 14 '24 19:01 imnaK

You could use

<NuxtImg :src="original.png" :placeholder="placeholder.png" />

as the placeholder acts like a fallback too if the image fails to load. (Got it from the Nuxt Discord)

This only works for NuxtImg and not NuxtPicture. Also the placeholder witll ALWAYS be loaded, even if not needed.

In my case I want to show the original image if the optimization failed for some reason. If I would use placeholder, it would always load the original image as well. To only show the fallback, if an actual eerror happens, I created my own components, wrapping NuxtImg and NuxtPicture.

CustomNuxtImg.vue:

<template>
  <nuxt-img v-if="!hasError || !fallback" v-bind="propsWithoutFallbackAndSrc" :src="src" @error="handleError" @load="emit('load', $event)" />
  <img v-else v-bind="propsWithoutFallbackAndSrc" :src="fallback" />
</template>

<script lang="ts" setup>
import type { NuxtImgProps } from "@base/modules/image/types/nuxt-image"

/*
 * Small wrapper over NuxtImg
 * Supports fallback to default img tag when nuxt-img was unable to optimize the image.
 * This could happen if an image file is corrupted (e.g. wrongly saved by an image editing software)
 * However, providing a fallback is always a good practise
 */

interface Emits {
  (e: "error", data: any): void
  (e: "load", data: any): void
}

interface Props extends /* @vue-ignore */ NuxtImgProps {
  src?: string
  fallback?: string
}

const emit = defineEmits<Emits>()

const props = defineProps<Props>()

const attrs = useAttrs()

const propsWithoutFallbackAndSrc = computed(() => {
  const { fallback, src, ...restProps } = props

  return { ...attrs, ...restProps }
})

const hasError = ref(false)

const handleError = (data: any) => {
  emit("error", data)
  hasError.value = true
}
</script>

CustomNuxtPicture.vue:

<template>
  <nuxt-picture v-if="!hasError || !fallback" v-bind="propsWithoutFallbackAndSrc" :src="src" :imgAttrs="{ onError: handleError }" @load="emit('load', $event)" />
  <img v-else v-bind="propsWithoutFallbackAndSrc" :src="fallback" />
</template>

<script lang="ts" setup>
import type { NuxtPictureProps } from "@base/modules/image/types/nuxt-image"

/*
 * Small wrapper over NuxtPicture
 * Supports fallback to default img tag when nuxt-img was unable to optimize the image.
 * This could happen if an image file is corrupted (e.g. wrongly saved by an image editing software)
 * However, providing a fallback is always a good practise
 * */
interface Emits {
  (e: "error", data: any): void
  (e: "load", data: any): void
}

interface Props extends /* @vue-ignore */ NuxtPictureProps {
  src?: string
  fallback?: string
}

const emit = defineEmits<Emits>()

const props = defineProps<Props>()

const attrs = useAttrs()

const propsWithoutFallbackAndSrc = computed(() => {
  const { fallback, src, ...restProps } = props

  return { ...attrs, ...restProps }
})

const hasError = ref(false)

const handleError = (data: any) => {
  emit("error", data)
  hasError.value = true
}
</script>

Would really appreciate an official API.

oemer-aran avatar Feb 28 '24 16:02 oemer-aran

@danielroe I was just looking for the same feature. Here in the Nuxt Movies example there is exactly a use case where the HD image is not present and we could use the SD one as fallback. Go under the videos tab here: https://movies.nuxt.space/tv/63770

/youtube/vi/nWufb0qL1xU/maxresdefault.jpg -> 404 ❌ /youtube/vi/nWufb0qL1xU/hqdefault.jpg -> 200 ✅

This is the componente Card.vue

<script setup lang="ts">
import type { Video } from '~/types'

const props = defineProps<{
  item: Video
}>()

const showModal = useIframeModal()
function play() {
  return showModal(getVideoLink(props.item)!)
}
</script>

<template>
  <button pb2 text-left @click="play()">
    <div
      block bg-gray4:10 p1 flex
      class="aspect-16/9"
      transition duration-400 relative
      hover="scale-102 z10"
    >
      <NuxtImg
        :src="`/youtube/vi/${item.key}/maxresdefault.jpg`"
        width="400"
        height="600"
        format="webp"
        :alt="props.item.name"
        w-full h-full object-cover
      />
      <div flex w-full h-full absolute inset-0 op20 hover:op100 transition>
        <div i-ph-play ma text-3xl />
      </div>
    </div>
    <div mt-2>
      {{ props.item.name }}
    </div>
    <div op60 text-sm>
      {{ props.item.type }}
    </div>
  </button>
</template>

Archetipo95 avatar Apr 06 '24 12:04 Archetipo95