How to Fix: CSS Module Hot Reloading Issue in Next.js Dev Mode

7 min read

CSS Module hot reloading breaks in Next.js dev mode when the stylesheet changes but the browser keeps rendering stale class mappings. The result is deceptively simple: you edit a .module.css file, save, and nothing updates until a full refresh or dev server restart.

This issue usually appears in Next.js development mode when Fast Refresh detects JavaScript changes correctly but fails to apply updated CSS Module output consistently. In practice, the bug can come from file watching problems, invalid module boundaries, caching behavior, symlinked packages, or importing CSS Modules from unsupported locations.

Understanding the Root Cause

Next.js hot reloading in development depends on a chain of systems working together: the file watcher notices the change, the bundler recompiles the CSS Module, the module graph invalidates the old stylesheet, and the browser receives the updated asset through HMR or Fast Refresh.

When CSS Module updates do not appear immediately, one of these technical failures is usually responsible:

  • File watcher misses changes: Common in Docker, WSL, network filesystems, synced folders, or monorepos using symlinks. If the watcher does not emit an update event, Next.js never rebuilds the stylesheet.
  • Incorrect CSS Module placement or import path: CSS Modules must be imported from supported component/module files. If the import happens through an unexpected boundary, hot replacement can become unreliable.
  • Stale module graph: If a component tree or layout structure keeps old references alive, the browser may preserve previous class name mappings until a full invalidation occurs.
  • App Router vs Pages Router behavior: Different Next.js versions and router architectures have slightly different dev pipelines. Some versions had known hot reload regressions affecting style updates.
  • Custom webpack or experimental config interference: Loader changes, aliases, transpilation rules, or package externalization can disrupt how CSS assets are tracked.
  • Browser or service worker caching: Less common in pure dev mode, but still possible if a PWA setup or aggressive cache layer serves old CSS.

With CSS Modules, the problem feels worse because Next.js generates scoped class names. If the browser is rendering old markup or old stylesheet chunks, the visible mismatch makes it look like hot reload is broken entirely, even though only part of the refresh chain failed.

Step-by-Step Solution

Use the following checklist in order. It covers the most common causes without requiring access to the original private repository.

1. Verify the CSS Module is used in a supported way

Make sure your stylesheet is actually a CSS Module and imported directly by the component that uses it.

// Correct: Button.tsx or Button.jsx
import styles from './Button.module.css'

export default function Button() {
  return <button className={styles.primary}>Save</button>
}
/* Button.module.css */
.primary {
  background: royalblue;
  color: white;
}

Avoid renaming a global stylesheet as a module without updating imports correctly. Also avoid unusual indirection during debugging, such as re-exporting style objects through multiple files.

2. Confirm the file watcher is detecting changes

Stop the dev server, then restart it and edit only the CSS Module file. Watch the terminal output carefully. If no rebuild occurs after save, the problem is usually the watcher rather than Next.js rendering.

If you are using Docker, WSL, a VM, or a network-mounted folder, try polling-based watching.

# macOS/Linux
CHOKIDAR_USEPOLLING=1 npm run dev
# Windows PowerShell
$env:CHOKIDAR_USEPOLLING=1; npm run dev

If that fixes the issue, your environment is dropping filesystem events. Keep polling enabled for local development or move the project to a native filesystem.

3. Clear the Next.js build cache

A stale .next directory can preserve bad module state after dependency or config changes.

rm -rf .next
npm run dev

On Windows:

rmdir /s /q .next
npm run dev

4. Test without custom Next.js configuration

If your project has a custom next.config.js, temporarily remove experimental options, webpack overrides, or package transpilation rules that affect CSS processing.

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
}

module.exports = nextConfig

Then restart the dev server and retest. If hot reloading starts working, reintroduce custom settings one by one until you find the conflicting option.

If the CSS Module lives in a shared package outside the app root, Next.js may need explicit transpilation support.

