next.js icon indicating copy to clipboard operation
next.js copied to clipboard

NextJS 13 next/image blur placeholder not working

Open imPrathamDev opened this issue 2 years ago • 14 comments

Verify canary release

  • [X] I verified that the issue exists in the latest Next.js canary release

Provide environment information

Operating System: Platform: win32 Arch: x64 Version: Windows 10 Home Single Language Binaries: Node: 16.13.2 npm: N/A Yarn: N/A pnpm: N/A Relevant packages: next: 13.0.0 eslint-config-next: 13.0.0 react: 18.2.0 react-dom: 18.2.0

What browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

vercel

Describe the Bug

next/image blur placeholder not working in both development and production. Demo: https://nextjs-13-image.vercel.app/ It's working on NextJS 12

Expected Behavior

Image Blur when images are loading

Link to reproduction

https://github.com/imPrathamDev/nextjs-13-image

To Reproduce

https://github.com/imPrathamDev/nextjs-13-image

imPrathamDev avatar Oct 29 '22 16:10 imPrathamDev

It also doesn't work on the example from the docs. I first thought maybe something changed but the example doesn't work either.

ShockTr avatar Oct 29 '22 19:10 ShockTr

@ShockTr At least for me, the shimmer effect does seem to be working in the example you've linked. Try opening the link in an incognito window and refreshing the page - you'll notice a grayish-black shimmer show up for a split second before getting replaced by the image.

ishaqibrahimbot avatar Oct 31 '22 14:10 ishaqibrahimbot

@ShockTr At least for me, the shimmer effect does seem to be working in the example you've linked. Try opening the link in an incognito window and refreshing the page - you'll notice a grayish-black shimmer show up for a split second before getting replaced by the image.

I can still reproduce my problem. Gif of the problem My browser info if it can be helpful: Google Chrome 106.0.5249.119 (64 bit) Windows 11(22623.746)

ShockTr avatar Oct 31 '22 17:10 ShockTr

@ShockTr the video you've shared - that is exactly how the component is supposed to behave with the 'shimmer' effect. If you look at the code, you will see that the custom svg-based shimmer component is passed into the blurDataUrl field for the Image component. That's why you see the grayish-black shimmer before the actual mountains image loads in.

For comparison, check out the 'placeholder' example here. For this one, the blur part is actually taken from the image. In the code for this one, you will notice no blurDataUrl property - that is because the image src is locally imported beforehand.

Note - the blur behavior is different in the nextjs v13 next/image component from what it was in the v12 and older next/image components. You shouldn't expect the same behavior from the v13 next/image. I'm writing another explanation to address the issue faced by @imPrathamDev - that might help explain this difference better.

ishaqibrahimbot avatar Oct 31 '22 20:10 ishaqibrahimbot

@imPrathamDev the v13 next/image component handles the blurring effect differently as compared to the <= v12 next/image component. You can do either one of these two things:

  • generate a placeholder using a tool like Plaiceholder and pass that in as the blurDataURI
  • instead of passing the entire >1 Mb size image to the blurDataURI (which would take as long as the original image to load, and hence defeat the purpose of having a placeholder), pass in a compressed (smaller dimension + lower quality = smaller file size) version of the same image

Both of the strategies above should get the placeholder="blur" effect working for you. Let me know if they don't.

FYI, in v12 (and earlier), the component directly sets the background image equal to the blurDataUrl along with applying a blur filter to achieve the effect:

Screen Shot 2022-11-01 at 2 33 01 AM

In v13, the component sets an actual data uri by loading the blurDataUri via an svg (this is done inside the getImageBlurSvg function):

Screen Shot 2022-11-01 at 2 34 46 AM

ishaqibrahimbot avatar Oct 31 '22 21:10 ishaqibrahimbot

@imPrathamDev @ishaqibrahimbot my app on Next 13 loads smaller images (1kb in size - 10px:10px) and the blurred image placeholders are gone. It previously worked on Next 12.

alextenczar avatar Nov 01 '22 03:11 alextenczar

