Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inconsistent CSS resolution order #64921

Open
GabenGar opened this issue Apr 23, 2024 · 70 comments
Open

Inconsistent CSS resolution order #64921

GabenGar opened this issue Apr 23, 2024 · 70 comments
Labels
bug Issue was opened via the bug report template. linear: next Confirmed issue that is tracked by the Next.js team.

Comments

@GabenGar
Copy link
Contributor

Link to the code that reproduces this issue

https://github.com/GabenGar/repros/blob/main/nextjs/css-out-of-order/README.md

To Reproduce

Reproduction steps are in the README.md

Current vs. Expected behavior

Current:
Different CSS resolution order between development and production. Before I had weird client vs. render CSS issues, but it looks like they are fixed in 14.2, although they weren't super reproducible before either.
Expected:
Work basically like pages router.

Provide environment information

Operating System:
  Platform: win32
  Arch: x64
  Version: Windows 10
  Available memory (MB): 7990
  Available CPU cores: 4
Binaries:
  Node: 20.9.0
  npm: N/A
  Yarn: N/A
  pnpm: N/A
Relevant Packages:
  next: 14.2.2 // Latest available version is detected (14.2.2).
  eslint-config-next: 14.2.2
  react: 18.2.0
  react-dom: 18.2.0
  typescript: 5.4.5
Next.js Config:
  output: N/A

Which area(s) are affected? (Select all that apply)

Not sure

Which stage(s) are affected? (Select all that apply)

next dev (local), next build (local), next start (local), Vercel (Deployed)

Additional context

No response

@GabenGar GabenGar added the bug Issue was opened via the bug report template. label Apr 23, 2024
@GabenGar
Copy link
Contributor Author

It seems 14.2.2 fixed this for development but not for prod.

@samcx
Copy link
Member

samcx commented Apr 24, 2024

@GabenGar Thanks for sharing a :repro:—we will be taking a look at this!

@benjitastic
Copy link

We are seeing the same issue on 14.2.2 where on production builds the CSS styles get included in an unexpected order.

The specific example we are seeing is that CSS styles imported into layout.js are overriding CSS styles set in a component level stylesheet even though they have the same CSS specificity and the component CSS should override the layout's CSS. This bug appears to be due to the order that the CSS is included in the final static .css files that are included in the production build.

@samcx
Copy link
Member

samcx commented May 1, 2024

@benjitastic What kind of CSS styling are you using in this case (e.g., css modules)?

@benjitastic
Copy link

benjitastic commented May 2, 2024

@benjitastic What kind of CSS styling are you using in this case (e.g., css modules)?

No, not modules. Just like this inside the component:

import './styles.css'


Some more details:

In this case layout.js had this at the top:
import '@/styles/customTheme.scss'

And customTheme.scss had this inside it:
@import 'bootstrap/scss/bootstrap';

That bootstrap file has a css style declared for .btn like this:
.btn { padding: .375rem .75rem }

Then in the component we have an element <button className="btn filter-pill"> and that component includes a styles.css. Inside that styles.css there's this declaration: .filter-pill { padding: 8px 33px 8px 14px }

