DEV Community

Cover image for The CSS Ordering Quiz That Will Break Your Next.js Assumptions
Alessandro Grosselle
Alessandro Grosselle

Posted on

The CSS Ordering Quiz That Will Break Your Next.js Assumptions

Can you predict how Next.js handles CSS import order? This interactive quiz reveals a hidden behavior that might surprise you.

πŸš€ For the Impatient

Want to see the magic immediately? Here are all the links:

πŸ“‚ Repository: https://github.com/ale-grosselle/css-order-next.js

πŸ§ͺ Live Test Cases:

  • Case A - Basic CSS ordering
  • Case B - Server component CSS
  • Case C - Client component follows rules
  • Case D - The mind-bender! 🀯

πŸ•΅οΈβ€β™‚οΈ Pro Tip: Open your browser's DevTools (F12 or right-click β†’ Inspect) and check the Elements panel to see the actual CSS order loaded for each case. This is crucial for understanding how Next.js handles CSS imports!


We all know the golden rule from the Next.js documentation: "CSS import order matters". It's a fundamental principle we've relied on for years. But how well do you really understand CSS ordering in Next.js?

Let's put your knowledge to the test with a hands-on quiz that reveals some surprising behavior you probably didn't know about.

The Setup: Our CSS Ordering Laboratory

I've created a simple Next.js project to test CSS import behavior. Here's what we're working with:

Base Configuration

Layout Component (layout.tsx)

import "./globals.css";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return <div>{children}</div>;
}
Enter fullscreen mode Exit fullscreen mode

Global Styles (globals.css)

div.foo {
    --layout-file: red;
    background-color: var(--layout-file);
}
Enter fullscreen mode Exit fullscreen mode

This sets all .foo divs to red by default.

Our Test Stylesheets

We have several CSS files that all target the same element with different colors:

1.global.css - Makes divs yellow:

div.foo {
    --1-global-page-css: yellow;
    background-color: var(--1-global-page-css);
}
Enter fullscreen mode Exit fullscreen mode

0.module.css - CSS Module for brown divs:

div.body-module {
    --0--page--css--module: brown;
    background-color: var(--0--page--css--module);
}
Enter fullscreen mode Exit fullscreen mode

Server Component CSS (components/2.css):

div.foo {
    --2-rsc-component: orange;
    background-color: var(--2-rsc-component);
}
Enter fullscreen mode Exit fullscreen mode

Client Component CSS (components/3.css):

div.foo {
    --3-client-component: black;
    background-color: var(--3-client-component);
}
Enter fullscreen mode Exit fullscreen mode

Now, let's see how well you can predict CSS ordering behavior!


🧩 Quiz Time: Can You Guess the Color?

Case A: The Baseline Test

🌐 Try Case A Live

// case-a/page.tsx
import "../1.global.css";
import style from "../0.module.css";

export default function HomePage() {
  return (
    <div className={`${style["body-module"]} foo`}>HELLO</div>
  );
}
Enter fullscreen mode Exit fullscreen mode

πŸ€” Your Prediction: What background color will the div have?

A) Red (from globals.css)
B) Yellow (from 1.global.css)
C) Brown (from 0.module.css)

βœ… Correct Answer: C) Brown

Why: Layout CSS (globals.css) is loaded first in a separate bundle. Within the page bundle, 0.module.css is imported LAST, so at equal specificity it wins over both globals.css and 1.global.css.


Case B: Adding a Server Component

🌐 Try Case B Live

// case-b/page.tsx
import "../1.global.css";
import style from "../0.module.css";
import { Example } from "@/components/Example"; // Server component with CSS

export default function HomePage() {
  return (
    <>
      <div className={`${style["body-module"]} foo`}>HELLO</div>
      <Example />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

The Example component imports ./2.css which makes .foo divs orange.

πŸ€” Your Prediction: What color will the div be now?

A) Red (from globals.css)
B) Brown (from 0.module.css)
C) Orange (from server component 2.css)

βœ… Correct Answer: C) Orange

Why: Server component CSS follows import order. The Example component is imported LAST, so its CSS comes after everything else and wins.


Case C: Still Following the Rules

🌐 Try Case C Live

// case-c/page.tsx
import { Example } from "@/components/Example";
import "../1.global.css";
import style from "../0.module.css";
import { ExampleClientComponent } from "@/components/Example-client-component"; // Client component!

