I ran into a frustrating problem while deploying my Nuxt.js project to Heroku. Everything ran flawlessly on my local machine, but the Heroku build kept failing with a scary-looking Webpack error. After burying myself in debugging for three days, I finally figured it out. I’m sharing the journey here in case it saves you the same headache.
The Buggy Code I Started With
In my layouts/default.vue file, I had this:
<template>
<div>
<Sidebar />
<nuxt/>
</div>
</template>
<script>
// Buggy import: wrong path casing, brittle relative path
import Sidebar from '../components/Sidebar/_Sidebar.vue'
export default {
components: { Sidebar }
}
</script>
And this was my repo structure:
components/
└── sidebar/
└── _Sidebar.vue
layouts/
└── default.vue
Notice the mismatch? The folder is sidebar/ (lowercase), but I imported it as Sidebar/ (uppercase). On macOS or Windows, this still works because the filesystem is case-insensitive. But Heroku runs on Linux, which is case-sensitive. That’s why my local build passed but Heroku exploded.
The Error Explain
Heroku’s build log screamed:
Module not found: Error: Can't resolve '../components/Sidebar/_Sidebar.vue'
Webpack was telling me the file didn’t exist at that exact path. The truth: it didn’t, at least not with the exact casing I typed. So the fix wasn’t about dependencies or Nuxt itself—it was about being precise with filenames and imports.
The Fix
Use Nuxt’s Alias + Correct Case
Instead of relying on fragile relative paths, I switched to Nuxt’s alias (~ or @) and matched the on-disk case exactly.
Here’s my updated layouts/default.vue:
<template>
<div>
<!-- Sidebar is lazy-loaded -->
<Sidebar :links="navLinks"/>
<nuxt/>
</div>
</template>
<script>
// Lazy-load, use alias, match casing
const Sidebar = () => import('~/components/sidebar/_Sidebar.vue')
export default {
components: { Sidebar },
data () {
return {
navLinks: [
{ text: 'Home', to: '/' },
{ text: 'About', to: '/about' }
]
}
}
}
</script>
The Sidebar Component
Here’s how I structured the components/sidebar/_Sidebar.vue file:
<template>
<aside class="sidebar">
<nav>
<ul>
<li v-for="(l, idx) in links" :key="idx">
<nuxt-link :to="l.to">{{ l.text }}</nuxt-link>
</li>
</ul>
</nav>
</aside>
</template>
<script>
export default {
name: 'Sidebar',
props: {
links: {
type: Array,
default: () => []
}
}
}
</script>
<style scoped>
.sidebar { padding: 1rem; border-right: 1px solid #eee; }
.sidebar a { text-decoration: none; }
</style>
This gave me a flexible sidebar with typed props and safe defaults.
Guard Against Case Error at Build Time
To catch this kind of problem locally before pushing to Heroku, I added case-sensitive-paths-webpack-plugin.
npm i -D case-sensitive-paths-webpack-plugin
And updated my nuxt.config.js:
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin')
export default {
build: {
vendor: ['axios', 'vue-touch'],
extend (config, { isClient }) {
if (isClient) {
config.module.rules.push({
enforce: 'pre',
test: /\.(js|vue)$/,
loader: 'eslint-loader',
exclude: /(node_modules)/
})
}
config.plugins = config.plugins || []
config.plugins.push(new CaseSensitivePathsPlugin())
}
},
alias: {
'hammerjs$': 'vue-touch/dist/hammer-ssr.js'
},
plugins: ['~plugins/vue-touch'],
css: [{ src: '~assets/scss/app.scss', lang: 'sass' }]
}
Git Case Sensitivity Fix
On macOS and Windows, Git sometimes ignores case-only renames. To force it, I had to do a two-step rename:
# Directory rename
git mv components/sidebar components/__tmp_sidebar && git commit -m "temp rename"
git mv components/__tmp_sidebar components/sidebar && git commit -m "fix: normalize dir casing"
# File rename
git mv components/sidebar/_Sidebar.vue components/sidebar/__tmp.vue && git commit -m "temp file rename"
git mv components/sidebar/__tmp.vue components/sidebar/_Sidebar.vue && git commit -m "fix: normalize file casing"
git push
Bonus Heroku SCSS Dependency Tip
Another common Heroku pitfall is missing SCSS dependencies. Since Heroku only installs production dependencies, I moved node-sass and sass-loader into dependencies:
"dependencies": {
"node-sass": "^4.5.0",
"sass-loader": "^6.0.2",
"axios": "^0.15.3",
"nuxt": "^0.9.9",
"vue-touch": "^2.0.0-beta.4"
}
Extra Practice Functionality
Here are a couple of “nice-to-have” improvements I added while fixing the error:
- Lazy-loading components: speeds up initial page load.
- Prop typing with defaults: avoids runtime surprises.
- Case-sensitive guard plugin: fails early instead of failing on Heroku.
- Unit test for Sidebar (with AVA):
// tests/sidebar.spec.js
import test from 'ava'
import { mount } from '@vue/test-utils'
import Sidebar from '@/components/sidebar/_Sidebar.vue'
test('renders links', t => {
const links = [{ text: 'Home', to: '/' }]
const wrapper = mount(Sidebar, { propsData: { links } })
t.true(wrapper.text().includes('Home'))
})
Final Thought
What I learned from this is simple but critical: case sensitivity matters. Just because something works locally doesn’t mean it’ll work in production especially when deploying to Linux environments like Heroku. By fixing my imports, using Nuxt’s aliases, and adding safeguards like CaseSensitivePathsPlugin, I now deploy with confidence. If you ever find yourself stuck with a Webpack error on Heroku that doesn’t appear locally, double check your file paths and casing. Nine times out of ten, that’s the culprit.

