-
-
Notifications
You must be signed in to change notification settings - Fork 205
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
I am using nextjs 13, typescript and tailwind and get this error of hydration #169
Comments
You just need to make sure the component is loaded on client side using an
Or you can use a simple version from this pull request updated document |
The problem is that the HTML that is rendered on the server is not the same as the HTML rendered on the client. Hence you see the warning I'm using Next 13 with the new export default function RootLayout({ children }: RootLayoutProps) {
return (
<html
lang="en"
style={{ colorScheme: 'light' }} // <--
className={classNames(fontSans.variable, 'light')} // <--
>
<body className="min-h-screen">
<ThemeProvider attribute="class" defaultTheme="light">
{children}
</ThemeProvider>
</body>
</html>
);
} However, the real problem is when user preferences change. Next Theme is storing the preferred theme in A possible solution is to use cookies or store the preference to a database (which you can fetch server-side). Edit |
That works momentarily, but yes, the problem comes when the user wants to choose the theme, hopefully this hydratation issue will be solved without having to use |
hard coding |
This work for me after folowing this #152 (comment)
Providers.tsx "use client";
import { ThemeProvider } from "next-themes";
import { useEffect, useState } from "react";
function Providers({ children }: { children: React.ReactNode }) {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return null;
}
return (
<ThemeProvider attribute="class" enableSystem={true}>
{children}
</ThemeProvider>
);
}
export default Providers; layout.tsx import "../globals.css";
import { Inter} from "next/font/google";
import Header from "@/components/Header";
import Providers from "../Providers";
const sg = Inter({ subsets: ["latin"], display: "swap" });
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="id">
<Providers>
<body className={sg.className}>
<Header />
<div className="pt-16">{children}</div>
</body>
</Providers>
</html>
);
}
DarkToggle.tsx "use client"
import React, { useState, useEffect } from "react";
import { useTheme } from "next-themes";
import { RiSunLine, RiMoonClearLine } from "react-icons/ri";
function DarkToggle() {
const [mounted, setMounted] = useState(false);
const [isDark, setIsDark] = useState(false);
const { theme, setTheme } = useTheme();
useEffect(() => {
setMounted(true);
setIsDark(theme === "dark");
}, [theme]);
if (!mounted) {
return null;
}
const toggleDarkMode = () => {
setIsDark(!isDark);
setTheme(theme === "light" ? "dark" : "light");
};
return (
<button onClick={toggleDarkMode} className="flex items-center focus:outline-none"
>
{isDark ? (
<div className="mr-2 text-2xl text-yellow-400"><RiSunLine /></div>
) : (
<div className="mr-2 text-2xl text-gray-700">
<span className="text-yellow-400"><RiMoonClearLine /></span>
</div>
)}
</button>
);
}
export default DarkToggle; |
You absolutely don't want to do that. By doing that, you are essentially completely eliminating server side rendering. So this doesn't solve the problem, it just avoids it by dropping the ball on all the good stuff :-P |
SSR works for me via the fork mentioned here |
Returning null until mounted is not an acceptable solution, it results in a noticeable delay in rendering, and looks clunky. I have yet to find a solution for a Theme toggle button that actually functions as it should. What I have below actually works perfectly on the client: I initially set a separate variable "localTheme" using localStorage. After the initial render I go back to using next-themes "setTheme" to toggle. However, this works fine on the client but throws an error on the server because it has no "window" object. I am not sure why it's trying to run anything on the server, as I've marked this with "use client".
|
In NextJS 13 it is possible for the server to read user cookies natively, if this library was modified so it's using cookies instead of local storage this would fix the hydration problem completely. |
Any updates on this? I still get the error :/ |
Not a fix for the hydration error, but if you are just doing this “isMounted” workaround to implement dark mode, there is an easy alternative. just use TailwindCSS classes, which use CSS media queries behind the scenes. “dark:border-white” for example. If you are using different components, or different background images, use “dark:hidden” and easily manipulate what is displayed without waiting for components to mount. |
Hmm, I am using tailwind, but I am not sure what you mean exactly. The My best guess from what others have said is that we need to have the server involved by setting cookies to the users theme preference (and somehow to their system preference by default). |
Using a |
Worked as a charm. Thanks. |
After spending a few days with search and trial and error, I managed to resolve the hydration mismatch error in my Next.js 13 application. While it seems to resolve the issue effectively, there may be potential drawbacks that I'm currently unaware of. Here's the strategy I employed, : Set a Default Theme on First Visit: To avoid inconsistencies between server-rendered and client-rendered content, I set a default theme for all first-time visitors. This means I bypassed checking the system's theme preference on the initial load to prevent discrepancies. Toggle Theme with Next-Themes: For subsequent theme changes after the initial load, I utilized the next-themes package to handle theme toggling, storing the chosen theme in the localStorage. Syncing Theme Preference with Cookies: When a user toggles the theme, I simultaneously set a cookie containing the current theme preference. This ensures that if the user refreshes the page or returns later, the server can reference this cookie to deliver content with the correct theme applied right from the initial server render. This approach effectively eliminates the hydration mismatch error while providing a seamless user experience across sessions. This is my ThemeButton.tsx
This is my provider.tsx
and finally my layout.tsx in app directory
|
+1 following - is there a fix on the way? |
thanks @kerden97 - that's worked a treat |
Yeah it doesn't fix the bug (hopefully sometime in the future this will be fixed), but adding suppressHydrationWarning will solve the pain in the arse console message. As stated in the React documentation (https://legacy.reactjs.org/docs/dom-elements.html) the suppressHydrationWarning flag: 'only works one level deep', so it won't effect any other hydration warnings you have in your code. I tested this by adding {new Date().toString()} below the tag where I have the suppressHydrationWarning attribute and it still gave me a hydration failure as expected.
|
Same warning, which i think the main reason is that there are some differences between the Server generate html and client init html. Under the Here is my solution, the main logic is use the "use client";
export const useMounted = (): boolean => {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
return mounted;
}; "use client";
import { useMounted } from "@/utils/hooks";
function ThemeSwitcher() {
const { theme: current, setTheme } = useTheme();
const mounted = useMounted();
return (
<div title="Change Theme" className="dropdown dropdown-end">
// ...
{themes.map((theme) => (
<ThemeOption
theme={theme}
key={theme}
setTheme={setTheme}
isChecked={mounted ? false : (current === theme)}
/>
))}
// ...
</div>
);
} |
i guess the problem is laying on rendering with data-theme attr on server side is not working, however next-themes is essentially setting a data-theme attr on .
|
Following the current docs always ends up with hydration errors. I used the solution here pacocoursey/next-themes#169 (comment) to address it. I migrated suppressHydrationWarning to body instead as it fixed a lot of miscellaneous browser extensions from causing an "Extra attributes from the server" error. This solution: vercel/next.js#22388 (reply in thread)
Any update on this? Plans to get this fixed? |
Please fix. |
The error is gone when I putted the const RootLayout = ({ children }: { children: React.ReactNode }) => {
return (
<html lang="en" className={styles.html()} suppressHydrationWarning>
<body className={styles.body()}>
<ThemeProvider>{children}</ThemeProvider>
</body>
</html>
)
} |
If you followed the steps from the documentation to add dark mode to your app and encountered this problem, here's a solution inspired by the one provided by @Yrobot useMounted.ts 'use client'
import { useEffect, useState } from 'react'
export const useMounted = (): boolean => {
const [mounted, setMounted] = useState(false)
useEffect(() => {
setMounted(true)
}, [])
return mounted
} theme-provider.tsx 'use client'
import { useMounted } from '@/lib/hooks/useMounted'
import { ThemeProvider as NextThemesProvider } from 'next-themes'
import { type ThemeProviderProps } from 'next-themes/dist/types'
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
const mounted = useMounted()
return mounted && <NextThemesProvider {...props}>{children}</NextThemesProvider>
} |
@ImEins this causes your entire page to flash, which defeats the purpose of SSR. If you want to display an icon depending on the theme, use 'use client'
import { useMounted } from '@/lib/hooks/use-mounted'
import { cn } from '@/lib/utils'
import { MoonIcon, SunIcon } from 'lucide-react'
import { useTheme } from 'next-themes'
export function ThemeTogglerButton({ className }: { className?: string }) {
const isMounted = useMounted()
const { resolvedTheme, setTheme } = useTheme()
const toggleTheme = () => {
setTheme(resolvedTheme === 'dark' ? 'light' : 'dark')
}
const Icon = resolvedTheme === 'dark' && isMounted ? MoonIcon : SunIcon
return (
<button
className={cn(className)}
title="Toggle Light/Dark Mode"
aria-label="Toggle Light/Dark Mode"
type="button"
onClick={toggleTheme}
>
{!isMounted && <span className="block mr-2 h-5 w-5 animate-pulse rounded-full" />}
{isMounted && <Icon className={cn('mr-2 h-5 w-5 opacity-100 starting:opacity-0 transition duration-500')} />}
</button>
)
}
|
Please read the docs: https://github.com/pacocoursey/next-themes?tab=readme-ov-file#with-app |
This error is not in the console in deployment version but shows in localhost.
In code when I comment the ThemeProvider the error is gone
react_devtools_backend.js:2655 Warning: Extra attributes from the server: class,style
at html
at ScrollAndFocusHandler (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/layout-router.js:153:1)
at InnerLayoutRouter (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/layout-router.js:195:11)
at RedirectErrorBoundary (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/layout-router.js:361:9)
at RedirectBoundary (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/layout-router.js:368:11)
at NotFoundBoundary (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/layout-router.js:404:11)
at LoadingBoundary (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/layout-router.js:317:11)
at ErrorBoundary (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/error-boundary.js:72:11)
at RenderFromTemplateContext (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/render-from-template-context.js:12:34)
at OuterLayoutRouter (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/layout-router.js:23:11)
at ReactDevOverlay (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/react-dev-overlay/internal/ReactDevOverlay.js:61:9)
at HotReload (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/react-dev-overlay/hot-reloader-client.js:20:11)
at Router (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/app-router.js:48:11)
at ErrorBoundaryHandler (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/error-boundary.js:59:9)
at ErrorBoundary (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/error-boundary.js:72:11)
at AppRouter (webpack-internal:///(app-client)/./node_modules/next/dist/client/components/app-router.js:24:13)
at ServerRoot (webpack-internal:///(app-client)/./node_modules/next/dist/client/app-index.js:147:11)
at RSCComponent
at Root (webpack-internal:///(app-client)/./node_modules/next/dist/client/app-index.js:164:11)
The text was updated successfully, but these errors were encountered: