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

Next 13 "app" vs pages support?

Open dstroot opened this issue 3 years ago • 4 comments

I am using Nextjs and Tailwind and experimenting with migrating a small app to the new app directory in Nextjs 13. I am struggling to figure out how to make dark/light mode work. I am using this library with the old system with the "pages" directory and a custom _app.js. I have tried this but it doesn't work:

"use client";

import "../styles/globals.css";
import { ThemeProvider } from "next-themes";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <ThemeProvider>
      <html lang="en">
        <head />
        <body
          className="px-4 sm:px-8 bg-white lg:px-24 dark:bg-gray-900"
        >
          {children}
        </body>
      </html>
    </ThemeProvider>
  );
}

dstroot avatar Jan 07 '23 05:01 dstroot

You need to make a provider component and wrap { children } in the root layout. You also need to (AFAIK) "npm i next-themes@npm:@wits/next-themes"

something like this:

Providers.tsx

'use client'

import { ReactNode } from "react";

import { ThemeProvider } from '@wits/next-themes'
import { OtherProvider } from "./context/OtherContext";

// * Because layout.tsx is only rendered on the server-side, we can use the
// * ThemeProvider and OtherProvider components directly in the RootLayout
// * component. 

// * This is not possible in the app.tsx file because it is rendered
// * on both the server-side and the client-side.

export default function Providers({ children }: { children: ReactNode }) {
  return (
    <ThemeProvider attribute="class">
      <OtherProvider>
        {children}
      </OtherProvider>
    </ThemeProvider>
  )
}

layout.tsx

import { ReactNode } from "react";
import { ServerThemeProvider } from "next-themes";
import { Quicksand } from '@next/font/google'

import Providers from "./Providers";
import Header from "./components/Header";
import "./output.css";

const quicksand = Quicksand()

// * The ServerThemeProvider component is used to ensure that the correct
// * theme is applied on the server-side. It is not required on the client-side.

// * It wraps the html element and takes an attribute prop which is the attribute
// * that will be used to set the theme class on the html element.
// * The default value is "class".
// * The theme class will be applied to the html element on the server-side
// * and then removed on the client-side to prevent a flash of unstyled content.

// * The Providers component wraps the app and provides the context for the
// * useTheme and usePokemon hooks.

export default function RootLayout({ children }: { children: ReactNode }) {

  return (
    <ServerThemeProvider attribute="class">
      <html lang="en" className={quicksand.className}>
        <head />
        <body>
          <Providers>
            <Header />
            {children}
          </Providers>
        </body>
      </html>
    </ServerThemeProvider>
  )
}

hope this helps

giuseppe-g-gelardi avatar Jan 22 '23 06:01 giuseppe-g-gelardi

This is awesome - works perfectly!

dstroot avatar Jan 27 '23 12:01 dstroot

Worked for me as well 🙌🏼

tristansinclair avatar Feb 21 '23 01:02 tristansinclair

After a lot of reading on this subject, I can say that this works for me as well! However, I have a strange bug. Sometimes, after I switched theme, some elements are randomly selected in the page, whether it is images or yet just part of text. I'm wondering if it is a matter of hardware or just a strange glitch resulting of hydration. It is not a big deal, but of course it would be better without it for UX.

ShortyLogos avatar Feb 22 '23 04:02 ShortyLogos

@giuseppe-g-gelardi thx for sharing your solution.

trm217 avatar Apr 22 '24 11:04 trm217