Next.js 中 CSS 模块 FOUC 问题的解决方案
洪嘉庆
概述
在 Next.js 中使用 CSS 模块时,可能会遇到一种称为 FOUC(Flash of Unstyled Content)的问题。 这种问题会导致页面在加载时短暂地显示未经样式处理的内容,然后再应用样式这篇文章将介绍如何解决这个问题。
问题
直到 Next.js 14.2.6,在使用APP ROUTER的时候,<Link>
组件的预加载 (prefetch) 都不包括css模块,只会预加载链接页面中的html和js内容。
这导致了在使用<Link>
组件跳转页面时,html内容会先被浏览器渲染到 dom tree 中,然后再向服务器请求css文件。
请求css的过程中,页面会出现一段时间的未经样式处理的内容,这就是FOUC问题。
这个问题从2018年就已经有人在Next.js的GitHub上提出了,但官方的态度非常敷衍,有不下3个类似的 Issue 在未被解决的情况下关闭了。
在失望的同时,我只能尝试自己解决这个问题。
解决方案
经过三天时间的尝试,我终于找到了一个最佳解决方案,这个解决方案是基于 import() 动态导入的。
大致上,我们需要在RootLayout
组件中,动态的导入一次所有的css模块。这样所有的css模块都会被存入同一个css文件内。
进而在首屏加载时,就把所有的css文件都加载进来,从根源上避免了FOUC问题。
-
安装globby库
bashCopy Codenpm i globby
-
修改根目录下的
jsconfig.json
文件,确保src/../../
是可以正确的指向项目根目录的 src 文件夹jsonCopy Code{ "compilerOptions": { "baseUrl": "./", "include": ["./"] } }
-
在root layout中插入这个
loadAllCssUnderAppDirectory()
方法。 该方法使用了globby去获取所有src/app/
下所有.module.css
的路径,然后在root layout中进行import. 这样就使root css文件包含了所有的src/app/
下的css,从而在首屏加载时fetch所有的css文件,避免了FOUC问题。
之所以使用模版字符串,是因为next.js的webpack需要路径是静态的,并且至少前两段路径是静态的,不然就会报错。如果你的.module.css
文件还分布在其他地方,如src/components/
,你也可以自行添加一个loadAllCssUnderComponentsDirectory()
方法。javascriptCopy Codeconst loadAllCssUnderAppDirectory = async () => { const pathPrefix = 'src/app/'; const pathSuffix = '.module.css'; // 获取所有的 CSS 模块路径 const paths = await globby(`${pathPrefix}**/*${pathSuffix}`); // 动态加载所有的 CSS 模块 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;