Skip to content

Commit f77b70d

Browse files
committed
Add better error message, add webp
1 parent 2dbb454 commit f77b70d

File tree

7 files changed

+71
-29
lines changed

7 files changed

+71
-29
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# `placeholder=blur` without `blurDataURL`
2+
3+
#### Why This Error Occurred
4+
5+
You are attempting use the `next/image` component with `placeholder=blur` property but no `blurDataURL` property.
6+
7+
#### Possible Ways to Fix It
8+
9+
- Add a [`blurDataURL`](https://nextjs.org/docs/api-reference/next/image#blurdataurl) property, the contents should be a small Data URL to represent the image
10+
- Change the [`src`](https://nextjs.org/docs/api-reference/next/image#src) property to a static import with one of the supported file types: jpg, png, or webp
11+
- Remove the [`placeholder`](https://nextjs.org/docs/api-reference/next/image#placeholder) property, effectively no blur effect

packages/next/build/webpack/loaders/next-image-loader.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import sizeOf from 'image-size'
33
import { processBuffer } from '../../../next-server/server/lib/squoosh/main'
44

55
const BLUR_IMG_SIZE = 8
6-
const VALID_IMAGE_TYPES = ['jpeg', 'png']
6+
const BLUR_QUALITY = 70
7+
const VALID_BLUR_EXT = ['jpeg', 'png', 'webp']
78

89
async function nextImageLoader(content) {
910
const context = this.rootContext
@@ -21,8 +22,8 @@ async function nextImageLoader(content) {
2122

2223
const imageSize = sizeOf(content)
2324
let blurDataURL
24-
if (VALID_IMAGE_TYPES.includes(extension)) {
25-
// Shrink the image's largest dimension to 6 pixels
25+
if (VALID_BLUR_EXT.includes(extension)) {
26+
// Shrink the image's largest dimension
2627
const resizeOperationOpts =
2728
imageSize.width >= imageSize.height
2829
? { type: 'resize', width: BLUR_IMG_SIZE }
@@ -31,7 +32,7 @@ async function nextImageLoader(content) {
3132
content,
3233
[resizeOperationOpts],
3334
extension,
34-
70
35+
BLUR_QUALITY
3536
)
3637
blurDataURL = `data:image/${extension};base64,${resizedImage.toString(
3738
'base64'

packages/next/client/image.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -329,9 +329,7 @@ export default function Image({
329329
)}`
330330
)
331331
}
332-
if (staticImageData.blurDataURL) {
333-
blurDataURL = staticImageData.blurDataURL
334-
}
332+
blurDataURL = blurDataURL || staticImageData.blurDataURL
335333
staticSrc = staticImageData.src
336334
if (!layout || layout !== 'fill') {
337335
height = height || staticImageData.height
@@ -385,8 +383,17 @@ export default function Image({
385383
)
386384
}
387385
if (!blurDataURL) {
386+
const VALID_BLUR_EXT = ['jpeg', 'png', 'webp'] // should match next-image-loader
387+
388388
throw new Error(
389-
`Image with src "${src}" has "placeholder='blur'" property but is missing the "blurDataURL" property.`
389+
`Image with src "${src}" has "placeholder='blur'" property but is missing the "blurDataURL" property.
390+
Possible solutions:
391+
- Add a "blurDataURL" property, the contents should be a small Data URL to represent the image
392+
- Change the "src" property to a static import with one of the supported file types: ${VALID_BLUR_EXT.join(
393+
','
394+
)}
395+
- Remove the "placeholder" property, effectively no blur effect
396+
Read more: https://nextjs.org/docs/messages/placeholder-blur-data-url`
390397
)
391398
}
392399
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import React from 'react'
2+
import Image from 'next/image'
3+
import testBMP from '../public/test.bmp'
4+
5+
const Page = () => {
6+
return (
7+
<div>
8+
<Image
9+
id="invalid-placeholder-blur-static"
10+
src={testBMP}
11+
placeholder="blur"
12+
/>
13+
</div>
14+
)
15+
}
16+
17+
export default Page

test/integration/image-component/default/pages/static.js

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,13 @@ import Image from 'next/image'
44

55
import testJPG from '../public/test.jpg'
66
import testPNG from '../public/test.png'
7+
import testWEBP from '../public/test.webp'
78
import testSVG from '../public/test.svg'
89
import testGIF from '../public/test.gif'
910
import testBMP from '../public/test.bmp'
1011
import testICO from '../public/test.ico'
11-
import testWEBP from '../public/test.webp'
1212

1313
import TallImage from '../components/TallImage'
14-
const testFiles = [
15-
testJPG,
16-
testPNG,
17-
testSVG,
18-
testGIF,
19-
testBMP,
20-
testICO,
21-
testWEBP,
22-
]
23-
2414
const Page = () => {
2515
return (
2616
<div>
@@ -46,9 +36,14 @@ const Page = () => {
4636
width="400"
4737
height="300"
4838
/>
49-
{testFiles.map((f, i) => (
50-
<Image id={`format-test-${i}`} key={i} src={f} placeholder="blur" />
51-
))}
39+
<br />
40+
<Image id="blur-png" src={testPNG} placeholder="blur" />
41+
<Image id="blur-jpg" src={testJPG} placeholder="blur" />
42+
<Image id="blur-webp" src={testWEBP} placeholder="blur" />
43+
<Image id="static-svg" src={testSVG} />
44+
<Image id="static-gif" src={testGIF} />
45+
<Image id="static-bmp" src={testBMP} />
46+
<Image id="static-ico" src={testICO} />
5247
</div>
5348
)
5449
}

test/integration/image-component/default/test/index.test.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,18 @@ function runTests(mode) {
494494
)
495495
})
496496

497+
it('should show invalid placeholder when blurDataUrl is missing', async () => {
498+
const browser = await webdriver(
499+
appPort,
500+
'/invalid-placeholder-blur-static'
501+
)
502+
503+
expect(await hasRedbox(browser)).toBe(true)
504+
expect(await getRedboxHeader(browser)).toMatch(
505+
/Image with src "(.*)bmp" has "placeholder='blur'" property but is missing the "blurDataURL" property/
506+
)
507+
})
508+
497509
it('should warn when using a very small image with placeholder=blur', async () => {
498510
const browser = await webdriver(appPort, '/small-img-import')
499511

test/integration/image-component/default/test/static.test.js

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,12 @@ const indexPage = new File(join(appDir, 'pages/static.js'))
2222
const runTests = () => {
2323
it('Should allow an image with a static src to omit height and width', async () => {
2424
expect(await browser.elementById('basic-static')).toBeTruthy()
25-
expect(await browser.elementById('format-test-0')).toBeTruthy()
26-
expect(await browser.elementById('format-test-1')).toBeTruthy()
27-
expect(await browser.elementById('format-test-2')).toBeTruthy()
28-
expect(await browser.elementById('format-test-3')).toBeTruthy()
29-
expect(await browser.elementById('format-test-4')).toBeTruthy()
30-
expect(await browser.elementById('format-test-5')).toBeTruthy()
31-
expect(await browser.elementById('format-test-6')).toBeTruthy()
25+
expect(await browser.elementById('blur-png')).toBeTruthy()
26+
expect(await browser.elementById('blur-jpg')).toBeTruthy()
27+
expect(await browser.elementById('static-svg')).toBeTruthy()
28+
expect(await browser.elementById('static-gif')).toBeTruthy()
29+
expect(await browser.elementById('static-bmp')).toBeTruthy()
30+
expect(await browser.elementById('static-ico')).toBeTruthy()
3231
})
3332
it('Should automatically provide an image height and width', async () => {
3433
expect(html).toContain('width:400px;height:300px')

0 commit comments

Comments
 (0)