Leon Gaban
Leon Gaban

Reputation: 39018

ThemeProvider from NextUI causing Warning: Extra attributes from the server: class,style

I've added NextUI to my NextJS 14 app

The issue has been isolated to the ThemeProvider in my main providers.tsx file:

'use client';

import { NextUIProvider } from '@nextui-org/react';
import { ThemeProvider as NextThemesProvider } from 'next-themes';

export default function Providers({ children }: { children: React.ReactNode }) {
  return (
    <NextUIProvider>
      <NextThemesProvider // <-- the issue
        attribute="class"
        defaultTheme="dark"
        themes={['light', 'dark', 'modern']}
      >
        {children}
      </NextThemesProvider>
    </NextUIProvider>
  );
}

The problem is this specifically, because if I remove it the error goes away:

<NextThemesProvider
    attribute="class"
    defaultTheme="dark"
    themes={['light', 'dark', 'modern']}
>

The warning

**Warning: Extra attributes from the server: class,style
    at html
    at RedirectErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/redirect-boundary.js:73:9)
    at RedirectBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/redirect-boundary.js:81:11)
    at NotFoundErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/not-found-boundary.js:76:9)
    at NotFoundBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/not-found-boundary.js:84:11)
    at DevRootNotFoundBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/dev-root-not-found-boundary.js:33:11)
    at ReactDevOverlay (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/react-dev-overlay/internal/ReactDevOverlay.js:84:9)
    at HotReload (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/react-dev-overlay/hot-reloader-client.js:307:11)
    at Router (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/app-router.js:182:11)
    at ErrorBoundaryHandler (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/error-boundary.js:114:9)
    at ErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/error-boundary.js:161:11)
    at AppRouter (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/app-router.js:538:13)
    at ServerRoot (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/app-index.js:129:11)
    at RSCComponent
    at Root (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/app-index.js:145:11)**

When I dive into the ThemeProvider type:

interface ThemeProviderProps {
    /** List of all available theme names */
    themes?: string[] | undefined;
    /** Forced theme name for the current page */
    forcedTheme?: string | undefined;
    /** Whether to switch between dark and light themes based on prefers-color-scheme */
    enableSystem?: boolean | undefined;
    /** Disable all CSS transitions when switching themes */
    disableTransitionOnChange?: boolean | undefined;
    /** Whether to indicate to browsers which color scheme is used (dark or light) for built-in UI like inputs and buttons */
    enableColorScheme?: boolean | undefined;
    /** Key used to store theme setting in localStorage */
    storageKey?: string | undefined;
    /** Default theme name (for v0.0.12 and lower the default was light). If `enableSystem` is false, the default theme is light */
    defaultTheme?: string | undefined;
    /** HTML attribute modified based on the active theme. Accepts `class` and `data-*` (meaning any data attribute, `data-mode`, `data-color`, etc.) */
    attribute?: string | 'class' | undefined;
    /** Mapping of theme name to HTML attribute value. Object where key is the theme name and value is the attribute value */
    value?: ValueObject | undefined;
    /** Nonce string to pass to the inline script for CSP headers */
    nonce?: string | undefined;
    /** React children */
    children: React.ReactNode;
}

class is ok to send into the attribute prop, is this an issue that React doesn't like?

I've found a few similar issues, but none related to NextUI, any thoughts on how I can remove this warning / error from the console?


I did have a custom ThemeContext I created, but I still get the error warning after I comment it out:

import { FC, ReactNode } from 'react';
import Providers from '@/redux/provider';
import MainHeader from '@/components/mainheader';
import SideBar from '@/components/sidebar';
// import { ThemeProvider } from '@/context/ThemeContext';
import { User } from '@/types/user';

interface layoutProps {
  children: ReactNode;
}

// const user: User = {
//   theme: 'dark',
// };

const Layout: FC<layoutProps> = ({ children }) => {
  return (
    // <ThemeProvider user={user}>
    <main>
      <MainHeader />
      <SideBar />
      <Providers>{children}</Providers>
    </main>
    // </ThemeProvider>
  );
};

export default Layout;

I also tried adding this suppressHydrationWarning, but did not work to remove the warning either.

<div suppressHydrationWarning={true}>
  <NextUIProvider>
    <NextThemesProvider
      attribute="class"
      defaultTheme="dark"
      themes={['light', 'dark', 'modern']}
    >
      {children}
    </NextThemesProvider>
  </NextUIProvider>
</div>

Upvotes: 7

Views: 2337

Answers (3)

Khuram Niaz
Khuram Niaz

Reputation: 919

Rather than suppressing the warning, you could also lazy load the ThemeProvider as highlighted here https://github.com/pacocoursey/next-themes?tab=readme-ov-file#avoid-hydration-mismatch

Note: Please test the production build, this might throw dynamic server usage error during build and using "force-dynamic" might help to resolve this

// layout.tsx

import dynamicImport from "next/dynamic";
export const dynamic = "force-dynamic"; // this might resolve the dynamic server usage error

const ThemeProvider = dynamicImport(
  () => import("@/components/theme-provider").then((mod) => mod.ThemeProvider),
  {
    ssr: false,
    loading: () => (
      // Optional: Add skeleton loader here
      <div className="min-h-screen bg-background" />
    ),
  }
);

export default function RootLayout({ children }: Readonly<Props>) {
  return (
    <html lang="en">
      <body
        className={cn(
          "min-h-screen bg-background font-sans antialiased",
          inter.variable
        )}
      >
        <ThemeProvider
          attribute="class"
          defaultTheme="system"
          enableSystem
          disableTransitionOnChange
        >
          {children}
        </ThemeProvider>
      </body>
    </html>
  );
}

Upvotes: 0

Win Min Aung
Win Min Aung

Reputation: 1

Finally yeah!

you should create ThemeProvider only on client side I mean only yeah its mean when components mounted

https://github.com/vercel/next.js/discussions/49832#discussioncomment-11038857

"use client";
import { ThemeProvider } from "next-themes";
import Navbar from "./Navbar";
import { useEffect, useState } from "react";

interface Props {
  children: React.ReactNode;
}
const BaseLayout = ({ children }: Props) => {
  const [mounted, setMounted] = useState(false);

  useEffect(() => {
    setMounted(true); // Only render the ThemeProvider after mounting
  }, []);

  if (!mounted) {
    return <>{children}</>; // Render without theme provider initially (avoids mismatch)
  }
  return (
    <ThemeProvider attribute="class" enableSystem={false}>
      <Navbar />
      <main className="pt-16">{children}</main>
    </ThemeProvider>
  );
};

export default BaseLayout;

Upvotes: 0

Leon Gaban
Leon Gaban

Reputation: 39018

Placed the suppressHydrationWarning flag on the wrong div.

I had to add the flag here on the root layout file:

<html suppressHydrationWarning={true} lang="en">

Warning is gone now.

Upvotes: 2

Related Questions