The expectation is that .filter-pill padding can override .btn padding. But .btn was overriding .filter-pill styles. This was because of the 5 /_next/static/css/*.css files appearing in the DOM the 1st one contained .filter-pill and the 2nd one contained .btn. This is backwards from expected order. Regardless of it they are in the same static CSS file or not the .btn declaration should come before the .filter-pill declaration.

Hard to post a repro since I think you need a project that has enough CSS to result in multiple static CSS files being generated.

We reverted back to 14.1.4 and the CSS went back to the correct order.

@github-actions github-actions bot added the linear: next Confirmed issue that is tracked by the Next.js team. label May 13, 2024
@paulyi
Copy link

paulyi commented May 16, 2024

I am seeing this issue as well, particularly for global styles as well as styles using css modules. As mentioned in this issue, it only happens in production builds.

@dstaley
Copy link
Contributor

dstaley commented May 22, 2024

Setting experimental: { cssChunking: 'strict' } in next.config.js resolves this issue for us. Not ideal, but better than broken CSS ordering in production!

@benjitastic
Copy link

benjitastic commented May 22, 2024

Setting experimental: { cssChunking: 'strict' } in next.config.js resolves this issue for us. Not ideal, but better than broken CSS ordering in production!

Interesting -- I can't find any docs anywhere on the cssChunking parameter.

Does anybody know exactly what "strict" css chunking does?

@Netail
Copy link
Contributor

Netail commented May 23, 2024

Setting experimental: { cssChunking: 'strict' } in next.config.js resolves this issue for us. Not ideal, but better than broken CSS ordering in production!

It does not seem to resolve the issue for us :(

@paulyi

This comment has been minimized.

@samcx
Copy link
Member

samcx commented May 24, 2024

@Netail Thanks for sharing.

Are there any updates on this issue from the nextjs team?

I can confirm there are several broken cases with the ordering of CSS, after looking at several :repro:s. We've been busy with the 15 release—since that's out of the way, we will be prioritizing this!

Does anybody know exactly what "strict" css chunking does?

Getting some answers internally to further clarify this—will respond back soon!

@Netail
Copy link
Contributor

Netail commented May 24, 2024

That would be great. We use a design system package and a navigation package which uses the design system package (with some overrides) and the app using the design system, but the overwrites are currently not working on productions. Thus making NextJS kind of unusable currently for us. So the sooner the better 😅

Do you by any chance have a ETA when development on this will happen?

@mrabuse
Copy link

mrabuse commented May 28, 2024

Can confirm this issue also comes up in a project using Next.js 14.2., Mantine v7.10 components, and css modules. Works fine in development mode, loads incorrectly in production.

@michaelkostal
Copy link

michaelkostal commented May 29, 2024

I had a similar issue, where global styles were bundled after component styles. Running dev I never had an issue, only on production. I'm using Next 14.2.2 with App router and SSG.

My workaround is only for getting global scss that's imported in layout.js to load ahead of client component scss modules. But perhaps this will be helpful for someone else / debugging the overall issue.

In my root layout.js I was importing /global.scss

There is an @import css rule in there that should be loaded first since that is the first css imported on the page and before any components. However, in dev tools I had the following Issue: An @import rule was ignored because it wasn't defined at the top.

Indeed there was css rules added above the global.scss.

After analyzing it seems that the css above my @import was all related to client components. This led me to believe that next must prioritize client component styles when bundling css during production builds.

My workaround fix is to make a new client component that imports the styles

"use client";

import "@/scss/global.scss";
const GlobalStyles = () => {
    return <></>;
};

export default GlobalStyles;

and then import that component in my root layout.js

import GlobalStyles from "./GlobalStyles";
export default function RootLayout({ children }) {
    return (
        <html lang="en">
            <GlobalStyles />
            <body>
                {children}
            </body>
        </html>
    );
}

This resolved the issues I was getting in dev tools, and also some issues with specificity (component styles were no longer overriding global styles before the workaround).

@samcx
Copy link
Member

samcx commented May 29, 2024

Do you by any chance have a ETA when development on this will happen?

@Netail No ETA to share yet, but this issue is definitely high on our plate!

@piratetaco
Copy link

piratetaco commented Jul 2, 2024

I noticed that if I replace getModuleReferencesInOrder with moduleGraph.getOutgoingConnections(module) my classes are all back in the order I expect (with some minor duplication of different hashes) *that part was a different experiment. This is a major issue in my org preventing further adoption of server components and merging of prs so it hopefully is just a little tweaking to the module reference logic.

I believe this regression was introduced here as part of the 14.2 release.

Edit: It appears that removing the sorting also results in the correct order

@samcx
Copy link
Member

samcx commented Jul 8, 2024

@piratetaco Is it possible to :repro: so we can take a closer look?

@michaelkostal Can you try testing a later Next.js version? I believe this :pr: fix (#63157) was not backported to 14.2.2. You can also try the latest canary (greater than v15.0.0-canary.56), which has an additional fix → #67373.

@paulyi
Copy link

paulyi commented Jul 8, 2024

@samcx I face this issue on 14.2.3 as well. Can we backport the additional bug fix to v14 as well for those who cannot switch to a canary version or upgrade a major version at this time?

@samcx
Copy link
Member

samcx commented Jul 10, 2024

@paulyi The latest changes should now be in 14.2.5 (includes both fixes mentioned above).

@mrabuse
Copy link

mrabuse commented Jul 10, 2024

@samcx just tried out the 14.2.5 release -- while this release is an improvement, I'm still seeing some incorrect loading of css in the production environment.

@samcx
Copy link
Member

samcx commented Jul 11, 2024

@mrabuse :frog-eyes:

Can you describe exactly how it's loading incorrectly, and is it possible to provide a minimal, public :repro: so we can take a look?

@mrabuse
Copy link

mrabuse commented Jul 11, 2024

It'll take me a bit to build a public repro for you, but I'll see if I have some time this weekend. The project I'm working on uses Mantine UI for our component library, which has a base styles css file that must be loaded first. Those are imported at the top of our layout.tsx file from the Mantine package (import '@mantine/core/styles.css';). We then use per-file css modules to further style components. In production on 14.2.5, I'm seeing that the imported base styles file gets loaded after all of our css modules, switching the override order.

@michaelkostal
Copy link

michaelkostal commented Jul 11, 2024

@michaelkostal Can you try testing a later Next.js version? I believe this :pr: fix (#63157) was not backported to 14.2.2. You can also try the latest canary (greater than v15.0.0-canary.56), which has an additional fix → #67373.

@samcx upgrading to 14.2.5 did resolve my issue. It now appears my global styles are loaded first in order as expected. Thanks!

@samcx
Copy link
Member

samcx commented Jul 12, 2024

Those are imported at the top of our layout.tsx file from the Mantine package (import '@mantine/core/styles.css';). We then use per-file css modules to further style components. In production on 14.2.5, I'm seeing that the imported base styles file gets loaded after all of our css modules, switching the override order.

@mrabuse :frog-eyes:. Where exactly do you see the order being incorrect (e.g., in a Page that's a Client Component or a Server Component?) Seems like you're doing it all correctly, so a :repro: will be great.

@michaelkostal That's great to hear!

@piratetaco
Copy link

piratetaco commented Jul 12, 2024

sadly I'm unable to get a public repro going. The issue only seems to appear after the components are mixing and matching through the entire component library we're maintaining - but the issue we are seeing is still happening as of 14.2.5.

For now we are going to import higher stylesheets in the component.js in our library as a cumbersome workaround.

import { FirstButton } from '../FirstButton'
// styles that are going out of order
import '../FirstButton.styles.module.scss'
// component styles that should __always__ be added after FirstButton styles based on import order but are not
import styles from './styles.module.scss'

...

@consdu
Copy link

consdu commented Jul 18, 2024

The issue still persist as of 14.2.5 version, I'm using Sass with CSS Modules and I get inconsistent css import order between running the dev server locally and the production build, my app is quite big and I cannot get you a public repo up. Also this doesn't happen on small projects where only 1 chunk of css is build, in my case I have 5/6 chunks of css being build and I can't really reproduce something of that magnitude.

Screenshot 2024-07-17 at 18 03 01 Screenshot 2024-07-17 at 18 03 38

In this case its a composed component from an atom where I want to overwrite the gap, locally all works as expected but when building the project the css imported chunks order is being mixed.

My only solution at the moment is the one suggested by @piratetaco but I have a huge project, please fix this!

@benjitastic
Copy link

The issue still persist as of 14.2.5 version, I'm using Sass with CSS Modules and I get inconsistent css import order between running the dev server locally and the production build, my app is quite big and I cannot get you a public repo up. Also this doesn't happen on small projects where only 1 chunk of css is build, in my case I have 5/6 chunks of css being build and I can't really reproduce something of that magnitude.

Screenshot 2024-07-17 at 18 03 01 Screenshot 2024-07-17 at 18 03 38
In this case its a composed component from an atom where I want to overwrite the gap, locally all works as expected but when building the project the css imported chunks order is being mixed.

My only solution at the moment is the one suggested by @piratetaco but I have a huge project, please fix this!

+1 on this only surfacing once your app generates multiple chunk files. Your explanation of the issue matches exactly the symptoms we reported back in May. I'm still sitting at v14.1.4 and we are waiting to upgrade until this has been resolved, we are not yet considering implementing any work-arounds described in this issue thread as they are cumbersome to implement and maintain.

@piratetaco
Copy link

piratetaco commented Aug 27, 2024

Just as a tiny bit of extra gas here, I took my repro and swapped the nextjs version from 14.2.5 to 14.1.4 and you can see that the library styles emit properly again, even with sideEffects: "false" set. I can make a precise repro if that if helpful, but that should prove that at least the current issue is a regression from 14.1.4 that happened in 14.2.

Will edit this in a minute to see if I can isolate to being introduced in a specific version in 14.2 or not. Edit 1: Starts in 14.2.0 flat.

https://github.com/vercel/next.js/releases/tag/v14.2.0

Edit 2: Issue remains in 14.2.7. Cloning next and going to try some reverts of some sussy merges to try and isolate the specific change.

@samcx
Copy link
Member

samcx commented Aug 27, 2024

@rohitpotato Sorry I forgot to include the link to it! → #68636

@jianliao
Copy link

jianliao commented Sep 4, 2024

Dear Next.js team,

I’ve just hit this issue on 14.2.8. It’s frustrating that 14.1.4 still seems to be the last stable 'golden' version for reliable CSS ordering. I've lost track of how many times I've tried upgrading to the latest Next.js. First, it was the environment variable issue, which finally seemed to be resolved, but now it's this. My project has been stuck on 14.1.4 for over six months, and it's becoming a blocker. Any updates on when we can expect a real fix?

@samcx
Copy link
Member

samcx commented Sep 5, 2024

@jianliao There's been multiple fixes since the start of this thread (and similar ones), but yet there are still some edge cases that haven't been solved.

still seems to be the last stable 'golden' version for reliable CSS ordering

I don't think this is really true. There were other cases that were fixed from the first fix that updated the behavior, but afterwards, there were still other cases that needed fixing.

If you want to see progress with whichever specific CSS issue you're coming across, I would first start with sharing a minimal :repro: of your issue so we can take a look!

@payamtrack
Copy link

Hi Team,

We have a similar issue. Our next.js (v14.2.10) project is using SCSS modules (like page.modules.scss) as well as a globas.css file imported in the root layout.

Our application appears significantly different in production compared to the development server, due to inconsistencies in the order of CSS rules.

This discrepancy is a deal breaker for us, as it makes it unreliable to go live.

I appreciate any suggestions or updates on resolving this issue.

Thanks

@piratetaco
Copy link

piratetaco commented Sep 13, 2024

I have isolated at least my issue to this commit released in 14.2.0. If I revert this, css emits in the correct order from a library with or without sideEffects: false set. Looking into why.

I have isolated library css improper order to the changes in this file.

For CSS files, I don't believe it is a good practice to restrict to only sideEffects: true, like is done in these lines. That forces library authors to sideEffect the entirety of their library including the JS. I think regardless of sideEffects true, false, or set as an array, behavior should always be roughly the same by letting the css compilers handle it, and sideEffects: true should be removed from likely all of these stipulations in this file. Likely responsible for many other existing css issues.

@mrabuse
Copy link

mrabuse commented Sep 25, 2024

@piratetaco @samcx what is the expected timeline for backporting this fix to v14? We can't upgrade our Next.js version to fix the cache-poisoning security vulnerability until this is backported because it would destroy our production build interface for our users.

@samcx
Copy link
Member

samcx commented Sep 25, 2024

@mrabuse

what is the expected timeline for backporting this fix to v14?

The PR hasn't merged yet, and we are looking to release another v15 soon. So not entirely sure yet if the aforementioned PR if this will be backported, but not out of the picture!

We can't upgrade our Next.js version to fix the cache-poisoning security vulnerability

Not sure what you are referring to here :frog-eyes:

@Netail
Copy link
Contributor

Netail commented Sep 25, 2024

@mrabuse

what is the expected timeline for backporting this fix to v14?

The PR hasn't merged yet, and we are looking to release another 15 soon. So not entirely sure yet if the aforementioned PR if this will be backported, but not out of the picture!

15 CSS fix PRs?! How come so many?

@mrabuse
Copy link

mrabuse commented Sep 25, 2024

@samcx, your team found a high level security vulnerability around cache-poisoning -- you can read the announcement about the problem, the users it impacts, and the fix here. The fix was released on v14.2.10.

The problem for every team impacted by the css rendering order bug is that we cannot upgrade Next.js past v14.1.4 without breaking our production renders. So now we all have to make a choice between staying on v14.1.4 and living with a known security vulnerability until v15 is officially released, or, when @piratetaco's fix gets merged into the v15 canary, upgrading to the unstable canary version to fix both the css rendering issue and security vulnerability, but having to discover the impacts of the other officially unstable features on our applications. For those reasons, it would be appreciated if when @piratetaco's fix is approved, you could push for that change to also be backported to v14 quickly.

@Netail I believe he means a v15 release, not 15 PRs.

@samcx
Copy link
Member

samcx commented Sep 25, 2024

@Netail

15 CSS fix PRs?! How come so many?

I meant Next.js version 15!

@samcx
Copy link
Member

samcx commented Sep 25, 2024

@mrabuse thank you for sharing.

To clarify, the cache-poisoning vulnerability is only for Pages Router along with two other requirements. @piratetaco's PR is to fix a CSS issue with App Router.

If you're on Pages Router, there shouldn't be any issues with the ordering of your CSS (as far as I'm aware) on the latest stable v14.

@mrabuse
Copy link

mrabuse commented Sep 25, 2024

@samcx it is technically possible to use both routers in one Next.js application, so it is possible for a team to be impacted by both issues.

@mhfaust
Copy link

mhfaust commented Sep 25, 2024

@samcx, My team has built an app-router NextJS app but in order to integrate with a 3rd-party auth SDK we needed to include some pages routes, so we're technically a hybrid, and therefore we are stuck with both issues simultaneously.

@GabenGar
Copy link
Contributor Author

The best of both worlds, some would say.

@studentIvan
Copy link

studentIvan commented Oct 1, 2024

Faced with this issue on 14.2.4
Solved by disable the rs-next-classnames-minifier webpack module (a.k.a. https://github.com/vordgi/classnames-minifier) (connected it manually before)
Not sure how it can be related...
Sad the next.js has not classnames minifications from out the box

@thepunkyone
Copy link

thepunkyone commented Oct 9, 2024

Encountered this issue in production mode on v14.2.15 using the Pages Router and a custom Webpack 5 config with Mini CSS Extract plugin. The order was fine in development mode.

The issue was due to two pages (A and B) importing and rendering the same Page Template component. The page template imports a Link component and overrides its styles using css modules, while one of the pages (A) also imports the same Link component but does not alter its styling.

PageTemplate.js

import Link from '@atoms/Link'

import styles from './styles.css'

export const PageTemplate = ({children}) => {
    return <div><Link className={styles.overriddenLinkStyles} />{children}</div>
}

pages/page-a/index.js

import Link from '@atoms/Link'
import PageTemplate from '@templates/PageTemplate'

export const PageA = () => {
    return <PageTemplate><Link /></PageTemplate>
}

pages/page-b/index.js

import PageTemplate from '@templates/PageTemplate'

export const PageB = () => {
    return <PageTemplate />
}

The shared Page Template styles were ending up in a separate stylesheet, which was added to the <head> first, followed by the page stylesheet. The stylesheet for page A, as it imports the styles for Link component and does not alter them, overrides the altered Link component styles from Page Template.

<head>
 <link rel="stylesheet" href="__next/static/css/page-template-component-css-here.css" />
 <link rel="stylesheet" href="__next/static/css/pages/page-a-css-overriding-page-template-component-here.css" />
</head>

The solution was to extend the existing cache group config in next.config.js webpack config to put styles in a separate cache group:

// ...

const isCssModule = (module) => {
 return !!module.type?.startsWith('css') || !!module.resource?.endsWith('.css')
}

let originalLibTest
let originalFrameworkTest
let originalLibName

// ....

const optimizationHasBeenOverridden =
     !!config.optimization.splitChunks.cacheGroups?.styles

   if (!optimizationHasBeenOverridden) {
     originalLibTest = config.optimization.splitChunks.cacheGroups?.lib.test

     originalFrameworkTest =
       config.optimization.splitChunks.cacheGroups?.framework.test

     originalLibName = config.optimization.splitChunks.cacheGroups?.lib.name
   }

   config.optimization.splitChunks = {
     ...config.optimization.splitChunks,

     cacheGroups: {
       ...config.optimization.splitChunks.cacheGroups,

       lib: {
         ...config.optimization.splitChunks.cacheGroups?.lib,
         test(module) {
           if (isCssModule(module)) return false

           return originalLibTest(module)
         },
       },

       framework: {
         ...config.optimization.splitChunks.cacheGroups?.framework,
         test(module) {
           if (isCssModule(module)) return false

           return originalFrameworkTest(module)
         },
       },

       styles: {
         name: originalLibName,
         test(module) {
           return isCssModule(module)
         },
         priority: 20,
         minChunks: 1,
         reuseExistingChunk: true,
       },
     },
   }

After the change the stylesheets are added to <head> in the correct order.

@sabbir073
Copy link

sabbir073 commented Oct 13, 2024

Next.js 14 and Bootstrap CSS Override Issue (solved for me)

I was using Next.js 14.2.14 and Bootstrap 5.3.0 for responsive design, along with custom CSS to style the app. I ran into an issue where Bootstrap was overriding my custom styles in the production build, though everything worked perfectly in the dev server.

Problem Summary

After trying, I found the following:

  1. CSS order bug is not yet fixed in production mode for the latest Next.js.
  2. I tried using PostCSS modules, but it didn’t resolve the issue.
  3. I also tried using CSS Cascade Layers, but that didn’t work either.

My Initial Setup

My global.css looked like this:

/* styles/global.css */
@import url('/assets/css/style.css');
@import url('/assets/css/responsive.css');
@import url('/assets/css/magnific-popup.css');

