next.js
next.js copied to clipboard
`next/image` is unusable in Server Components with `loader` function
Verify canary release
- [X] I verified that the issue exists in the latest Next.js canary release
Provide environment information
Operating System:
Platform: darwin
Arch: x64
Version: Darwin Kernel Version 21.6.0: Mon Aug 22 20:17:10 PDT 2022; root:xnu-8020.140.49~2/RELEASE_X86_64
Binaries:
Node: 16.13.1
npm: 8.1.2
Yarn: 1.22.19
pnpm: 7.1.7
Relevant packages:
next: 13.0.0
eslint-config-next: 13.0.0
react: 18.2.0
react-dom: 18.2.0
What browser are you using? (if relevant)
No response
How are you deploying your application? (if relevant)
No response
Describe the Bug
When using next/image
with a custom loader in a Server Component we get the following error:
Uncaught Error: Functions cannot be passed directly to Client Components because they're not serializable.
<... loader={function} src=... alt=...>
^^^^^^^^^^
Expected Behavior
I expect a custom loader to be usable with server components.
Link to reproduction
https://codesandbox.io/s/cocky-bohr-g3tqkt?file=/app/post/ImageWrapper.tsx:275-287
To Reproduce
Go to /post
in the provided codesandbox.
Getting the same thing, to get the image working it has to be a client component which is not what I want.
Not optimal but as a workaround it seems to work if you set the loader in next.config.js
file
images: {
loader: 'custom',
loaderFile: './loader.tsx',
...
}
https://nextjs.org/docs/api-reference/next/image#loader-configuration
next/image
has to be a client component. It uses JS to detect when the image has loaded. Although I agree that it would be nice to be able to create a shell around next/image
using loader
which isn't a client component (perhaps by allowing a loader template string $src?q=$q
), it won't save you much. The only thing it saves is that the loader function doens't end up in your JS bundle.
Keep in mind that client components are still server-side rendered, so requiring next/image
to be a client component doesn't make it less usable.
I create a repo to reproduce this bug https://github.com/jee-r/image-loader-test GH action build logs : https://github.com/jee-r/image-loader-test/actions/runs/4632100214/jobs/8195761620
as you can see in this branch if we set the loader in next.config.js
https://github.com/jee-r/image-loader-test/commit/51ae06d7a1729abe233f31352920ed9a0595e058
build has succeeded https://github.com/jee-r/image-loader-test/actions/runs/4632232545
next/image
is a Client Component, which is why you can't serialize a function from a server component to the client component. Creating your own client component around it is entirely fine, there's no performance difference in doing so. As said next/image
is already a Client Component, you just don't specifically see it is a Client Component. Also to clarify further Client Components are the same as components in pages
, they are pre-rendered to HTML, so the <img />
tags will be in the initial HTML.
TLDR: Client Components are not bad, they are the components that you already knew. They are pre-rendered as part of the initial HTML.
With this context, it’s entirely fine to create your own client component around it:
'use client'
import Image from 'next/image'
const imageLoader = ({ src, width, quality }) => {
return `https://example.com/${src}?w=${width}&q=${quality || 75}`
}
export default function Page() {
return (
<Image
loader={imageLoader}
src="me.png"
alt="Picture of the author"
width={500}
height={500}
/>
)
}
You can learn more in the documentation here: https://nextjs.org/docs/app/api-reference/components/image#loader. You can also set "loaderFile" globally: https://nextjs.org/docs/app/api-reference/components/image#loaderfile, however there is no performance win in doing so, it's purely convenience.