@alextenczar can you share the code for your app (or if it's big/private, create a small code sandbox that reproduces the issue)? I'll try to look into why the blurred image placeholders are gone.

ishaqibrahimbot avatar Nov 01 '22 04:11 ishaqibrahimbot

@ishaqibrahimbot

Here is the code from inside of my return function of the page's TSX.

      {returnData.map((value: any, index: any) => {
        let imgUrl = value.artworkUrl100.replace('100x100', '300x300')
        let blurImgUrl = value.artworkUrl100.replace('100x100', '10x10')
        let showId = value.id
        let imagePriority = false;
        if(index <= 20) {
          imagePriority = true;
        }
        return (
          <div className={styles.showItem} key={value.id}>
            <Link href={{ pathname: `/show`, query: { id: showId } }}>
                <Image className={styles.thumb} src={imgUrl} alt={value.name} width={300} height={300} priority={imagePriority} placeholder="blur" blurDataURL={blurImgUrl}/>
                <p>{value.name}</p>
                <p>{value.artistName}</p>
            </Link>
          </div>
        );
      })
      }

Basically, I replace the file's URL to the one used for 10x10 images. Then feed it as the blurDataURL. These file URLs are contained within a prop I receive during getServerSideProps. And just a note: importing next/legacy/image brings back the expected blur.

Link to code's file: https://github.com/alextenczar/ApplePodcastsWeb/blob/dev/pages/locale/%5Bid%5D/index.tsx

alextenczar avatar Nov 01 '22 13:11 alextenczar

Same issue here. I just upgraded next to 13.0.1 and the blur is not working. In my case, the blurDataURL displays the broken image icon. Was working fine before the upgrade.

Panix-dev avatar Nov 01 '22 14:11 Panix-dev

@imPrathamDev @alextenczar @Panix-dev so here's what I've understood after some digging around.

The way you're expected to use the blurDataUrl property on the Next/Image component has changed with nextjs v13. In earlier versions, you could pass a url to an image and that would be sufficient to achieve the blur effect.

However, with the new v13 Next/Image component, you're expected to pass base64-encoded image data to the blurDataUrl property. Passing in a simple url like before no longer works cause the url is a reference to where the image is stored, whereas blurDataUrl requires actual image data. This much is already made clear in the beta docs for the new image component:

Screen Shot 2022-11-01 at 10 13 09 PM

So now, if you want to achieve the blur effect, the best strategy is to use a tool like Plaiceholder to get a suitable base64 placeholder image on the server side, and pass that into the blurDataUrl property.

Here's what this would look like for the nextjs-13-image app @imPrathamDev has shared:

import Image from "next/image";
import { getPlaiceholder } from "plaiceholder";

export default async function Home() {
  const imageUrls = [
    "https://firebasestorage.googleapis.com/v0/b/replay-chat-dd920.appspot.com/o/next-images%2Fbilly-huynh-W8KTS-mhFUE-unsplash.jpg?alt=media&token=c754dcd9-5bb6-422b-bba2-35b40f1b047f",
    "https://firebasestorage.googleapis.com/v0/b/replay-chat-dd920.appspot.com/o/next-images%2Fmilad-fakurian-nY14Fs8pxT8-unsplash.jpg?alt=media&token=88ac6424-3b03-4da2-8099-2768d11cce35",
    "https://firebasestorage.googleapis.com/v0/b/replay-chat-dd920.appspot.com/o/next-images%2Fpawel-czerwinski-vI5XwPbGvmY-unsplash.jpg?alt=media&token=bc94569f-c146-44d3-9e95-4334783e77c1",
    "https://firebasestorage.googleapis.com/v0/b/replay-chat-dd920.appspot.com/o/next-images%2Fpawel-czerwinski-prMn9KINLtI-unsplash.jpg?alt=media&token=e437b711-3518-4fbe-a6de-ab6950908277",
  ];
  const placeholders = await Promise.all(
    imageUrls.map(async (url) => {
      const { base64 } = await getPlaiceholder(url);
      return base64;
    })
  );
  return (
    <div>
      {imageUrls.map((url, index) => {
        return (
          <Image
            key={url}
            src={url}
            alt="Vercel Logo"
            blurDataURL={placeholders[index]}
            placeholder="blur"
            width={420}
            height={220}
          />
        );
      })}
    </div>
  );
}

Result:

https://user-images.githubusercontent.com/74908398/199297214-03bdb3c3-9df4-485f-95e0-e353b9b8c2f2.mov

This example uses React Server Components (which you can use inside the new app directory) so if your app is using the pages directory, you will have to generate the placeholders in either of getStaticProps or getServerSideProps or inside an API route (Plaiceholder works in a nodejs environment, not in the browser).

Let me know if you guys have any questions about this explanation.

ishaqibrahimbot avatar Nov 01 '22 17:11 ishaqibrahimbot

@ishaqibrahimbot Thank you for this. I tried a variation of your solution but have reverted to using Next's Legacy Image component. While it worked, implementing plaiceholder significantly slowed down my site (maybe because I'm running it with getserversideprops?). If you're curious what I did, free look at my code and see if anything could be improved. Any help would be appreciated, thank you!