/** @type {import('next').NextConfig} */
const nextConfig = {
  transpilePackages: ['@your-org/ui'],
}

module.exports = nextConfig

Also ensure the shared package source is not being prebuilt into a separate output directory that Next.js watches inconsistently during development.

6. Keep CSS Module imports inside client-rendered component boundaries when needed

In newer Next.js app structures, style behavior can look inconsistent if the component ownership boundary changes frequently during refresh. If debugging a complex tree, reduce the example to a small component and import the module directly there.

'use client'

import styles from './Card.module.css'

export function Card() {
  return <div className={styles.card}>Content</div>
}

This does not mean every CSS Module requires a client component, but isolating the style at the point of use helps identify whether the hot reload issue is tied to a boundary or layout composition problem.

7. Upgrade Next.js to the latest stable version

Several dev-mode and style refresh bugs have been fixed across releases. If your app uses an older version, upgrade next, react, and react-dom together.

npm install next@latest react@latest react-dom@latest

After upgrading, delete .next and restart development mode.

8. Disable service workers or PWA caching during debugging

If your app uses a PWA plugin, old CSS can be served from cache even though the dev server rebuilt successfully. Temporarily unregister the service worker in the browser and test again.

// In browser devtools console
navigator.serviceWorker.getRegistrations().then((regs) => {
  for (const reg of regs) reg.unregister()
})

9. Reproduce the issue in a minimal Next.js app

Create a fresh app and verify whether CSS Module hot reload works there. This separates an upstream framework bug from a project-specific configuration issue.

npx create-next-app@latest next-css-hmr-test
cd next-css-hmr-test
npm run dev

Create a small module and edit it repeatedly. If the fresh app works but your private project does not, the root cause is almost certainly environmental or configuration-related.

10. Compare dev behavior between local native filesystem and current environment

If your project sits inside a synced folder, remote workspace, or mounted volume, copy it temporarily to a normal local directory and run it there. CSS hot reload often starts working immediately, confirming a filesystem event issue rather than a Next.js code bug.

Common Edge Cases

  • Editing the wrong file: In monorepos, you may be changing source files while the app imports compiled output from another directory.
  • Case-sensitive path mismatch: Importing ./button.module.css while the file is actually Button.module.css can behave inconsistently across operating systems.
  • Global CSS mixed with module CSS: Accidentally moving rules between globals.css and a module file can make it seem like only some styles hot reload.
  • Duplicate package versions: Multiple copies of react or framework dependencies in a workspace can break refresh behavior.
  • Custom webpack css loader changes: Overriding default loader behavior may stop Next.js from handling CSS Module invalidation correctly.
  • App-level caching layers: Reverse proxies, local CDN tools, or browser extensions can hold stale assets during development.
  • Editor save strategy: Some editors use atomic save or rename-on-save behavior that certain watchers miss in mounted environments.

If none of the fixes above work, the strongest diagnostic path is this: test the same component in a fresh app, on a native filesystem, with the latest stable Next.js, and no custom config. That isolates whether the issue is a real framework regression or a local dev environment problem.

FAQ

Why does JavaScript hot reload work but CSS Modules do not?

Fast Refresh for JavaScript and HMR for styles use related but different invalidation paths. A watcher or bundler problem may affect stylesheet updates while component code still refreshes.

Does deleting the .next folder actually help?

Yes. It will not fix a broken watcher, but it often clears stale compiled assets and invalid module state after dependency upgrades, router migrations, or config changes.

Is this more common in Docker, WSL, or monorepos?

Yes. Those setups frequently introduce missed filesystem events, symlink complexity, or transpilation boundaries that interfere with reliable CSS Module hot reloading in development.

The practical fix is usually one of four things: restore reliable file watching, simplify CSS Module imports, remove conflicting Next.js config, or upgrade to a stable framework version with known HMR fixes.

Leave a Reply

Your email address will not be published. Required fields are marked *