And my _app.js was like this:

// pages/_app.js
import Head from 'next/head';
import 'bootstrap/dist/css/bootstrap.min.css'; // Bootstrap CSS
import '@/styles/global.css'; // Custom global styles

The Solution

I merged all the individual CSS files into a single global.css. Once I did that, everything worked perfectly in production!

After Fix:

I combined all the styles from style.css, responsive.css, and magnific-popup.css directly into global.css, like this:

/* styles/globals.css */
/* Contents from style.css, responsive.css, and magnific-popup.css merged here */

This approach solved the issue and made sure my custom styles worked in production. You can try merging all CSS files into one to avoid issues caused by CSS ordering in Next.js.

@saltycrane
Copy link

I saw this issue on our project when trying to migrate to App Router and created this minimal repro here: https://github.com/saltycrane/repros/tree/main/next-css-modules-order

When running npm run dev, the button is blue.

But when running npm run build && npm run start, the button is gray.

I don't understand the cause of the issue. It seemed to be related to which files import which files. If I move components to different files, the results change.

I am using Node.js v22.9.0 on macOS Sonoma and the following npm dependencies:

    "bootstrap": "^5.3.3",
    "next": "^15.0.3",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "reactstrap": "^9.2.3"

@naimlatifi5
Copy link

naimlatifi5 commented Dec 4, 2024

@samcx any updates on this? Have a similar issue as @saltycrane described above. I tried a different next.js version and upgraded to the latest but the problem persists. This is happening only when running npm run build && npm run start :)

@samcx
Copy link
Member

samcx commented Dec 4, 2024

@naimlatifi5 One fix is still being worked on → #70087

@chaance
Copy link

chaance commented Dec 6, 2024

@samcx Will this fix land in a 14.x patch or only 15? We're affected by this but still have a bit of work to do to get to Next 15.

@samcx
Copy link
Member

samcx commented Dec 6, 2024

@chaance We should be able to backport that PR once it lands!

@saltycrane
Copy link

Probably you already know this, but I found the issue for me was introduced in [email protected]. I decided to roll back to 14.1.4 in our project.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Issue was opened via the bug report template. linear: next Confirmed issue that is tracked by the Next.js team.
Projects
None yet
Development

No branches or pull requests