pacocoursey / next-themes

Perfect Next.js dark mode in 2 lines of code. Support System preference and any other theme with no flashing
https://next-themes-example.vercel.app/
MIT License
5.21k stars 191 forks source link

[Bug]: Intermediate loading.tsx + ThemeProvider in the layout breaks SSR Suspense #325

Open cirex-web opened 5 days ago

cirex-web commented 5 days ago

What happened?

Next JS does server-side rendering on all components by default. If a component throws a promise during the server-side render, the client should see the nearest suspense boundary and the component shouldn't run client-side until the promise is resolved.

However, with the following setup (all on the root level), the Page component runs client-side as well even though the thrown promise never resolves. Removing ThemeProvider fixes the issue. I should note that removing loading.tsx also fixes the issue, although that's probably not the desired fix.

This is a dealbreaker for anyone using SSR + streamed hydration, since we want all components to fully render on the server (eg. with data) before being rendered on the client.

Minimum reproducible example: https://github.com/cirex-web/next-theme-ignored-suspense

layout.tsx

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <ThemeProvider>{children}</ThemeProvider>
      </body>
    </html>
  );
}

loading.tsx

export default function Loading() {
  return <div>Loading...</div>;
}

page.tsx

"use client";

export default function Home() {
  console.log(
    "you shouldn't see this client-side! (should only be logged on the server)"
  );
  throw new Promise(() => {});
}

Version

0.4.3

What browsers are you seeing the problem on?

Chrome

nicanordlc commented 3 days ago

I think using suppressHydrationWarning just to make it work feels bad 😢