export default function HomePage() {
  return (
    <>
      <div className={`${style["body-module"]} foo`}>HELLO</div>
      <ExampleClientComponent />
      <Example />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now we have:

  • Example (server component with orange CSS) imported FIRST
  • 1.global.css (yellow) imported SECOND
  • 0.module.css (brown) imported THIRD
  • ExampleClientComponent (client component with black CSS) imported LAST

πŸ€” Your Prediction: Based on import order, what color should win?

A) Orange (server component imported first)
B) Brown (from 0.module.css imported third)
C) Black (client component imported last)

βœ… Expected Answer: C) Black

Why: Following the "last import wins" rule, since ExampleClientComponent is imported last, its CSS should come last and win.

πŸ“Š Expected CSS Loading Order:

  1. Layout Bundle: globals.css (red)
  2. Page Bundle: components/2.css (orange) β†’ 1.global.css (yellow) β†’ 0.module.css (brown) β†’ components/3.css (black) ← Should win

And indeed, the div IS black! The rule still holds...


Case D: The Mind-Bender That Breaks Everything

🌐 Try Case D Live

Let's try one more test to confirm our suspicions:

// case-d/page.tsx
import { ExampleClientComponent } from "@/components/Example-client-component"; // FIRST
import { Example } from "@/components/Example";
import "../1.global.css"; // THIRD
import style from "../0.module.css"; // LAST CSS import

export default function HomePage() {
  return (
    <>
      <div className={`${style["body-module"]} foo`}>HELLO</div>
      <ExampleClientComponent />
      <Example />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now the client component is imported FIRST, and 0.module.css is imported LAST.

πŸ€” Final Prediction: Based on the "last import wins" rule, what color should the div be?

A) Black (client component imported first)
B) Brown (from 0.module.css imported last)
C) Orange (from server component)

🀯 Answer: Still Black!

The Hidden Truth Revealed: Even though 0.module.css is imported LAST and should win according to the fundamental CSS ordering rule, the client component's CSS still wins!

πŸ“Š Expected CSS Loading Order:

  1. Layout Bundle: globals.css (red)
  2. Page Bundle: components/3.css (black - imported first) β†’ components/2.css (orange) β†’ 1.global.css (yellow) β†’ 0.module.css (brown) ← Should win!

πŸ“Š Actual CSS Loading Order:

  1. Layout Bundle: globals.css (red)
  2. Page Bundle: components/2.css (orange) β†’ 1.global.css (yellow) β†’ 0.module.css (brown)
  3. Client Bundle: components/3.css (black) ← Always wins!

This completely breaks the "last import wins" rule! Client component CSS gets moved to the end regardless of where you import it.


πŸ” The Discovery: Next.js Has a unclear CSS Rule

What I learned:

  • βœ… Layout CSS is always loaded first (separate bundle)
  • βœ… Server component CSS follows import order within the page
  • ❌ Client component CSS always loads after server component CSS, regardless of import order.
  • βœ… Among client components, import order between multiple client components is still respected

🚨 MUST-Follow Rules to Avoid CSS Chaos

After discovering this hidden behavior, the Next.js CSS recommendations become absolute requirements, not just suggestions:

1. Use CSS Modules for ALL Component-Specific Styles

// βœ… DO: Always use CSS Modules for components
import styles from "./MyComponent.module.css";

// ❌ DON'T: Never use global CSS in components
import "./MyComponent.css"; // This creates ordering conflicts!
Enter fullscreen mode Exit fullscreen mode

Why this is critical: CSS Modules provide scoping that prevents the CSS ordering conflicts we discovered. Your styles won't accidentally override or be overridden by other components.

2. Import CSS Only Once Per Component

// βœ… DO: One CSS import per component
import styles from "./Button.module.css";

export const Button = () => <button className={styles.button}>Click me</button>;

// ❌ DON'T: Multiple CSS imports in one component
import "./theme.css";
import "./button.css";
import "./overrides.css"; // Unpredictable ordering!
Enter fullscreen mode Exit fullscreen mode

Why this matters: Multiple CSS imports create dependency chains that behave differently for server vs client components.

3. Define Global CSS ONLY in Layout

// βœ… DO: Only in layout.tsx
import "./globals.css";
import "./tailwind.css"; // If using Tailwind

// ❌ DON'T: Global CSS anywhere else
// pages/home.tsx - NEVER do this!
import "../globals.css"; // This breaks predictability
Enter fullscreen mode Exit fullscreen mode

Why this is essential: Global CSS should be loaded once and consistently. Importing it multiple places creates the exact ordering conflicts we discovered.


Did this quiz surprise you? Have you encountered similar CSS ordering mysteries in your Next.js projects? Share your experiences in the comments!

Top comments (0)