Quick Answer
Install vue-loader and @vue/compiler-sfc, add the rule to your webpack config, and include the VueLoaderPlugin. That's it.
Why This Happens
Webpack 5 doesn't know how to read a .vue file out of the box. It sees the <template> tag and chokes on the angle brackets, throwing Module parse failed: Unexpected token. This usually hits when you're setting up a Vue project manually or upgrading from webpack 4, where the config might've been looser. I had a client last month whose build broke exactly like this after they pulled a fresh Vue 3 project and forgot to carry over the vue-loader setup. The error points to the first line of the template block, which confuses people because it looks like valid HTML.
The real fix is telling webpack to hand .vue files to vue-loader, which splits the SFC into separate JavaScript, template, and style parts. Without that loader, webpack tries to parse the whole file as plain JavaScript and fails on the first non-JS token.
Fix Steps
- Install the required packages
Run this in your project root:
If you're on Vue 2, you'd usenpm install --save-dev vue-loader @vue/compiler-sfcvue-template-compilerinstead of@vue/compiler-sfc. But since you're likely on Vue 3, the compiler-sfc is the right one. - Update your webpack.config.js
Add the module rule and the plugin. Here's the minimal config that works for webpack 5:
The plugin is non-negotiable. It does the heavy lifting of cloning your other rules (like babel or sass) and applying them to the language blocks inside the SFC.const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
},
plugins: [
new VueLoaderPlugin()
]
} - Check your resolve extensions
Make sure.vueis in the resolve.extensions array, so imports likeimport App from './App.vue'don't fail because webpack can't find the file. Add it like this:resolve: {
extensions: ['.js', '.vue', '.json']
} - Rebuild
Run your build command again. If the error persists, double-check that you didn't accidentally have two Vue copies or a mismatched version of vue-loader and vue. The plugin and loader must be the same major version.
Alternative Fixes
If you've done all that and it's still failing, try these:
- Clear your node_modules and reinstall
Sometimes the package manager leaves stale versions. Deletenode_modulesandpackage-lock.json, then runnpm installagain. - Check for a rogue babel rule
If you have a rule that applies to/\.[jt]sx?$/with a loader like babel-loader, make sure it's not also catching.vuefiles. Addexclude: /\.vue$/to that rule to avoid conflicts. - For Vue 2 specifically
Usevue-loader@15andvue-template-compiler. The newer vue-loader 16+ is built for Vue 3 and won't work with Vue 2's compiler.
Prevention Tip
The best way to avoid this is to start from the official Vue project scaffolding. Run npm create vue@latest and let the tool set up the webpack config for you. If you're manually configuring webpack, always add the vue-loader plugin right after you add the rule, and keep the versions in sync. I've seen this error pop up months later when someone updates vue-loader without updating vue, or vice versa. Check the version compatibility table before upgrading anything.
Also, if you're migrating from webpack 4 to 5, watch out for changes in how loaders are resolved. Webpack 5 is stricter about the loader vs use syntax, but the above config works for both. Save yourself the headache and test your build after every dependency bump.