https://github.com/alextenczar/ApplePodcastsWeb/blob/55e690fc31fd41195fd2b02deb6080f6cf28059a/pages/locale/%5Bid%5D/index.tsx

alextenczar avatar Nov 02 '22 02:11 alextenczar

@ishaqibrahimbot Thank you for this. I tried a variation of your solution but have reverted to using Next's Legacy Image component. While it worked, implementing plaiceholder significantly slowed down my site (maybe because I'm running it with getserversideprops?). If you're curious what I did, free look at my code and see if anything could be improved. Any help would be appreciated, thank you!

https://github.com/alextenczar/ApplePodcastsWeb/blob/55e690fc31fd41195fd2b02deb6080f6cf28059a/pages/locale/%5Bid%5D/index.tsx

@alextenczar Try to deploy on vercel then check speed because sometimes next app works slow in development but in production works fine.

imPrathamDev avatar Nov 02 '22 05:11 imPrathamDev

@alextenczar is there a reason you're using getServerSideProps and not doing SSG with getStaticProps? Your use case seems to suggest that you'd be better off generating your pages at build time and then setting a suitable revalidate time to make sure the data does not become stale. I've modified your file to use getStaticProps and it's working a whole lot faster. Here's the code:

import type {
  GetStaticPropsContext,
  InferGetStaticPropsType,
  NextPage,
} from 'next'
import styles from '../../../styles/Home.module.scss'
import Link from 'next/link'
import Layout from '../../../components/layout'
import CountrySelect from '../../../components/countrySelect'
import Image from 'next/image'
import { getPlaiceholder } from 'plaiceholder'

export async function getStaticPaths({}) {
  let countriesArr = [
    'dz',
    'ao',
    'ai',
    'ag',
     .
     .
     .
    'vn',
    'ye',
    'zm',
    'zw',
  ]

  return {
    paths: countriesArr
      .slice(0, 5)
      .concat(['us'])
      .map((countryCode) => `/locale/${countryCode}`), // limiting the number of pages built for now
    fallback: false,
  }
}

async function genPlaceholders(item: any) {
  const placeholders = await Promise.all(
    item.map((index: any) => {
      return getPlaiceholder(index.artworkUrl100.replace('100x100', '10x10'))
    })
  )

  return placeholders
}

export async function getStaticProps({
  params,
}: GetStaticPropsContext<{ id: string }>) {
  // Fetch data from external API
  const id = params?.id || 'us'

  const res = await fetch(
    `https://rss.applemarketingtools.com/api/v2/${id}/podcasts/top/50/podcasts.json`
  )
  const data = await res.json()

  // 404 if no results retrieved
  if (!data?.feed?.results)
    return {
      notFound: true,
    }

  const placeholders = await genPlaceholders(data.feed.results)

  let returnPlaceholders: any = []
  placeholders.map((index) => {
    returnPlaceholders.push(index.base64)
  })
  //console.log(test)

  // Pass data to the page via props
  let returnData: Array<object>
  returnData = data.feed.results
  return {
    props: { returnData, countryCode: id, returnPlaceholders },
    revalidate: 60 * 60, // revalidate cache every hour
  }
}

interface Props {
  returnData: Array<object>
  countryCode?: String
  returnPlaceholders: any
}

function LocaleHome(props: InferGetStaticPropsType<typeof getStaticProps>) {
  let { returnData, countryCode, returnPlaceholders } = props

  return (
    <Layout>
      <div className={styles.headerSelect}>
        <h2>Top Shows In:</h2>
        <CountrySelect country={{ countryCode }}></CountrySelect>
      </div>
      <div className={styles.showContainer}>
        {returnData.map((value: any, index: any) => {
          let imgUrl = value.artworkUrl100.replace('100x100', '300x300')
          let blurImgUrl = value.artworkUrl100.replace('100x100', '10x10')
          let showId = value.id
          let imagePriority = false
          if (index <= 20) {
            imagePriority = true
          }

          return (
            <div className={styles.showItem} key={value.id}>
              <Link href={{ pathname: `/show`, query: { id: showId } }}>
                <Image
                  className={styles.thumb}
                  src={imgUrl}
                  alt={value.name}
                  width={300}
                  height={300}
                  priority={imagePriority}
                  placeholder="blur"
                  blurDataURL={returnPlaceholders[index]}
                />
                <p>{value.name}</p>
                <p>{value.artistName}</p>
              </Link>
            </div>
          )
        })}
      </div>
    </Layout>
  )
}

