I'm having trouble with inconsistent inlined CSS in my Next.js (v15.1.7) static exports (SSG). Every time I run next export (i run next build
with config set to output: 'export'
), the generated HTML files have different inlined CSS, even though my CSS code hasn't changed. This unpredictable output leads to really high Core Vital scores on some pages. Specially for LCP value.
I've tried different combination of values for following fields on next.config.js
without any success.
inlineCss
useLightningcss
optimizeCss
sassOptions
styledComponents
Following screenshots are from a dynamic route [feature].tsx
I have on my NextJS app. On the left half of the image, it shows rendered HTML after JS and CSS loaded. On the right half, under Chrome dev console's Preview tab, it shows HTML with inlined CSS. Observe that on the first image's preview resembles what actually rendered after JS and CSS loaded by the browser, this this page has better Core Vital score. On the other hand, second image shows a preview that is nothing like the final rendered HTML by the browser.
Static generated route with better inline CSS
Static generated route with bad inline CSS
Note that first image is not perfect, but its far better than the seconds one. This show the HTML diff of 2 pages /.
This outout is very unpredictable. For example, if I run the export again, output for above pages will be something very different from previous. Its looks like it nondeterministic which CSS classes will be inlined.
I'm expecting the inlined CSS to be the same between exports, as the source CSS is unchanged. Why is the inlined CSS different on each export, and how can I make it consistent? Is there a configuration option or best practice I'm missing?
Thanks in advance for any help!
next.config.js
const path = require('path')
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
})
const withTM = require('next-transpile-modules')(['lodash-es']);
module.exports = withTM(withBundleAnalyzer({
output: 'export',
distDir: 'build-web/xconvert-web',
modularizeImports: {},
experimental: {
inlineCss: true,
// cssChunking: 'strict', // This causes initial CLS to be really
useLightningcss: true,
forceSwcTransforms: true,
optimizeCss: true,
optimizePackageImports: ['web'],
workerThreads: false,
cpus: 32,
},
poweredByHeader: false,
basePath: '',
productionBrowserSourceMaps: true,
sassOptions: {
includePaths: [path.join(__dirname, 'styles')],
},
compiler: {
styledComponents: {
pure: true,
},
},
webpack: (config, { isServer }) => {
config.watchOptions = {
ignored: ['files/**/*.js', 'node_modules', '**/node_modules/**']
}
return config
}
},
));
babel.config.js
module.exports = {
"plugins": [
['babel-plugin-styled-components', {
ssr: true, // Enable SSR for styled-components
displayName: true, // Add component display names (useful for debugging)
pure: true, // Try to optimize by removing unnecessary styles
}],
//
// FIxes @inject on constructor and class constructor argument save issue
"babel-plugin-transform-typescript-metadata",
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
],
"@babel/plugin-transform-class-properties",
"babel-plugin-parameter-decorator",
"@babel/plugin-syntax-dynamic-import",
[
"@babel/plugin-transform-typescript",
{
"allowNamespaces": true
}
]
],
"presets": [
"next/babel",
"@babel/preset-typescript",
[
"@babel/preset-env",
{
"bugfixes": true,
"useBuiltIns": "usage",
"corejs": "3",
"targets": {
"browsers": [
"last 10 versions, ie 11, not dead, > 0.25%"
],
"node": 'current'
},
"modules": "auto"
}
],
]
}