vercel / next.js

The React Framework
https://nextjs.org
MIT License
122.96k stars 26.27k forks source link

[NEXT-643] FOUC using CSS-in-JS libraries in appDir through useServerInsertedHTML #44125

Open mghizzo opened 1 year ago

mghizzo commented 1 year ago

Verify canary release

Provide environment information

Operating System: Platform: darwin Arch: arm64 Version: Darwin Kernel Version 22.2.0: Fri Nov 11 02:03:51 PST 2022; root:xnu-8792.61.2~4/RELEASE_ARM64_T6000 Binaries: Node: 16.16.0 npm: 8.11.0 Yarn: 1.22.19 pnpm: N/A Relevant packages: next: 13.0.8-canary.0 eslint-config-next: 13.0.6 react: 18.2.0 react-dom: 18.2.0

Which area(s) of Next.js are affected? (leave empty if unsure)

App directory (appDir: true)

Link to the code that reproduces this issue

https://github.com/mghizzo/next-reproduction-app

To Reproduce

Start the bug reproduction project and just navigate to http://localhost:3000 to see FOUC, you can also disable javascript on the page to ensure that styles which should be injected are not there.

Describe the Bug

Using useServerInsertedHTML inside a component won't inject result in the head if there's no <head> tag. You can temporarily fix this issue by using head tag somewhere (head file if you have one or just put a <head /> in the Root Layout. I also found a commit which was meant for a fix but it was then reverted.

Expected Behavior

It is expected that the result of useServerInsertedHTML gets injected inside the head.

Which browser are you using? (if relevant)

Google Chrome 108.0.5359.124 (ARM64)

How are you deploying your application? (if relevant)

No response

NEXT-643

jesko-plitt commented 1 year ago

@mghizzo Thank you so much for opening this issue. Especially for your temporary solution.

I am experiencing the same problems. Using styled-components and following the instructions in https://beta.nextjs.org/docs/styling/css-in-js#styled-components leads to layout shifts. Adding the <head /> to my app/layout.tsx fixed that.

I have no idea if that is expected behavior or not. If it is expected it would be nice to add this to the documentation.

IonelLupu commented 1 year ago

@jesko-plitt I already have the "< head >" tag in my RootLayout and I still get this issue (using AntDesign v5). It was always there in Next.js 13 projects. Here is my app/layout.tsx file:

export default function RootLayout({ children }: { children: React.ReactNode }) {
    return (
        <html lang="en">
            <head />
            <body>
                <ThemeProvider>
                    {children}
                </ThemeProvider>
            </body>
        </html>
    )
}
mghizzo commented 1 year ago

@IonelLupu This is my solution in Nextjs 13 with App Directory & Ant Design 5

layout.tsx

"use client";
import { ConfigProvider, theme } from "antd";
import { useEffect, useState } from "react";
import { createCache, extractStyle, StyleProvider } from "@ant-design/cssinjs";
import { useServerInsertedHTML } from "next/navigation";
import "styles/globals.css";
import "antd/dist/reset.css";
import itIT from "antd/locale/it_IT";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="it">
      <head />
      <body>
        <StyleProviderLayout>{children}</StyleProviderLayout>
      </body>
    </html>
  );
}

function StyleProviderLayout({ children }: { children: React.ReactNode }) {
  const [cache] = useState(() => createCache());

  const render = <>{children}</>;

  useServerInsertedHTML(() => {
    return <script
        dangerouslySetInnerHTML={{
          __html: `</script>${extractStyle(cache)}<script>`,
        }}
      />;
  });

  if (typeof window !== "undefined") {
    return render;
  }

  return <StyleProvider cache={cache}>{render}</StyleProvider>;
}

Edit: found a workaround so that we don't need an external library anymore

devagrawal09 commented 1 year ago

@mghizzo I am facing this issue with the pages directory, ant design is causing FOUC, how did you fix it?

IonelLupu commented 1 year ago

@devagrawal09 check this issue: https://github.com/ant-design/ant-design/issues/39891. Someone also proposed a fix on the Next.js side: https://github.com/vercel/next.js/pull/44015. Waiting to be merged

devagrawal09 commented 1 year ago

@IonelLupu all the examples are with the app directory, I'm on the pages directory

dreamstyle commented 1 year ago

I have the same issue with Next.js 13 (pages directory) + Ant Design v5! Did anyone get a solution?

devagrawal09 commented 1 year ago

I have the same issue with Next.js 13 (pages directory) + Ant Design v5! Did anyone get a solution?

No I pretty much gave up and moved to Tailwind

bombillazo commented 1 year ago

So, no solution for Next 13 (pages) + Ant Design SSR?

tobiasriemenschneider commented 1 year ago

So, no solution for Next 13 (pages) + Ant Design SSR?

This would be a bummer, considering that app dir is still in beta and pages still the default.

ctgsjrgl commented 1 year ago

Same here! Did anyone get a solution?

HasanMothaffar commented 1 year ago

Same issue here with Next.js 13 (pages directory) and Ant Design 5. Is there any example for the pages directory?

jason-ku-8313 commented 1 year ago

Hey guys,

I discovered that the antd team offers two solutions on their official page: https://ant.design/docs/react/customize-theme#server-side-render-ssr. The second solution, "Whole export" worked for me 👍 All I had to do was follow this example project to resolve the issue: https://github.com/ant-design/create-next-app-antd

@dreamstyle @bombillazo @ctgsjrgl @HasanMothaffar

PandukaNandara commented 11 months ago

Adding hashPriority="high" to StyleProvider is worked for me. This is how I did.

"use client";
import { ConfigProvider, theme } from "antd";
import React, {
  createContext,
  FC,
  PropsWithChildren,
  useContext,
  useMemo,
  useState,
} from "react";

import { StyleProvider, createCache, extractStyle } from "@ant-design/cssinjs";
import { useServerInsertedHTML } from "next/navigation";

const ThemeContext = createContext<{  }>({

} as any);

// eslint-disable-next-line react-hooks/rules-of-hooks
export const useTheme = () => useContext(ThemeContext);

const ThemeProvider: FC<PropsWithChildren> = ({ children }) => {
  const [cache] = useState(() => createCache());
  useServerInsertedHTML(() => {
    return (
      <style
        id="antd-style"
        dangerouslySetInnerHTML={{
          __html: extractStyle(cache, true),
        }}
      />
    )
  })

  return (
    <StyleProvider cache={cache} hashPriority="high">
      <ConfigProvider
        direction="ltr"
        theme={{
          algorithm: mode === "dark" ? theme.darkAlgorithm : theme.defaultAlgorithm,
          token: {
            colorPrimary: "#3f7291",
          },
        }}
      >
        <ThemeContext.Provider
          value={{}}
        >
          {children}
        </ThemeContext.Provider>
      </ConfigProvider>
    </StyleProvider>
  );
};

export default ThemeProvider;
monolithed commented 10 months ago

@PandukaNandara, the problem with this approach is that the Time to First Byte (TTFB) will be 2-3 seconds. Your users won't be able to get styles until the server compiles them in runtime.