Chirag's Blog

Debugging with Source Maps: A Comprehensive Guide

March 9, 2025

We all have been there, working on a new feature for 10+ hours straight, and everything is going well. You build your project and push the code to production. And boom, a new production error alert! Everyone in your team - whether at a company or a hackathon - starts to look for someone to blame. Found it; it was from you. But none of your test suites resulted in an error. Everything with the code itself looks excellent.

You check the logs. The error is:

1Uncaught Error: Cannot read property 'xyz' of undefined at app.min.js:1:45678

You think to yourself, what the heck is app.min.js:1:45678 supposed to mean? There was no file like that in the entire source code? Your file was called app.js. And it's 45678 characters long! That's impossible to debug!! Still, you try to open the file and potentially find the root cause of the error. It's a mess. The entire file is filled with random gibberish you are unable to understand. What should you do?

Now, this is where Source Maps come into play. Source Maps allow you to map the minified code in your production environment, aka the random gibberish you just saw, with the actual source code, allowing you to pinpoint the root cause of the error in your source code and debug it effectively.

In this blog, we will detail what source maps are, why and how they are created, and give some tips on effectively using source maps to debug your code. Let's dive in!

Why is source code minified?

Before we delve into Source Maps, let's first decode what happened to your clean, formatted and linked source code and why it looks nothing like it on the browser.

The simple answer is minification.

Minification is the process of converting your source code into production-ready code without changing any of its functionality. This is typically done by the bundler you are using, such as Webpack. To learn more about bundlers, you can check out this awesome guide on Javascript bundlers by Snipcart.

Simply put, bundlers optimise your source code by stripping out whitespaces, comments, and redundant code and even removing or renaming variables for shorter alternatives. This makes your code super efficient and much smaller in size.

Why does this happen?

Here is an example of what a minified React app code looks like:

Bundle.js file containing minified code
bundle.js file created by Webpack on building a simple React app, even the text is overflowing from the terminal screenshot!

What are source maps?

Source maps are files whose names end with .map and that map the minified code to your actual source code. Examples of such files can be example.min.js.map or for css, styles.css.map. They are explicitly generated only by build tools like Webpack, Vite, Rollup, Parcel etc. Since source maps are only required for debugging purposes, these tools usually have the option to generate source maps off by default. For example to enable it in Webpack, you can do:

1// add this to your package.json file
2"scripts": {
3 "build:dev": "webpack --mode development --devtool source-map",
4}

or add it to your webpack.config.js file:

1module.exports = {
2 devtool: "source-map",
3 // ...rest of your config
4};

A source map includes crucial information on how the mapping is done, including the actual source file name, the content it includes, various variable name the source code has, name of the minified code file etc.

Here is a format of how a typical source map file looks like:

1{
2 "mappings": "AAAA,SAAQA,MAAMA,QAAQ,OAAO;AAC7B,SAAQC...",
3 "sources": ["src/index.js"],
4 "sourcesContent": [
5 "import React from 'react';\nimport { createRoot } from 'react-dom/..."
6 ],
7 "names": ["React", "createRoot", "App", "count", "setCount", "useState", ...],
8 "version": 3,
9 "file": "bundle.js.map"
10}

The most important section here is mappings. This uses a special kind of encoding called VLQ base 64 encoded string to map the lines and locations to compiled file and its corresponding original file.

Visualising source maps

"Okay, great!" I hear you saying. "How is this actually helpful? I still can't read the source maps and manually decode the mappings."

That's a great question! This brings me to the main highlight of this blog—source map visualisers. These tools allow you to see the mappings in a visual manner to locate and debug the problem effectively. There are many source map visualisers on the market, but today, we will be focusing on Sokra & Paulirish's source map visualization. You can find the source code for this on their Github Repository.

Here is a side-by-side comparison of how your code (on the right-hand side) can look like a jumbled mess when minified (on the left-hand side). However, the colour-coded mapping of the visualiser helps us map these two codes by hovering over them.

Comparison between minified code and source code

Working Example

Let’s create a simple React app and play around with it’s sourcemaps!

  1. Start with creating a project directory:
1mkdir my-project
2cd my-project
  1. Init a new project:
1npm init -y
  1. Add the following dependencies into package.json
1{
2 "name": "react-counter-app",
3 "version": "1.0.0",
4 "description": "Simple React Counter App",
5 "main": "index.js",
6 "scripts": {
7 "start": "webpack serve --mode development",
8 "build": "webpack --mode production",
9 "test": "echo \"Error: no test specified\" && exit 1"
10 },
11 "dependencies": {
12 "react": "^18.2.0",
13 "react-dom": "^18.2.0"
14 },
15 "devDependencies": {
16 "@babel/core": "^7.23.0",
17 "@babel/preset-env": "^7.23.0",
18 "@babel/preset-react": "^7.22.15",
19 "babel-loader": "^9.1.3",
20 "css-loader": "^6.8.1",
21 "html-webpack-plugin": "^5.5.3",
22 "style-loader": "^3.3.3",
23 "webpack": "^5.88.2",
24 "webpack-cli": "^5.1.4",
25 "webpack-dev-server": "^4.15.1"
26 }
27}
  1. Create a src/index.js file with following React code:
