In development mode, when a page imports styles from a .module.css file ( import styles from "./home.module.css" ) and then imports another component after that ( import { Container } from "../components/Container" ), and passes the imported styles to the component like <Container className={styles.blue}>, these styles get overriden by the imported component. This component has it's own styles and passes the className prop to a DOM element:
import styles from "./Container.module.css";
function classes(...classNames) {
const joined = classNames.join(' ');
return joined;
}
export const Container = ({
children,
className= "",
}) => {
return (
<div className={classes(styles.container, className)}>
{children}
</div>
);
};
The Container has the following style:
.container {
background-color: yellow;
}
But the page provides the following style:
.blue {
background-color: blue;
}
In development, the styles are injected into the head as follows:
<style>
.index_blue__3Iu-s {
background-color: blue;
}
</style>
<style>
.Container_container__1UmhU {
background-color: yellow;
}
</style>
In production, these styles are combined into one stylesheet:
.Container_container__1UmhU {
background-color: #ff0
}
.index_blue__3Iu-s {
background-color: #00f
}
As you can see, there is a difference in order. This causes the inconsistency across environments with CSS ( yellow in development, blue in production ).
I believe that the production result is the correct one, as you should be able to override the component styles.
I'd be happy to try to contribute to a fix. However, I am a beginner developer so I might need some guidance.
Clone my reproduction repository and run npm run dev to see the styles applied incorrectly, and run npm run build; npm run start to see that the styles work as intended.
I would expect that in both development and production mode the classes get defined in the same order so that the styles are applied correctly.
The style tags are injected by the code in this file. It uses style-loader in development and the MiniCssExtractPlugin in production. What is the reason as to why it isn't used in development mode?
@Timer I rewrote the issue as it wasn't that clear previously.
Even moving the import to the bottom of the file doesn't work perfectly, because changing the component className still moves that class below the page styles.
I'd be happy to work on this, but I need some direction. I tried using MiniCssExtractPlugin that's already used in production also for development mode but that causes some errors too. Could someone explain why there is a different approach to styles across development and production mode? @Timer
I think the order of the imports matters - in the example page you have
import styles from "./index.module.css";
import { Container } from "../components/Container";
So I would expect the source order of the compiled css to be
1) page styles
2) component styles
Therefore in your example the component style should override the page styles and the background should be yellow. ie. the dev mode is correct. Worryingly it's the production mode that gets it wrong.
@petewarman Moving the styles import to after the component import works, until I modify the component too much (changing the name of the class it applies to the div). Then the styles of the component are re-inserted after the page styles despite the page styles import being the last import in the file
This is likely a generic issue with CSS Modules and not Next.js specific https://twitter.com/giuseppegurgone/status/1084921816030896135
https://twitter.com/giuseppegurgone/status/1166043099900076033
You might be right. Although I do still think the loading order should be consistent across environments
fair point
Going to keep this open because this is something that Next.js could improve. I'm willing to help work on it.
I'm not even sure what to do as a workaround for this.
I think an easy solution would be to automatically apply page-specific identifiers as part of the css modules hash generation, but the ability to configure that isn't exposed.
In my app today, I guess I could pass the style attribute and inline my styles, but I have a heavy reliance on PostCSS stuff that can't be leveraged in my JS.
I tried using classNames with dynamic values and .bind() to no avail... Even !important wont work because if more pages are passing one-offs to the same components, those will be fighting eachother.
For this deployment you can hover on one of these three image buttons, and see the whole page's content shift:

Each of those images is a next/link, so a route is pre-fetched... including it's stylesheet. The styles on that page end up overriding conditional styles or styles passed in via a className prop, for a component used on both pages (namely, the "HeroBanner").
Basically, prefetched stylesheets have the potential to obliterate rules on the current page.
The only idea I've had to solve this inside Next.js would be to make a route-named CSS class, wrapping each pages styles in that class ruleset, and then add class="route-named-class" to the __next element, but that's just off the top of my head and I'm sure it's whack compared to what y'all could come up with. A temporary fix would be to not prefetch the page's stylesheet maybe, but that might make transitions look janky. Another idea could be to ensure the current page's stylesheet is resolved last and popped off the top when route changes actually begin?
Ah shit, @Timer I think my issue and @jensmeindertsma are actually different errors. My problem is happening in production and it's not about improper ordering... lemme make a repro repo and a new issue
Most helpful comment
Going to keep this open because this is something that Next.js could improve. I'm willing to help work on it.