Next.js Discord

Discord Forum

How to prevent UI flickering when using useEffect to change styles on page load

Unanswered
Polar bear posted this in #help-forum
Open in Discord
Avatar
Polar bearOP
I'm implementing a header in my Next.js app where the background color changes based on the scroll position: if window.scrollY is 0, the background is transparent. Otherwise, it changes to a solid color. I’m using a useEffect hook to set up the scroll listener and adjust the background color state accordingly.

The problem is that if a user refreshes the page while scrolled down, the header would initially be rendered with transparent background even if the initial scrollY is greater than 0 then changed to white. This happens because the color changing logic inside useEffect runs after the component mounts, causing a delay before it checks the scroll position.

How can I avoid this flicker effect and make sure the header displays the correct background color immediately on page load without any delay?

"use client"
export default function Header() {
  useEffect(() => {
    const handleScrollChange = () => {
      const header = document.querySelector("header");
      if (window.scrollY > 0) {
        header?.classList.remove("bg-transparent");
        header?.classList.add("bg-white");
      } else {
        header?.classList.remove("bg-white");
        header?.classList.add("bg-transparent");
      }
    };
    handleScrollChange();
    window.addEventListener("scroll", handleScrollChange);
    return () => window.removeEventListener("scroll", handleScrollChange);
  }, []);

  return (
    <header className="sticky w-full top-0 z-10">
      ...
    </header>
  );
}

22 Replies

Avatar
Polar bearOP
Here's the demo
Image
Avatar
@Polar bear can you try next/dynamic with ssr: false when you load your header component?
Avatar
Polar bearOP
like this?
const Header = dynamic(() => import("@/components/header"), {ssr: false})

...
return (
  ...
  <Header />
  ...
)
Avatar
yeah, exactly
Avatar
Polar bearOP
That makes the whole header lazy loaded with even more delay
Avatar
yeah, ofc. you would need a skeleton UI while it's loading
but once it's loaded you won't have flickering
you know Next.js does SSR and on the server side there is no window object => that's causing flickering, right?
Avatar
Polar bearOP
yeah
I tried adding script using next/script to change the style but it doesn't work in next.js
Avatar
make a skeleton (clone your header assuming window.scrollY = 0) and then use it as a loader
Avatar
Polar bearOP
doesn't that create the same problem since the background color of skeleton can't be determined on the server?
I don't get it. can you give me a code example?
Avatar
ah, my bad @Polar bear I couldn't look through your code carefully.
so what you can do is hmm
let me share the codebase
"use client"
import { useEffect, useState } from "react";

export default function Header() {
  const [isScrolled, setIsScrolled] = useState(false);

  useEffect(() => {
    const handleScrollChange = () => {
      setIsScrolled(window.scrollY > 0);
    };

    handleScrollChange(); // Initial check on mount
    window.addEventListener("scroll", handleScrollChange);

    return () => window.removeEventListener("scroll", handleScrollChange);
  }, []);

  return (
    <header
      className={`sticky w-full top-0 z-10 transition-colors ${
        isScrolled ? "bg-white" : "bg-transparent"
      }`}
    >
      ...
    </header>
  );
}
try this instead
Avatar
Polar bearOP
That still causes flickering when refreshing page at window.scrollY > 0 (isScrolled should be true) since you assume that the initial isScrolled is false.
Avatar
sorry man, I have no idea then
Avatar
Polar bearOP
it's ok. good try🙏
Avatar
I mean, there won't be a perfect solution for this, if you don't want to long delay (my first approach)
Avatar
Polar bearOP
I think there's a solution. It's the same problem as doing dark mode without color flickering on page load, like what 'next-themes' does. I just don't understand how to implement