The Error Scenario
You’ve just finished a coding session or migrated to a new build tool. You hit refresh, expecting to see your app, but the screen stays blank. When you open the browser console, you're greeted by a single, cryptic line:
Uncaught SyntaxError: Unexpected token '<'
This error is frustrating because it rarely points to a specific component. Instead, it signals a fundamental breakdown between your server and the browser. Essentially, the browser expected a JavaScript file but received something else—usually an HTML file.
Why is the browser seeing a '<'?
Two main issues typically trigger this in a React environment:
- JSX Transpilation Failure: Browsers cannot read JSX directly. If you try to run
<App />without converting it to standard JavaScript first, the browser hits that first<and immediately stops. This happens if your build tool (Babel, Vite, or SWC) isn't processing your files correctly. - The HTML Fallback Trap: This is the most frequent culprit. Your
index.htmlmight request/main.js, but if the server can't find that file, it often returnsindex.htmlinstead. Because HTML files start with<!DOCTYPE html>, the browser tries to parse that first<as JavaScript and fails.
Quick Fix: Check your Paths and Extensions
Start with the simplest solutions before touching your configuration files.
1. Use the Network Tab
Open Developer Tools (F12) and head to the Network tab. Refresh your page. Look for your JavaScript bundles, such as bundle.js or main.jsx. If you see a 404 Not Found status, your script path is wrong. Click on the failed request and check the "Response" sub-tab; if you see HTML code there, the server is serving your homepage instead of your script.
2. Strict File Extensions in Vite
Vite is faster than Webpack but much stricter. It will not transpile JSX inside a file ending in .js by default. If your component contains <div> tags but is named App.js, rename it to App.jsx. This single change fixes the error for many developers migrating from Create React App.
Permanent Fix: Configuring the Build Pipeline
If your paths are correct but the browser is still seeing raw JSX, you need to verify your transpiler settings.
Configuring Webpack and Babel
Modern React projects using Webpack require babel-loader and the React preset. First, confirm the packages are installed:
npm install --save-dev @babel/preset-react babel-loader
Update your babel.config.json to use the modern JSX transform. This removes the requirement to import React in every single file:
{
"presets": [
["@babel/preset-react", {
"runtime": "automatic"
}]
]
}
In your webpack.config.js, ensure the loader targets both .js and .jsx files:
module.exports = {
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: "babel-loader"
}
]
},
resolve: {
extensions: [".js", ".jsx"]
}
};
Configuring Vite
Vite requires the official React plugin to handle JSX. If you started a "vanilla" Vite project and added React later, you might be missing this. Install it via terminal:
npm install @vitejs/plugin-react --save-dev
Then, ensure your vite.config.js includes the plugin:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()]
})
Handling Server-Side Routing (Express/Nginx)
Does the error only appear when you refresh a page like myapp.com/settings? This happens because the browser looks for the script at myapp.com/settings/main.js instead of the root. If your script tag uses a relative path like src="main.js", it will break on any sub-page.
The Fix: Force the browser to look at the root directory by adding a leading slash to your script source in index.html:
<!-- Avoid this -->
<script src="bundle.js"></script>
<!-- Use absolute paths -->
<script src="/bundle.js"></script>
Debugging and Prevention
Build errors often hide in small syntax mistakes within your configuration. When I'm troubleshooting package.json or complex Babel objects, I use ToolCraft's JSON Formatter. It highlights missing commas or mismatched brackets that cause build tools to silently ignore your settings. If you are moving configurations from a YAML-based CI/CD pipeline back to a local JSON config, ToolCraft's YAML to JSON converter helps ensure the logic stays identical.
Final Verification
- Nuke the Cache: Delete the
node_modules/.cachefolder (Webpack) ornode_modules/.vite(Vite) and restart your server. - Check Page Source: Right-click your app in the browser and select "View Page Source". Click the link to your JavaScript file. If you see
<!DOCTYPE html>, your server routing is wrong. If you see<div>tags, your transpiler is skipped.

