Skip to content

Commit 9c65c99

Browse files
authored
Add next/image default loader errors (#18152)
* Add next/image default loader errors * Add domains check * Apply suggestions from PR * Update test
1 parent 33eac8a commit 9c65c99

File tree

5 files changed

+97
-2
lines changed

5 files changed

+97
-2
lines changed

packages/next/build/webpack-config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -994,6 +994,12 @@ export default async function getBaseWebpackConfig(
994994
sizes: config.images.sizes,
995995
path: config.images.path,
996996
loader: config.images.loader,
997+
...(dev
998+
? {
999+
// pass domains in development to allow validating on the client
1000+
domains: config.images.domains,
1001+
}
1002+
: {}),
9971003
}),
9981004
'process.env.__NEXT_ROUTER_BASEPATH': JSON.stringify(config.basePath),
9991005
'process.env.__NEXT_HAS_REWRITES': JSON.stringify(hasRewrites),

packages/next/client/image.tsx

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type ImageData = {
1717
sizes: number[]
1818
loader: LoaderKey
1919
path: string
20+
domains?: string[]
2021
}
2122

2223
type ImageProps = Omit<
@@ -34,7 +35,12 @@ type ImageProps = Omit<
3435
)
3536

3637
const imageData: ImageData = process.env.__NEXT_IMAGE_OPTS as any
37-
const { sizes: configSizes, loader: configLoader, path: configPath } = imageData
38+
const {
39+
sizes: configSizes,
40+
loader: configLoader,
41+
path: configPath,
42+
domains: configDomains,
43+
} = imageData
3844
configSizes.sort((a, b) => a - b) // smallest to largest
3945
const largestSize = configSizes[configSizes.length - 1]
4046

@@ -349,6 +355,42 @@ function cloudinaryLoader({ root, src, width, quality }: LoaderProps): string {
349355
}
350356

351357
function defaultLoader({ root, src, width, quality }: LoaderProps): string {
358+
if (process.env.NODE_ENV !== 'production') {
359+
const missingValues = []
360+
361+
// these should always be provided but make sure they are
362+
if (!src) missingValues.push('src')
363+
if (!width) missingValues.push('width')
364+
365+
if (missingValues.length > 0) {
366+
throw new Error(
367+
`Next Image Optimization requires ${missingValues.join(
368+
', '
369+
)} to be provided. Make sure you pass them as props to the \`next/image\` component. Received: ${JSON.stringify(
370+
{ src, width, quality }
371+
)}`
372+
)
373+
}
374+
375+
if (src && !src.startsWith('/') && configDomains) {
376+
let parsedSrc: URL
377+
try {
378+
parsedSrc = new URL(src)
379+
} catch (err) {
380+
console.error(err)
381+
throw new Error(
382+
`Failed to parse "${src}" if using relative image it must start with a leading slash "/" or be an absolute URL`
383+
)
384+
}
385+
386+
if (!configDomains.includes(parsedSrc.hostname)) {
387+
throw new Error(
388+
`Invalid src prop (${src}) on \`next/image\`, hostname is not configured under images in your \`next.config.js\``
389+
)
390+
}
391+
}
392+
}
393+
352394
return `${root}?url=${encodeURIComponent(src)}&w=${width}&q=${
353395
quality || '100'
354396
}`
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import React from 'react'
2+
import Image from 'next/image'
3+
4+
const Page = () => {
5+
return (
6+
<div>
7+
<p>Hello World</p>
8+
<Image src="https://google.com/test.png" unsized></Image>
9+
</div>
10+
)
11+
}
12+
13+
export default Page
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import React from 'react'
2+
import Image from 'next/image'
3+
4+
const Page = () => {
5+
return (
6+
<div>
7+
<Image width={200}></Image>
8+
</div>
9+
)
10+
}
11+
12+
export default Page

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {
88
nextStart,
99
nextBuild,
1010
check,
11+
hasRedbox,
12+
getRedboxHeader,
1113
} from 'next-test-utils'
1214
import webdriver from 'next-webdriver'
1315
import fs from 'fs-extra'
@@ -20,7 +22,7 @@ const nextConfig = join(appDir, 'next.config.js')
2022
let appPort
2123
let app
2224

23-
function runTests() {
25+
function runTests(mode) {
2426
it('should load the images', async () => {
2527
let browser
2628
try {
@@ -79,6 +81,26 @@ function runTests() {
7981
}
8082
}
8183
})
84+
85+
if (mode === 'dev') {
86+
it('should show missing src error', async () => {
87+
const browser = await webdriver(appPort, '/missing-src')
88+
89+
await hasRedbox(browser)
90+
expect(await getRedboxHeader(browser)).toContain(
91+
'Next Image Optimization requires src to be provided. Make sure you pass them as props to the `next/image` component. Received: {"width":1200}'
92+
)
93+
})
94+
95+
it('should show invalid src error', async () => {
96+
const browser = await webdriver(appPort, '/invalid-src')
97+
98+
await hasRedbox(browser)
99+
expect(await getRedboxHeader(browser)).toContain(
100+
'Invalid src prop (https://google.com/test.png) on `next/image`, hostname is not configured under images in your `next.config.js`'
101+
)
102+
})
103+
}
82104
}
83105

84106
describe('Image Component Tests', () => {

0 commit comments

Comments
 (0)