Next.js Discord

Discord Forum

Can't get image blur placeholders to work

Answered
Floh posted this in #help-forum
Open in Discord
I am trying to setup blur placeholders for my images on my home page in Next 15 but running into some weird issues, and not understanding how this really should work.

When I load my page with cache disabled and 2G throttling I can sometimes see the blurred placeholder images briefly on the page that has no JS or CSS loaded, but then once the JS loads my page appears and there's just empty space where the images should be.

The placeholder never shows after that, and then the images just load from top to bottom in the empty space with a white background, shifting the layout as they appear.

Where could I be going wrong?

This is my page.tsx
// Set revalidation time to 1 hour (3600 seconds)
export const revalidate = 3600

// Force static rendering
export const dynamic = 'force-static'

export default async function HomePage() {
  const photos = await getPhotos()

  return (
    <div>
      <Padding className="relative">
        <div className="flex flex-col gap-4 mt-8">
          {photos.map(
            (photo, index) =>
              photo.url && <Photo key={photo.id} photo={photo} priority={index < 3} disabled />,
          )}
        </div>
      </Padding>
    </div>
  )
}


This is my getPhotos() server action:
export const getPhotos = cache(async (): Promise<Photo[]> => {
  const payload = await getPayload({ config })
  // Use direct database query instead of API route
  const photos = await payload.find({
    collection: 'photos',
    depth: 1,
    overrideAccess: true,
    limit: 25,
  })

  const photosWithBlurDataUrl = await Promise.all(
    photos.docs.map(async (photo) => {
      if (!photo.url) {
        console.error(`[lib/actions/photos] Photo ${photo.id} has no URL`)
        return photo
      }
      const blurDataUrl = await getBase64ImageUrl(photo.url)
      return { ...photo, blurDataUrl }
    }),
  )
  // console.log('[lib/actions/photos] fetched photos', photosWithBlurDataUrl)
  return photosWithBlurDataUrl
})


As you can see, I am currently generating the base64 image URL using getBase64ImageUrl() which I created using sharp functions

import sharp from 'sharp'

export async function getBase64ImageUrl(imageUrl: string) {
  const fullUrl = `${process.env.NEXT_PUBLIC_BASE_URL}${imageUrl}`
  const response = await fetch(fullUrl)
  const buffer = await response.arrayBuffer()
  const resizedImageBuffer = await sharp(buffer).resize(40, null, { fit: 'inside' }).toBuffer()
  return `data:image/png;base64,${resizedImageBuffer.toString('base64')}`
}
Answered by Floh
I figured out that something with my custom photo component was cuasing the issue
View full answer

2 Replies

Also here's the <Photo> component
import { cn } from '@/lib/utils'
import Image from 'next/image'
import Link from 'next/link'
export default function Photo(props: {
  photo: Photo
  className?: string
  disabled?: boolean
  priority?: boolean
}) {
  const { photo, className, disabled = false, priority = false } = props

  if (!photo.url) {
    console.error(`[components/photos/photo] Photo ${photo.id} has no URL`)
    return null
  }

  if (!photo.blurDataUrl) {
    console.error(`[components/photos/photo] Photo ${photo.id} has no blur data URL`)
    return <p>No blur data URL</p>
  }

  return (
    <Link
      href={`/photo/${photo.id}`}
      className={cn(
        'relative w-fit h-full rounded select-none [&_*]:pointer-events-none z-10',
        className,
        disabled ? 'pointer-events-none' : '',
      )}
      key={photo.id}
      tabIndex={disabled ? -1 : 0}
    >
      <Image
        src={photo.url}
        alt={photo.name || ''}
        width={2560}
        height={1440}
        className="w-fit h-full object-cover rounded"
        priority={priority}
        draggable={false}
        placeholder="blur"
        blurDataURL={photo.blurDataUrl}
      />
    </Link>
  )
}
I figured out that something with my custom photo component was cuasing the issue
Answer