Is it bad to make the html tag in layout.tsx a client component?
Unanswered
American Wirehair posted this in #help-forum
American WirehairOP
I want to set a
data-theme="red" property on the html tag, selected randomly on page load. Can i make a client component with the <html> tag, and do that there, or would that be a bad idea?39 Replies
American WirehairOP
html.tsx
layout.tsx
"use client";
import { useRef } from "react";
interface HtmlProps {
className?: string;
children?: React.ReactNode;
lang?: string;
}
const themeValues = ["red", "blue"] as const;
export function Html(props: HtmlProps) {
const theme = useRef(themeValues[Math.floor(Math.random() * themeValues.length)])
return (
<html data-theme={theme.current} lang={props.lang} className={props.className}>
{props.children}
</html>
);
}layout.tsx
return (
<Html
lang="en"
className={`${consort.variable} ${graebenbach.variable}`}
>
<body
className={`bg-white font-sans theme-green:text-green theme-blue:text-blue theme-red:text-red theme-brown:text-brown`}
>
<Header theme={"brown"} globals={globals} />
{children}
<Footer globals={globals} />
</body>
</Html>
);
}Is it bad to make the html tag in layout.tsx a client component?Yes, usually its not really a good practice to make layout/page.tsx a client component. it has shown to result in errors in the past.
i saw your code. im not sure why you need a client component for that.
do you want to create a static route or a dynamic route?
if dynamic route, dont need client component.
if static route, well, you can always display blank until the browser loaded the js and run your randomizer using useEffect that targets <html>
American WirehairOP
I'm trying to achieve "best of both worlds". Statically rendered website, but where the
data-theme is changed on each full page reload, so that the site displays different theme/colors. Also I want to avoid having to set the theme using useEffect as not have the website flash a color change (from default color to the random color from useEffect).well.. if you dont use useEffect then the theme will only get randomized once at build time :/
and re-randomized again at request-time
which would... also.. flash a color change
(from the default color to the random color from useRef re-running in client components)
soo.........
American WirehairOP
I could do a:
this will cause a hydration warning though.
<head>
<script dangerouslySetInnerHTML={{
__html: `
(function() {
const themes = ["red", "blue", "brown", "green"];
const theme = themes[Math.floor(Math.random() * themes.length)];
document.documentElement.setAttribute('data-theme', theme);
})();
`
}} />
</head>this will cause a hydration warning though.
these are all just the same idea and logic but different implementation
what are you trying to achieve?
why randomize theme?
American WirehairOP
That's the design from the agency I'm implementing. 🙂 They have various logo variants and color themes they want to show of. random on full page reload, but stable on client side navigation.
you can't avoid flash of white but you can avoid flash of color change
like i said, just display blank site if js is not loaded
or perhaps use PPR to have dynamic component in a static route
so that randomization happens on the server not on the client
if randomization happens on the server there would be no flash of anything
American WirehairOP
the script tag i posted above won't flash, as it will put the theme there on first render. But will create the hydration warning.
Current best solution is probably to make the entire site dynamic.
Current best solution is probably to make the entire site dynamic.
what is PPR?
@American Wirehair what is PPR?
partial pre-rendering, as much as the name not self-describe, it just allows you to add dynamic components while making the entire thing static
it needs you to use
experimenta.ppr: true or cacheComponent: trueAmerican WirehairOP
ohh, cool. I'll have to read up about that. Might be a solution to to cache stuff below it.
cache stuff above it*
i was thinking of just plopping <RandomizedTheme /> in the children of <body> (not affecting the rest of the page)
then just use CSS selector to select the theme from html
then just use CSS selector to select the theme from html
that way you dont need to rely on data-theme no more
American WirehairOP
Hmm, I have to test that out, and see if I fully understand it, and how it will work. 🙂
Another issue then is that I currently pass the theme as a prop to
I could hide them based on the same css selectors, but the logos have to be inline svg, and then all of them will be included in the html. Which is a lot!
Another issue then is that I currently pass the theme as a prop to
Header to conditionally render different svg logos. const themes = ["red", "blue", "green", "brown"] as const;
const theme = themes[Math.floor(Math.random() * themes.length)] || "blue";
return (
<html
lang="no"
data-theme={theme}
className={`${consort.variable} ${graebenbach.variable}`}
>
<body
>
<Header theme={theme} globals={globals} />I could hide them based on the same css selectors, but the logos have to be inline svg, and then all of them will be included in the html. Which is a lot!
header.tsx
interface HeaderProps {
globals: Globals;
theme: "brown" | "blue" | "red" | "green";
className?: string;
}
export function Header(props: HeaderProps) {
return (
<header className={clsx(props.className, "border-b")}>
<div className="h-32 py-4 lg:h-64 lg:py-8 flex items-center justify-center">
{Match.value(props.theme).pipe(
Match.when("brown", () => (
<>
<PALBrownAlt className="hidden lg:block" />
<PALBrownAltMobile className="lg:hidden" />
</>
)),
Match.when("blue", () => (
<>
<PALBlue className="hidden lg:block" />
<PALBlueMobile className="lg:hidden" />
</>
)),
Match.when("red", () => (
<>
<PALRed className="hidden lg:block" />
<PALRedMobile className="lg:hidden" />
</>
)),
Match.when("green", () => (
<>
<PALGreenAlt className="hidden lg:block" />
<PALGreenAltMobile className="lg:hidden" />
</>
)),
Match.exhaustive,
)}
</div>interesting. if you dont need to have the <svg> interactive, you can sandbox it and lazy load it using <img>. that way you wont be penalized for writing many svg at once
American WirehairOP
they use the
currentColor for fill. That's why they are inlined. 🙂 But that might not be needed.interesting.
for sake of simplicity, why not just use client component and do opacity-0 initially and transition to opacity-100 when js, and theme is loaded
American WirehairOP
Yeah, I could. Just not a fan of that. 😛 I'm researching all possibilities before I either do that, or
forced-dynamic.@American Wirehair I could do a:
<head>
<script dangerouslySetInnerHTML={{
__html: `
(function() {
const themes = ["red", "blue", "brown", "green"];
const theme = themes[Math.floor(Math.random() * themes.length)];
document.documentElement.setAttribute('data-theme', theme);
})();
`
}} />
</head>
this will cause a hydration warning though.
the hydration warning can be ignored as this is standard procedure if you want to have client-theme without fuoc just like how next-themes works (as in the suppressHydrationWarning prop)
not recommended to do
forced-dynamic :(assuming ure in next15, just write
await cookies() or something lolAmerican WirehairOP
didn't know about the next-themes. I might look at that for inspo. Yeah, I see they just supress the hydration warning. 🙂
Ok, I have quite a few options to look into. 😄
1. Cache Components: https://nextjs.org/docs/app/getting-started/cache-components
2. next-themes (just supress hydration warning and set the theme in a head script)
3. opt out of static generation
4. fade in page once js is loaded and theme is set
Thanks a lot for the chat and help. 🙂
1. Cache Components: https://nextjs.org/docs/app/getting-started/cache-components
2. next-themes (just supress hydration warning and set the theme in a head script)
3. opt out of static generation
4. fade in page once js is loaded and theme is set
Thanks a lot for the chat and help. 🙂
anytime!