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