export default LocaleHome

ishaqibrahimbot avatar Nov 02 '22 09:11 ishaqibrahimbot

Thank you @imPrathamDev I'll look into my options for re-validation. I still think it's a shame that placeholders don't play well with getserversideprops when several image placeholders need to get generated. The previous method of generating placeholders was sufficient for my needs. Maybe a solution for this use-case will pop up later on.

alextenczar avatar Nov 02 '22 13:11 alextenczar

@imPrathamDev @alextenczar @Panix-dev so here's what I've understood after some digging around.

The way you're expected to use the blurDataUrl property on the Next/Image component has changed with nextjs v13. In earlier versions, you could pass a url to an image and that would be sufficient to achieve the blur effect.

However, with the new v13 Next/Image component, you're expected to pass base64-encoded image data to the blurDataUrl property. Passing in a simple url like before no longer works cause the url is a reference to where the image is stored, whereas blurDataUrl requires actual image data. This much is already made clear in the beta docs for the new image component:

Screen Shot 2022-11-01 at 10 13 09 PM

So now, if you want to achieve the blur effect, the best strategy is to use a tool like Plaiceholder to get a suitable base64 placeholder image on the server side, and pass that into the blurDataUrl property.

Here's what this would look like for the nextjs-13-image app @imPrathamDev has shared:

import Image from "next/image";
import { getPlaiceholder } from "plaiceholder";

export default async function Home() {
  const imageUrls = [
    "https://firebasestorage.googleapis.com/v0/b/replay-chat-dd920.appspot.com/o/next-images%2Fbilly-huynh-W8KTS-mhFUE-unsplash.jpg?alt=media&token=c754dcd9-5bb6-422b-bba2-35b40f1b047f",
    "https://firebasestorage.googleapis.com/v0/b/replay-chat-dd920.appspot.com/o/next-images%2Fmilad-fakurian-nY14Fs8pxT8-unsplash.jpg?alt=media&token=88ac6424-3b03-4da2-8099-2768d11cce35",
    "https://firebasestorage.googleapis.com/v0/b/replay-chat-dd920.appspot.com/o/next-images%2Fpawel-czerwinski-vI5XwPbGvmY-unsplash.jpg?alt=media&token=bc94569f-c146-44d3-9e95-4334783e77c1",
    "https://firebasestorage.googleapis.com/v0/b/replay-chat-dd920.appspot.com/o/next-images%2Fpawel-czerwinski-prMn9KINLtI-unsplash.jpg?alt=media&token=e437b711-3518-4fbe-a6de-ab6950908277",
  ];
  const placeholders = await Promise.all(
    imageUrls.map(async (url) => {
      const { base64 } = await getPlaiceholder(url);
      return base64;
    })
  );
  return (
    <div>
      {imageUrls.map((url, index) => {
        return (
          <Image
            key={url}
            src={url}
            alt="Vercel Logo"
            blurDataURL={placeholders[index]}
            placeholder="blur"
            width={420}
            height={220}
          />
        );
      })}
    </div>
  );
}

Result:

Screen.Recording.2022-11-01.at.10.22.22.PM.mov This example uses React Server Components (which you can use inside the new app directory) so if your app is using the pages directory, you will have to generate the placeholders in either of getStaticProps or getServerSideProps or inside an API route (Plaiceholder works in a nodejs environment, not in the browser).

Let me know if you guys have any questions about this explanation.

Thanks for your solution, it works fine. Can you please explain why there is no longer support for simple thumbnail url and we force to use base64 ?

Also I couldn't find any note about Sharp in the docs for image component but there is a warning when runing server in production. I think it's good to add some note about Sharp in the docs.

ArianHamdi avatar Nov 21 '22 00:11 ArianHamdi

To add to this issue, the blur style is not removed when the image is loaded, so any non-square transparent bg will show the background blur behind it.

Igosuki avatar Dec 08 '22 10:12 Igosuki

To add to this issue, the blur style is not removed when the image is loaded, so any non-square transparent bg will show the background blur behind it.

The same issue.

vadymstebakov avatar Dec 14 '22 11:12 vadymstebakov

Same issue with the blur not being removed. Example: https://dev.goodparty.org/

(Next 13.0.6)

tomer-tgp avatar Dec 15 '22 19:12 tomer-tgp

I've only blurDataUrl with no blur effect - on Next 13.1.0.

Here is my code :

<Image
    placeholder='blur'
    blurDataURL='/placeholder.jpg'
    className={clsx('absolute top-0 left-0 w-full h-full object-cover rounded-lg')}
    src={imgUrl}
    alt='movie thumbnail'
    width="200"
    height="200"