1// src/index.js
2import React from "react";
3import { createRoot } from "react-dom/client";
4import "./styles.css";
5
6function App() {
7 const [count, setCount] = React.useState(0);
8
9 const increment = () => {
10 setCount(count + 1);
11 };
12
13 const decrement = () => {
14 setCount(count - 1);
15 };
16
17 return (
18 <div className="app">
19 <h1>Counter: {count}</h1>
20 <button onClick={increment}>Increment</button>
21 <button onClick={decrement}>Decrement</button>
22 </div>
23 );
24}
25
26// New React 18 createRoot API
27const container = document.getElementById("root");
28const root = createRoot(container);
29root.render(<App />);
  1. Add styling by adding a src/styles.css file:
1/* src/styles.css */
2.app {
3 font-family: Arial, sans-serif;
4 max-width: 500px;
5 margin: 0 auto;
6 padding: 20px;
7 text-align: center;
8}
9
10button {
11 background-color: #4caf50;
12 border: none;
13 color: white;
14 padding: 10px 20px;
15 text-align: center;
16 text-decoration: none;
17 display: inline-block;
18 font-size: 16px;
19 margin: 10px;
20 cursor: pointer;
21 border-radius: 4px;
22}
23
24button:hover {
25 background-color: #45a049;
26}
  1. Now define the webpack config by creating a webpack.config.js file in the root folder:
1// webpack.config.js
2const path = require("path");
3const HtmlWebpackPlugin = require("html-webpack-plugin");
4
5module.exports = {
6 entry: "./src/index.js",
7 output: {
8 path: path.resolve(__dirname, "dist"),
9 filename: "bundle.js",
10 },
11 module: {
12 rules: [
13 {
14 test: /\.(js|jsx)$/,
15 exclude: /node_modules/,
16 use: {
17 loader: "babel-loader",
18 options: {
19 presets: ["@babel/preset-env", "@babel/preset-react"],
20 },
21 },
22 },
23 {
24 test: /\.css$/,
25 use: ["style-loader", "css-loader"],
26 },
27 ],
28 },
29 plugins: [
30 new HtmlWebpackPlugin({
31 template: "./public/index.html",
32 }),
33 ],
34 devServer: {
35 static: {
36 directory: path.join(__dirname, "public"),
37 },
38 port: 3000,
39 open: true,
40 },
41 resolve: {
42 extensions: [".js", ".jsx"],
43 },
44};
  1. Now you can start the application by running:
1webpack serve --mode development

This is how it should look (very basic i know :D):

Basic UI Image

You can find the source maps using your browser's dev tools. The format can look different depending on the browser you are using. Here, I am using Zen, but the format should look similar for all browsers.

You can do so by right-clicking anywhere on the page and clicking on Inspect Element. Then, go to the Sources section of your browser and find the source file. Here on Zen, it's available in the debugger section since it's mainly used for debugging purposes.

Showing source map in dev tools

Now, you can load this in the source-map-visualization. It will look something like this:

Showing a visualization of source map

On right you can skip all the React code and skip to the section that contains just your code. On hovering over each section of your code you will see exactly which part of the minified code it maps to!

It can look pretty confusing at first, but try hovering over various elements in the UI and you will see how intuitive it actually is. For eg. in this example code,

1React.useState(0) ----> t().createElement("h1",null,"Counter: ",n) .... // so on

Hovering over React.useState reveals that it maps to a createElement in the minified code. So our bundler, Webpack, in this case, optimised our code by directly converting our state into a javascript element and directly modifying it in subsequent code. This makes our application much, much more performant and reduces the file sizes the browser has to load!

Security Considerations

While creating the example app, you may have noticed we had to explicitly add the flag --mode development to the Webpack run command. This is because source maps are supposed to be used for debugging purposes only, and can lead to security concerns when used in production, including:

ConcernDescriptionMitigation
Exposing Source CodeSource maps reveal your original code, including comments and logicUse hidden-source-map or nosources-source-map in production
IP ProtectionIntellectual property may be exposed via full source mapsDeploy source maps to secure, authenticated location
File SizeSource maps can be large, affecting download performanceGenerate maps only in development, or serve separately
Server ConfigurationCORS issues may prevent source map loadingConfigure proper Access-Control-Allow-Origin headers

There are also tools like Sentry or Rollbar which use your source maps for better error reports without violating any of the security concerns. Tools like these are considered best practise for production environments.

Conclusion

Source maps are a mind-blowing feature that lets you map your source code precisely to the minified code loaded by your browsers, which is generated by bundlers like Webpack for performance and speed. We explored how debugging can be made easy using this feature and tools like source map visualisers to aid in the process.

The web is built on top of layers and layers of abstractions done by tools like bundlers, but when things go catastrophically wrong, we might discover that these abstractions are not always hundred percent perfect, and hence, we need to take out our tools, look under the hood, and find the fix ourselves.

To learn more about package managers like NPM, Bun, PNPM, and yarn, you can check out my other article, Mastering npm: A Comprehensive Guide to Package Management.

References

Thanks to these fantastic references by the Google Chrome dev team that helped me learn about source maps myself and in writing this article: