Solution to CSS Module FOUC Issue in Next.js
Henry Hung
Overview
When using CSS modules in Next.js, you might encounter an issue called FOUC (Flash of Unstyled Content). This issue causes the page to briefly display unstyled content before applying styles. This article will discuss how to solve this issue.
Problem
Until Next.js 14.2.6, when using the APP ROUTER, the prefetching of the <Link>
component does not include CSS modules, prefetching only the HTML and JS content of the linked page. This causes the HTML content to be rendered into the DOM tree by the browser first when navigating with the <Link>
component, and then it requests the CSS file from the server. During the request for the CSS, the page will display unstyled content, which is the FOUC issue. This problem was reported on Next.js' GitHub as early as 2018, but the official response was very dismissive, with at least three similar issues being closed without resolution. Disappointed by this, I attempted to solve this issue myself.
Solution
After three days of trials, I finally found an optimal solution based on dynamic import().
In general, we need to dynamically import all the CSS modules once in the RootLayout
component. This ensures that all CSS modules are stored in the same CSS file. Consequently, when the initial screen loads, all CSS files are loaded in, effectively preventing the FOUC issue.
-
Install the globby library:
bashCopy Codenpm i globby
-
Modify the
jsconfig.json
file in the root directory to ensuresrc/../../
correctly points to thesrc
folder in the project root directory:jsonCopy Code{ "compilerOptions": { "baseUrl": "./", "include": ["./"] } }
-
Insert the
loadAllCssUnderAppDirectory()
method into the root layout. This method uses globby to get all paths to.module.css
files undersrc/app/
, and then imports them in the root layout. This makes the root CSS file include all CSS fromsrc/app/
, fetching all CSS files upon the initial screen load and thereby avoiding the FOUC issue. Using template strings is necessary because Next.js' webpack requires paths to be static, with at least the first two segments being static, or it will throw an error. If you have.module.css
files in other places such assrc/components/
, you can additionally create aloadAllCssUnderComponentsDirectory()
method.javascriptCopy Codeconst loadAllCssUnderAppDirectory = async () => { const pathPrefix = 'src/app/'; const pathSuffix = '.module.css'; // Get all CSS module paths const paths = await globby(`${pathPrefix}**/*${pathSuffix}`); // Dynamically load all CSS modules for (let path of paths) { const truncatedPath = path.slice(pathPrefix.length, -pathSuffix.length); (await import(`src/app/${truncatedPath}.module.css`)).default; } }; const Layout = async ({ children }) => { await loadAllCssUnderAppDirectory(); return ( <html> <body> {children} </body> </html> ); }; export default Layout;