vercel / next.js

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

Inconsistent CSS resolution order #64921

Open GabenGar opened 6 months ago

GabenGar commented 6 months ago

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 commented 6 months ago

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

samcx commented 6 months ago

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

benjitastic commented 6 months ago

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 commented 6 months ago

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

benjitastic commented 6 months ago

@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.

paulyi commented 5 months ago

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 commented 5 months ago

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

benjitastic commented 5 months ago

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 commented 5 months ago

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 commented 5 months ago

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

Edit by maintainer bot: Comment was automatically minimized because it was considered unhelpful. (If you think this was by mistake, let us know). Please only comment if it adds context to the issue. If you want to express that you have the same problem, use the upvote 👍 on the issue description or subscribe to the issue for updates. Thanks!

samcx commented 5 months ago

@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 commented 5 months ago

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 commented 5 months ago

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 commented 5 months ago

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 commented 5 months ago

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 commented 4 months ago

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 commented 4 months ago

@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 (https://github.com/vercel/next.js/pull/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 → https://github.com/vercel/next.js/pull/67373.

paulyi commented 4 months ago

@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 commented 4 months ago

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

mrabuse commented 4 months ago

@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 commented 4 months ago

@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 commented 4 months ago

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 commented 4 months ago

@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 commented 4 months ago

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 commented 4 months ago

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 commented 3 months ago

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 commented 3 months ago

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.

samcx commented 3 months ago

@consdu Can you also try using this experimental option (if you haven't yet)?

experimental: {
  cssChunking: "strict" // default is "loose"
}

x-ref: https://github.com/vercel/next.js/pull/67691/files

consdu commented 3 months ago

@consdu Can you also try using this experimental option (if you haven't yet)?

experimental: {
  cssChunking: "strict" // default is "loose"
}

x-ref: https://github.com/vercel/next.js/pull/67691/files

@samcx

I did that yes. I'm currently using version 14.1.1, which I believe didn't include the flag you mentioned. I upgraded to version 14.2.5 and set the flag to "strict", but unfortunately, the issue persists. I even tried downgrading to version 13, but the problem remained, suggesting that it's not related to changes introduced in version 14 and above.

I'm using the pages router for my project. I'm happy to provide more screenshots and details about my configuration if needed.

samcx commented 3 months ago

@consdu I see. Just to clarify, the fixes that have been pushed recently all relate to App Router, not Pages Router.

Are you using a postcss.config.js file by any chance? If yes, can you share your config?

consdu commented 3 months ago

@consdu I see. Just to clarify, the fixes that have been pushed recently all relate to App Router, not Pages Router.

Are you using a postcss.config.js file by any chance? If yes, can you share your config?

@samcx I have it as dependency but with no config file.

samcx commented 3 months ago

@consdu Thanks for clarifying. At the moment, it's unclear how to proceed with this—we'll need a :repro: somehow—if that meant somehow creating a minimal :nextjs: project with multiple files to mimic the merging. This may have always been the case with :nextjs: Pages Router. One thing to note is that CSS import order matterswas always the case even with Pages Router, so @piratetaco's workaround would make sense. Just unclear how to be sure if this is expected, or a dependency such as is affecting it, etc. (this is why we need a :repro:. e.g., not sure how you're using postcss, which could be a culprit. maybe related → https://github.com/vercel/next.js/issues/67193).

consdu commented 3 months ago

@samcx I did some additional tests and found that this issue occurs only with components that are deeply nested. I am using the same component, which extends from an atom component, on two different pages. In the first instance, where the styles import correctly, the component is 2 to 3 levels deep in the component tree. In the second instance, where the styles import incorrectly, the component is 7 to 8 levels deep. By levels deep, I mean the page renders a layout component, which renders a container, which renders a section, and so on until the component in question.

I tried manually moving the component higher up in the tree, and this resolved the issue with the incorrect style import. I hope this information is helpful to try find a solution to this problem.

Robin-Hoodie commented 3 months ago

Tried 14.2.5 as well - can confirm that the issue still persists and does NOT fix this issue for our project

samcx commented 3 months ago

@consdu @Robin-Hoodie If you guys can provide a minimal, public :repro:, that will be great!

piratetaco commented 3 months ago

I have isolated at least my issue and created a replication path! The issue seems to stem from having a library package that is published with sideEffects: false in the package json.

Library and replication path guide:

Simple create next app clone with a page that references library:

Note that if you remove sideEffects: false from the package.json the order is correct and follows the rest of the import order ahead of adding local styles.

samcx commented 3 months ago

@piratetaco That's very interesting—maybe something to add to our :nextjs-spin: :docs:. Thanks for digging deep into that!

piratetaco commented 3 months ago

@samcx, I've noticed that my js still gets tree shaken (likely given that my library is included in the transpile modules pattern), although there might be some slight adverse build time impact from removing it. However, while the CSS is now all in order, it no longer treeshakes to only include the styles for the modules actually used. (I can create a quick replication for this if helpful?)

In my case maintaining a legacy library, this is about 100kb of css built (which zips much much smaller) but still a fairly significant consideration.

ORLVNIMUSIC commented 3 months ago

I have isolated at least my issue and created a replication path! The issue seems to stem from having a library package that is published with sideEffects: false in the package json.

Library and replication path guide:

Simple create next app clone with a page that references library:

Note that if you remove sideEffects: false from the package.json the order is correct and follows the rest of the import order ahead of adding local styles.

We've tried adding "sideEffects": false to our package.json instead of removing it, and it solved the problem (besides that now we need to manually disable tree shaking of some other files)

I guess Next's CSS bundler has a sideEffect itself, so, if it works incorrectly, ignoring it might be a solution to the problem

Regarding of changes in the bundles:

  1. Before adding "sideEffects": false - CSS was bundled in 2 more or less equal size files (and the second one was rewriting styles of the first)
  2. After adding "sideEffects": false - CSS is bundled in 2 files, second of which is very small relatively (does not rewrite the first)
piratetaco commented 3 months ago

@ORLVNIMUSIC that seems like one of the the duplication issues (two) rather than the order issue? Are you on next 14.2.5(+)?

As of 14.2.5 I'm no longer able to replicate the duplication in chunks for the same page issue, even with a minimal repro

ORLVNIMUSIC commented 3 months ago

@piratetaco it doesnt seem so as we've problem neither with global styles, nor styles from last page (styles are out of order right after first app load)

we are on 13.4.12 Page Router, and upgrading to 14.2.5 did not help with this (cssChunking: 'strict' did not as well)

Its just that styles for a single element occur in different files (aka style tags) when there are to much of CSS for a page, and the second file (tag) appears to have a priority over the first (regardless of import order, applying order etc)

I believe the reason of wrong style order resolution (for our case at least) is bundler optimization, as Next tries to create multiple CSS files of the same size, instead of one very big file (but i might be completely wrong about it)

ps.

I've checked if deleting some css would help to resolve the problem, and it did! (just created one CSS file for production instead of two)

Also adding "sideEffects": false helped

IIACTbIPb commented 2 months ago

enabling the --turbo flag helped me on the version 14.2.5 next dev --turbo

rohitpotato commented 2 months ago

@samcx Is this fixed? We are facing some nasty memory leak issues and desperately need to upgrade to 14.2.5. We have a throughput of 10k requests/minute (minimum) and we need a workaround asap, is there any update? Please let me know incase of any possible solutions.

samcx commented 2 months ago

@rohitpotato If you have memory issues, it could be related to your Node version. Please make sure to report on a related-issue, as this issue is about CSS-ordering.

rohitpotato commented 2 months ago

@rohitpotato If you have memory issues, it could be related to your Node version. Please make sure to report on a related-issue, as this issue is about CSS-ordering.

HI @samcx, thanks for the quick response. This is not related to our node version. I am talking about the memory usage issue mentioned in the official 14.2 release, we were facing the same issue and 14.2 claims to resolve that, and thats why we are trying to upgrade, resulting in the above issue with incorrect CSS ordering.

Also, can you point me to the node version issue if thats the case? Would like to investigate more.

Current config:

"engines": {
    "node": ">=20.10.0",
    "yarn": ">=1.22.0"
  }

Ref:

Screenshot 2024-08-26 at 8 32 02 PM
samcx commented 2 months ago

@rohitpotato This is the separate issue I found that was causing memory leak issues, which stemmed from a Node.js release.

piratetaco commented 2 months ago

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 commented 2 months ago

@rohitpotato Sorry I forgot to include the link to it! → https://github.com/vercel/next.js/discussions/68636

jianliao commented 2 months ago

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 commented 2 months ago

@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!