diff --git a/packages/next/src/shared/lib/get-img-props.ts b/packages/next/src/shared/lib/get-img-props.ts index 2f251f46761bc..f0fcad0cde358 100644 --- a/packages/next/src/shared/lib/get-img-props.ts +++ b/packages/next/src/shared/lib/get-img-props.ts @@ -285,6 +285,11 @@ export function getImgProps( config = { ...c, allSizes, deviceSizes } } + if (typeof defaultLoader === 'undefined') { + throw new Error( + 'images.loaderFile detected but the file is missing default export.\nRead more: https://nextjs.org/docs/messages/invalid-images-config' + ) + } let loader: ImageLoaderWithConfig = rest.loader || defaultLoader // Remove property so it's not spread on element diff --git a/test/e2e/app-dir/loader-file-named-export-custom-loader-error/app/get-img-props/page.tsx b/test/e2e/app-dir/loader-file-named-export-custom-loader-error/app/get-img-props/page.tsx new file mode 100644 index 0000000000000..011e726ef84b1 --- /dev/null +++ b/test/e2e/app-dir/loader-file-named-export-custom-loader-error/app/get-img-props/page.tsx @@ -0,0 +1,17 @@ +import { getImageProps } from 'next/image' + +export default function Page() { + const { props: imageProps } = getImageProps({ + id: 'logo', + alt: 'logo', + src: '/logo.png', + width: '400', + height: '400', + }) + + return ( +
+ +
+ ) +} diff --git a/test/e2e/app-dir/loader-file-named-export-custom-loader-error/app/layout.tsx b/test/e2e/app-dir/loader-file-named-export-custom-loader-error/app/layout.tsx new file mode 100644 index 0000000000000..e7077399c03ce --- /dev/null +++ b/test/e2e/app-dir/loader-file-named-export-custom-loader-error/app/layout.tsx @@ -0,0 +1,7 @@ +export default function Root({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ) +} diff --git a/test/e2e/app-dir/loader-file-named-export-custom-loader-error/app/page.tsx b/test/e2e/app-dir/loader-file-named-export-custom-loader-error/app/page.tsx new file mode 100644 index 0000000000000..7111ad0bd5512 --- /dev/null +++ b/test/e2e/app-dir/loader-file-named-export-custom-loader-error/app/page.tsx @@ -0,0 +1,9 @@ +import Image from 'next/image' + +export default function Page() { + return ( +

+ +

+ ) +} diff --git a/test/e2e/app-dir/loader-file-named-export-custom-loader-error/dummy-loader.ts b/test/e2e/app-dir/loader-file-named-export-custom-loader-error/dummy-loader.ts new file mode 100644 index 0000000000000..b79612d38546c --- /dev/null +++ b/test/e2e/app-dir/loader-file-named-export-custom-loader-error/dummy-loader.ts @@ -0,0 +1,3 @@ +export function dummyLoader({ src, width, quality }) { + return `/_next/image/?url=${src}&w=${width}&q=${quality || 50}` +} diff --git a/test/e2e/app-dir/loader-file-named-export-custom-loader-error/loader-file-named-export-custom-loader-error.test.ts b/test/e2e/app-dir/loader-file-named-export-custom-loader-error/loader-file-named-export-custom-loader-error.test.ts new file mode 100644 index 0000000000000..82e077b1a111b --- /dev/null +++ b/test/e2e/app-dir/loader-file-named-export-custom-loader-error/loader-file-named-export-custom-loader-error.test.ts @@ -0,0 +1,53 @@ +import { nextTestSetup } from 'e2e-utils' +import { getRedboxHeader, hasRedbox } from 'next-test-utils' + +const errorMessage = + 'images.loaderFile detected but the file is missing default export.\nRead more: https://nextjs.org/docs/messages/invalid-images-config' + +async function testDev(browser, errorRegex) { + expect(await hasRedbox(browser)).toBe(true) + expect(await getRedboxHeader(browser)).toMatch(errorRegex) +} + +describe('Error test if the loader file export a named function', () => { + describe('in Development', () => { + const { next, isNextDev } = nextTestSetup({ + skipDeployment: true, + files: __dirname, + }) + + ;(isNextDev ? describe : describe.skip)('development only', () => { + it('should show the error when using `Image` component', async () => { + const browser = await next.browser('/') + await testDev(browser, errorMessage) + }) + + it('should show the error when using `getImageProps` method', async () => { + const browser = await next.browser('/get-img-props') + await testDev(browser, errorMessage) + }) + }) + }) + + describe('in Build and Start', () => { + const { next, isNextStart } = nextTestSetup({ + skipDeployment: true, + skipStart: true, + files: __dirname, + }) + + // next build doesn't support turbopack yet + // see https://nextjs.org/docs/architecture/turbopack#unsupported-features + ;(isNextStart && !process.env.TURBOPACK ? describe : describe.skip)( + 'build and start only', + () => { + it('should show the build error', async () => { + await expect(next.start()).rejects.toThrow( + 'next build failed with code/signal 1' + ) + expect(next.cliOutput).toContain(errorMessage) + }) + } + ) + }) +}) diff --git a/test/e2e/app-dir/loader-file-named-export-custom-loader-error/next.config.js b/test/e2e/app-dir/loader-file-named-export-custom-loader-error/next.config.js new file mode 100644 index 0000000000000..77961243627b0 --- /dev/null +++ b/test/e2e/app-dir/loader-file-named-export-custom-loader-error/next.config.js @@ -0,0 +1,10 @@ +/** + * @type {import('next').NextConfig} + */ +const nextConfig = { + images: { + loaderFile: '/dummy-loader.ts', + }, +} + +module.exports = nextConfig diff --git a/test/e2e/app-dir/loader-file-named-export-custom-loader-error/public/logo.png b/test/e2e/app-dir/loader-file-named-export-custom-loader-error/public/logo.png new file mode 100644 index 0000000000000..e14fafc5cf3bc Binary files /dev/null and b/test/e2e/app-dir/loader-file-named-export-custom-loader-error/public/logo.png differ