/>

I tried : placeholder='empty' = ok placeholder='blur' = ko

itag-tech avatar Jan 12 '23 22:01 itag-tech

Same issue shimmer effect not working with NextJs13.

We can see difference in example from docs. Legacy version has the effect.

https://image-component.nextjs.gallery/shimmer https://image-legacy-component.nextjs.gallery/shimmer

Xelaflash avatar Jan 23 '23 16:01 Xelaflash

To add to this issue, the blur style is not removed when the image is loaded, so any non-square transparent bg will show the background blur behind it.

@Igosuki @vadymstebakov could you guys please share a reproducible example with me, I'd like to debug why the blur style is not being removed cause the code handling this is more or less the same as the code handling this in the legacy image component.

@tomer-tgp I can't see the blur style remaining in the example you've shared, perhaps because you've fixed it - would be great if you could help me with an example as well. Thanks!

ishaqibrahimbot avatar Jan 29 '23 08:01 ishaqibrahimbot

could you guys please share a reproducible example with me...

I'm also having the blurHash remaining in place post load. @ishaqibrahimbot Here's a CodeSandbox link for a stripped down project. - Next 13.0.7

Addendum: I tried updating and it seems to be working in Next 13.1.6 - But the issue now is that css transitions on the image have no effect. In prior versions (last project was 12.1.7-canary.5) you put img { transition: all 300ms } in your css and it automagically faded between blurData and img.

MisterPea avatar Jan 29 '23 22:01 MisterPea

To add to this issue, the blur style is not removed when the image is loaded, so any non-square transparent bg will show the background blur behind it.

@Igosuki @vadymstebakov could you guys please share a reproducible example with me, I'd like to debug why the blur style is not being removed cause the code handling this is more or less the same as the code handling this in the legacy image component.

@tomer-tgp I can't see the blur style remaining in the example you've shared, perhaps because you've fixed it - would be great if you could help me with an example as well. Thanks!

Next.js version: 13.0.6.

Here is Codesandbox

image

vadymstebakov avatar Feb 01 '23 14:02 vadymstebakov

Can't do any better than what @vadymstebakov commented

Igosuki avatar Mar 10 '23 10:03 Igosuki

Same issue shimmer effect not working with NextJs13.

We can see difference in example from docs. Legacy version has the effect.

https://image-component.nextjs.gallery/shimmer https://image-legacy-component.nextjs.gallery/shimmer

Next.js 13 always creates a blurry version of blurDataUrl but in case we need shimmer effect, it would be redundant (and have performance issues in our case) and resulting in a broken shimmer effect.

What should I do to bring it up? Should I open a new issue or feature request?

There should be an option to use blurDataUrl without modifying. perhaps we should have a better prop name like placeholderDataUrl and more options in placeholder prop instead of just empty & blur.

ArianHamdi avatar Mar 13 '23 21:03 ArianHamdi

I think it would also be good if you could provide a placeholder img when empty is defined, so that not a base64 encoded gif is used from here. We have pretty strict CSP policies, which forbid data: references in images. Unfortunately, when empty is defined, a transparent gif is used: data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7.

kelvinlouis avatar Apr 05 '23 18:04 kelvinlouis

Is there the possibility that next/image could add support for URLs in the blurDataURL prop? We use IMGIX with custom query parameters to generate our image placeholders. We don't need to use an external tool because imgix is enough for this.

<Image
        src={
          'https://my-company-images-prd.imgix.net/public/bg-desktop.png?auto=format'
        }
        alt="company"
        blurDataURL={
          // this is our placeholder version using imgix query parameters.
          'https://my-company-images-prd.imgix.net/public/bg-desktop.png?auto=format&blur=200&px=24'
        }
        placeholder="blur"
        layout="fill"
        objectFit="cover"
      />

For now, we'll use the next/image/legacy.

GiancarlosIO avatar Apr 13 '23 21:04 GiancarlosIO

I faced the same problem and changing the base64 from data:application/octet-stream;base64,.... to data:image/png;base64,... worked for me.

larbisahli avatar Apr 14 '23 13:04 larbisahli

just use "import Image from 'next/legacy/image'" instead of "import Image from 'next/image'".

4b6576696e avatar Apr 17 '23 16:04 4b6576696e

just use "import Image from 'next/legacy/image'" instead of "import Image from 'next/image'".

it helped to show blurred preloaded image, thanks

Irines avatar Apr 18 '23 11:04 Irines