next-themes icon indicating copy to clipboard operation
next-themes copied to clipboard

next/image example does not work after the page refresh

Open jeffminsungkim opened this issue 1 year ago • 8 comments

I have added a few more code to wait for the client to render the component.

import Image from 'next/image'
import { useTheme } from 'next-themes'
import { useEffect, useState } from 'react'

function ThemedImage() {
  const { resolvedTheme } = useTheme()
  const [mounted, setMounted] = useState(false)

  useEffect(() => {
    setMounted(true)
  }, [])

  if (!mounted) {
    return null
  }

  let src

  switch (resolvedTheme) {
    case 'light':
      src = '/light.png'
      break
    case 'dark':
      src = '/dark.png'
      break
    default:
      src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
      break
  }

  return <Image src={src} width={400} height={400} />
}

export default ThemedImage

It works fine but not perfect. I want the image to stay still. However, when I refresh the page, the image starts to flicker on every page render. Any solutions?

jeffminsungkim avatar Nov 21 '22 06:11 jeffminsungkim

Can you share a minimal deployed reproduction?

pacocoursey avatar Nov 23 '22 15:11 pacocoursey

@pacocoursey I've added a short clip to demonstrate the issue. This is what it looks like when the page has refreshed.

https://user-images.githubusercontent.com/6092023/204434855-cf447ebc-1c6f-4fce-9bf7-85edd243ec8b.mp4

The reason why I've upload the clip is because the CodeSandbox has a delay when I click the refresh button. But I'm pretty sure that you will get the point.

CodeSandbox demo

jeffminsungkim avatar Nov 29 '22 03:11 jeffminsungkim

@pacocoursey Any ideas? 🤔

jeffminsungkim avatar Dec 08 '22 16:12 jeffminsungkim

I have the same problem (I use tailwind too). After first page load/refresh, everything updates to dark mode but not the images.

keyur555 avatar Dec 13 '22 13:12 keyur555

I've been experiencing this problem too. Hoping to get help with it. What I've done (and baybe can throw a bit of help in here) is to use svg or png logos for the header, that way I can control with tailwind (for the SVG case) the color with light and dark themes, and if it's a PNG the background will be transparent (this doesn't work for all use cases though).

ismaeljtl avatar Jan 05 '23 14:01 ismaeljtl

Okay, after a bit of investigation and thinking I think I have a solution, want to share it woth you guys so you can tell me if you think is okay:

I'm building a website where I'm displaying images based on the theme of the website. For components as the header and footer I'm using an SVG element to display the logo of the site. With this technique this specific case problem is solved, because I'm using Tailwind, which let's you set the light and dark colors with the currentTheme property as the following code shows:

<div className="text-blue-700 dark:text-blue-100">
      <svg
        fill="none"
        xmlns="http://www.w3.org/2000/svg"
      >
</div>

The problem comes with images. The mismatch error occurs because in the server we don't have a certain way to know what's the theme the browser will have, so when rendering the component in the server, there will always be a chance that the image displayed in the server doesn't match the one shown in the client (that's why I think it's best to use SVG elements in this kind of apps whenever we can). But there are times where we cannot change the image for an SVG element, for those specific cases what I've done (and I want your opinion on this) is to not show the image at all when the page is not rendered in the client. I don't know if this can cause my page not to rank higher due to SEO or something like that, but with this I'm not getting errors in my project, here is a look at how my component looks like:

const ThemedImage: FC<{ lightImage: IImage; darkImage: IImage }> = ({
  lightImage,
  darkImage,
}) => {
  const { resolvedTheme } = useTheme();
  const [mounted, setMounted] = useState(false);
  let srcImage: IImage;

  useEffect(() => {
    setMounted(true);
  }, []);

  if (!mounted) {
    return null;
  }

  switch (resolvedTheme) {
    case "light":
      srcImage = lightImage;
      break;
    case "dark":
      srcImage = darkImage;
      break;
    default:
      srcImage = lightImage;
      break;
  }

  return (
    <Image
      className="rounded-md"
      src={srcImage.url}
      width={srcImage.width}
      height={srcImage.height}
      alt={srcImage.description}
    />
  );
};

export default ThemedImage;

What do you guys think about this solution?

ismaeljtl avatar Jan 05 '23 15:01 ismaeljtl

@ismaeljtl Thanks for the response with some ideas. Let me go through your solution first and then I'll leave feedback. :) Btw, do you have a link for the reproduction?

jeffminsungkim avatar Jan 20 '23 08:01 jeffminsungkim