Site icon FSIBLOG

How Do I Fix Nuxt.js Webpack Build Error on Heroku When Everything Works Locally

How Do I Fix Nuxt.js Webpack Build Error on Heroku When Everything Works Locally

How Do I Fix Nuxt.js Webpack Build Error on Heroku When Everything Works Locally

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:

// 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.

Exit mobile version