When I first deployed my Create React App to Heroku, I thought everything would “just work.” Instead, I was greeted with this frustrating error:
Module not found: Error: Can't resolve './app.css' in '/tmp/build_xxx/src'
At first glance, it looked like a silly problem surely it was just a missing file. But the more I dug, the more I realized this issue is a combination of Linux case sensitivity, Git commits, and my own mistake of trying to override Create React App (CRA) with a custom Webpack config.
Let me walk you through exactly what went wrong, how I fixed it, and how I ended up with a cleaner, more scalable CSS setup.
The First Error I Saw in My Project
Here’s the minimal code that triggered the error.
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './app.css'; // CRA will crash if this file is missing or misnamed
function App() {
return <h1>Hello Heroku</h1>;
}
ReactDOM.render(<App />, document.getElementById('root'));
src/app.css
h1 {
font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, "Helvetica Neue", Arial, "Noto Sans";
font-size: 2rem;
}
On my Mac, everything worked fine. But Heroku builds on Linux, which is case-sensitive. That means App.css
and app.css
are two different files. If you get the casing wrong in your import
, Heroku can’t find it—even if it worked locally.
Why This Error Happens on Heroku
Here’s what I learned:
- Linux cares about casing
App.css
≠app.css
. On macOS/Windows, it doesn’t matter. On Heroku, it does. - CRA ignores custom webpack.config.js
I added awebpack.config.js
and installedcss-loader
—but CRA uses its own config. My changes did nothing. - Git is the source of truth
If I created a file locally but never committed it (git add
+git commit
), Heroku simply never saw it.
What Was Wrong in My Setup
I had:
- Installed
css-loader
indevDependencies
. - Added a
webpack.config.js
that CRA never read. - An import mismatch: I thought I had
app.css
, but the file was actually namedApp.css
.
Heroku didn’t care about my local setup—it just failed with Can't resolve './app.css'
.
Step by Step Fix I Applied
Here’s the exact process I followed to fix it:
- Remove custom Webpack and css-loader
rm webpack.config.js yarn remove css-loader
- Match the filename and import exactly
- If I write
import './app.css';
, the file must be exactlysrc/app.css
.
- If I write
- Commit the CSS file to Git
git add -A git commit -m "Fix CSS path/case and remove custom webpack" git push heroku main
- Optional: Clear Heroku cache if needed
heroku plugins:install heroku-builds heroku builds:cache:purge -a <my-app-name> git push heroku main
After that, the app built successfully and my styles loaded perfectly on Heroku.
Leveling Up: Better CSS Practices in CRA
Once I fixed the basics, I took the opportunity to clean up my CSS setup without touching Webpack.
Add Normalize.css
I already had normalize.css
installed, so I imported it in src/index.js
:
import 'normalize.css'; // reset browser styles
import './app.css'; // then load my own styles
Use CSS Modules
CRA supports CSS Modules automatically for *.module.css
files.
src/Button.module.css
.button {
padding: 0.75rem 1rem;
border-radius: 9999px;
border: none;
cursor: pointer;
}
.primary {
background: #111827;
color: #fff;
}
src/components/Button.js
import React from 'react';
import styles from '../Button.module.css';
export default function Button({ children, variant = 'primary' }) {
return (
<button className={`${styles.button} ${styles[variant]}`}>
{children}
</button>
);
}
Now I had scoped, conflict-free styles.
Add Global Variables
src/styles/vars.css
:root {
--brand: #4f46e5;
--text: #111827;
--muted: #6b7280;
}
src/app.css
@import './styles/vars.css';
body {
color: var(--text);
}
a {
color: var(--brand);
}
Split CSS by Purpose
I separated base, layout, and utilities:
styles/base.css
→ resets & defaultsstyles/layout.css
→ containers, gridsstyles/util.css
→ margin/padding helpers
Then I imported them all in index.js
.
The Final Working Example
Here’s what my final cleaned-up project looked like.
package.json (no css-loader, no webpack.config.js)
"dependencies": {
"normalize.css": "^8.0.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "5.0.0"
}
src/index.js
import 'normalize.css';
import './app.css';
import React from 'react';
import ReactDOM from 'react-dom';
function App() {
return (
<main className="container">
<h1>Heroku + CRA CSS works </h1>
</main>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
src/app.css
.container {
max-width: 720px;
margin: 2rem auto;
padding: 1rem;
}
h1 {
font-size: 2rem;
line-height: 1.2;
}
And finally deployment worked perfectly.
Final Thought
This whole experience taught me a simple but important lesson: don’t fight CRA’s defaults unless you absolutely have to. The error wasn’t about loaders or Webpack tweaks it was just about path casing and file management.
Once I stopped over-engineering and trusted the defaults, things worked exactly as intended. On top of that, I took the opportunity to clean up my CSS setup with Normalize, CSS Modules, and a structured file system.
So if you’re stuck with Heroku’s Can’t resolve './app.css'
error check your filenames, commit your files, and keep it simple. Chances are, the fix is easier than you think.