Search code examples
vue.jsnuxt.jslazy-loadingserver-side-renderingsrc

How to properly lazy-load images in Nuxt?


Is it possible to use both - ssr and lazy-loading for images on the client side. Here is my situation. My markup is rendered with img[src], in my chrome browser I press ctrl+U and can see all images have an src attribute (seo robots will recognize them and I'm happy), but on the client side I need to have img[data-src] for lazy-loading. If I have a data-src attribute on the server-side, the seo robots won't find any src (pressing ctrl + U will prove it), but all images will be displayed with lazy loading... Should I filter the markup before receiving on the client side (actually I'm sure how to implement it in nuxt). Please suggest any ideas or directions to solve the issue?


Solution

  • Here is how to achieve the following:

    • fetching some images from an API
    • display 1 image when on a mobile viewport (< 1000px) represented by the red line
    • if your viewport is wider, you have 4 images (desktop viewport)
    • loading="lazy" is doing the heavy lifting here, it's working fine on not-so old browsers
    • if you start on mobile, open your network tab, filter by images named 6, you will see that you'll get some network requests going out if increasing the viewport > 1000px (lazy-loaded when scrolling still)
    <template>
      <div>
        <pre>all the interesting images: {{ pokemon.sprites.other }}</pre>
        <div class="reference-breakpoint"></div>
    
        <p>Down below are all the remaining images ⬇️</p>
        <img :src="pokemon.sprites.other.dream_world.front_default" />
        <img
          class="hide-when-mobile"
          loading="lazy"
          :src="pokemon.sprites.other.home.front_default"
        />
        <img
          class="hide-when-mobile"
          loading="lazy"
          :src="pokemon.sprites.other.home.front_shiny"
        />
        <img
          class="hide-when-mobile"
          loading="lazy"
          :src="pokemon.sprites.other['official-artwork'].front_default"
        />
      </div>
    </template>
    
    <script>
    export default {
      async asyncData({ $axios }) {
        const pokemon = await $axios.$get(
          'https://pokeapi.co/api/v2/pokemon/charizard'
        )
        return { pokemon }
      },
    }
    </script>
    
    <style scoped>
    img {
      display: block;
      height: 100vh;
      width: auto;
    }
    .reference-breakpoint {
      width: 1000px;
      height: 1rem;
      background-color: red;
    }
    @media (max-width: 1000px) {
      .hide-when-mobile {
        display: none;
      }
    }
    </style>
    

    Not satisfied with this one? Here is an intersectionObserver example, a bit more complex but also more powerful/flexible.