NextJS v11 and higher
Use blurDataURL property for placeholders, but before convert your SVG to data-uri.
Read more about blurDataURL
Solution for NextJS below version v11
To avoid the image loading before the placeholder does you need to convert it to data-uri and deliver it with your code:
const placeholder = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-8 -8 40 40' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-image'%3E%3Crect x='3' y='3' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Ccircle cx='8.5' cy='8.5' r='1.5'%3E%3C/circle%3E%3Cpolyline points='21 15 16 10 5 21'%3E%3C/polyline%3E%3C/svg%3E`
<img src={placeholder}/>
Now you need to fetch the real image and for this use Image class:
const [url, setUrl] = useState(placeholder) // use placeholder as default image, which appears instantly
const img = new Image()
img.src = src
img.onload = () => setUrl(img.src) // callback is called when image is loaded, with setUrl we swap images
Full component:
import './styles.css'
import {useEffect, useState} from 'react'
// to create data url use this service: https://heyallan.github.io/svg-to-data-uri/
const placeholder = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-8 -8 40 40' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-image'%3E%3Crect x='3' y='3' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Ccircle cx='8.5' cy='8.5' r='1.5'%3E%3C/circle%3E%3Cpolyline points='21 15 16 10 5 21'%3E%3C/polyline%3E%3C/svg%3E`
const Picture = ({alt, src, ...props}) => {
const [url, setUrl] = useState(placeholder)
useEffect(() => {
if (!src) return
const img = new Image()
img.src = src
img.onload = () => setUrl(img.src)
}, [src])
return <img alt={alt} src={url} {...props} />
}
const App = () => (
<div className='App'>
<h1>Hello CodeSandbox</h1>
<Picture
className='img'
alt='Random image'
src='https://picsum.photos/500/500'
/>
</div>
)
☝️ To avoid name collisions import NextJS Image component with different name:
import NextImage from 'next/image' // instead of import Image from 'next/image'
Here you can find the demo. To see how it works click refresh icon:
