New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Render icons on server side in NextJS 13 #234
Comments
It is intended behaviour. Component renders span before it is mounted to avoid breaking hydration. If component renders different content on server and client sides, it would result in React failing to hydrate content and throw an error. Rendering identical content on server and client sides is not always possible. Many icons use unique ids for masks, clip path, animations, reusable elements. IDs are supposed to be unique, so to avoid errors, component randomises those IDs for each render. This guarantees that two icons do not have same IDs. This also means that content is likely to be different on each render. To avoid breaking stuff, component renders span before it is actually mounted. I recommend using Unplugin Icons for that instead of Iconify components: https://github.com/antfu/unplugin-icons Unplugin Icons are designed to do exactly what you need: generate components for icons that are bundled, no extra overhead. |
@cyberalien Couldn't this be solved with React Server Components? OP mentioned using Next.js' app router, it seems like there could be a RSC-friendly version of the Icon component that doesn't require client rendering logic. |
Probably. However, it is a new thing and compatibility is a big issue. It needs to work in recent versions of React and all frameworks based on React, not just Next.js. |
After thinking more about it, I think it is time to rewrite component, targeting only latest React. Devs using older version can use older version of icon component. |
Good idea. I suppose solving the hydration problem is still necessary for folks who want to use it in Client Components and/or other frameworks with latest React. I believe |
Revisited this issue and just noticed that in code sample in first post it uses So I'm very confused. Issue mentions behaviour that applies to
|
Perhaps it was a mistake in their example. On my end when using the latest Next.js with app router:
"use client";
export { Icon } from "@iconify/react";
"use client";
export { Icon } from "@iconify-icon/react"; In both cases, using the Icon component as a client-component causes it to visibly "pop-in" and after the page hydrates. My desired behavior would be to use the Icon component without wrapping in |
If you want SVG content included in pre-rendered HTML, you are using wrong component. Both these components are designed to load icon data on demand. One of core functionalities is that nothing is rendered on server, icon data is not sent from server, but loaded from Iconify API as needed. Solution is to use Unplugin Icons instead. It generates simple components, which are rendered on server: https://github.com/antfu/unplugin-icons |
I wish there was a solution that wasn't quite so intrusive to use. Although it renders the desired SVG output, with Unplugin Icons I have to:
Since it uses a Webpack plugin, it's also not compatible with Next.js's It's one of those things you add to a project and then forget how to work around its quirks a month later. What I'd really like is a regular React component that "just works" with Iconify's icon sets without any additional compiler config or special import syntax. |
Would something simple, like Then usage would be like this: import { DiscordIcon } from '@iconify-react/logos';
function Whatever() {
return <div><DiscordIcon /></div>;
} or something like that (suggestions are welcome)? |
I am facing the exact same problem and as the issue is open and the conversation ended up with a question. |
I was showing an error when rendering on the client side, I used |
Update: I created an empty component that uses /components/providers/icons-bundle.provider.tsx 'use client'
import '@/lib/icons-bundle/icons-bundle' // for client components
export function IconsBundleProvider() {
return <></>
} /app/layout.tsx import '@/lib/icons-bundle/icons-bundle' // for server components
export default function RootLayout({ children }: React.PropsWithChildren) {
return (
<html lang="en">
<body>
{children}
<IconsBundleProvider />
</body>
</html>
)
} |
okay, but the problem in SSR or Next.js app router is layout shift. layout-shift.movso I simply wrapped
import React, { useEffect, useState } from 'react';
import { Icon as RealIcon, IconProps } from '@iconify/react';
export function Icon(props: IconProps) {
const [mounted, setMounted] = useState<boolean>(false);
useEffect(() => {
setMounted(true);
}, []);
return mounted ? (
<RealIcon {...props} />
) : (
<span
style={{
width: props.width || 20,
height: props.width || 20,
}}
>
<RealIcon {...props} />
</span>
);
} Result no-layout-shift.mov |
Published new version of Currently available as Long overdue. Sorry for delay. It can render icons on server side if data is provided. You can provide data by using |
I'm trying to bundle icons when building instead of loading them on demand on client side.
The iconify documentation mentions "providing icon data as parameter instead of icon name"
I've tried the following
but this still returns an empty
<span></span>
which is then filled on the client side.Is it possible to server-side render these icons with NextJS' app dir?
The text was updated successfully, but these errors were encountered: