Cloudflare Wrangler Sourcemaps on Sentry
June 22, 2025
I just spent way too much time figuring out how to get proper sourcemaps working for a Cloudflare Workers project that uses Sentry for error monitoring. The default Wrangler setup uses esbuild internally, and while you can get sourcemaps working with esbuild, Sentry's Vite plugin makes this trivially easy.
The Problem
Cloudflare Workers deployed via wrangler deploy
use esbuild by default. This is fast and works
great, but when errors happen in production, Sentry shows you minified stack traces that are
basically useless:
Error: Something went wrong
at a (index.js:1:234)
at b (index.js:1:567)
Not helpful when you're trying to debug a production issue at 2am.
The Solution
Wrangler supports Vite as an alternative bundler, and Sentry has excellent Vite plugin support. Here's my complete config:
vite.config.ts:
import { resolve } from "node:path";
import { cloudflare } from "@cloudflare/vite-plugin";
import { sentryVitePlugin } from "@sentry/vite-plugin";
import { defineConfig, loadEnv } from "vite";
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), "");
const envValue =
env["NODE_ENV"] === "development" ? "development" : "production";
return {
define: {
__ENV__: JSON.stringify(envValue),
},
resolve: {
alias: {
"~": resolve(__dirname, "./src"),
},
},
plugins: [
cloudflare(),
sentryVitePlugin({
org: process.env["SENTRY_ORG"] ?? "your-org",
project: process.env["SENTRY_PROJECT"] ?? "api",
authToken: process.env["SENTRY_AUTH_TOKEN"],
debug: envValue === "development",
sourcemaps: {
assets: [".vite/your_worker/**/*.js", ".vite/your_worker/**/*.map"],
},
}),
],
build: {
sourcemap: true,
outDir: resolve(__dirname, "./.vite"),
},
server: {
port: 8787,
host: "0.0.0.0",
allowedHosts: true,
},
};
});
Injecting Build-Time Variables
One nice feature of Vite is the define
option, which lets you inject constants at build time. I
use this for environment detection:
define: {
__ENV__: JSON.stringify(envValue),
}
Then in your worker code:
declare const __ENV__: string;
This gets replaced at build time, so there's no runtime overhead. Much cleaner than checking
process.env
or other runtime environment detection.
Environment Variables Gotcha
Here's something that tripped me up: Cloudflare Workers bindings (like secrets) don't work in CI
environments. You can't use wrangler secrets put SENTRY_AUTH_TOKEN
and expect it to be available
during your build process.
Instead, you need to set SENTRY_AUTH_TOKEN
as a regular environment variable if you're using Worker
Builds.
The Sentry auth token is only needed at build time for uploading sourcemaps - it's not a runtime dependency of your worker.
Package Scripts
package.json:
{
"scripts": {
"deploy": "vite build && wrangler deploy --outdir=.vite/your_worker",
"dev": "vite dev"
}
}
wrangler.jsonc
{
"name": "your-worker",
"compatibility_date": "2025-06-17"
"main": "./src/index.ts",
}
What This Gets You
- Automatic sourcemap upload: The Sentry Vite plugin handles uploading sourcemaps during build
- Proper stack traces: Production errors now show actual function names and line numbers
- Release tracking: Each deploy gets tagged with a release ID for better error correlation
- Build-time constants: Use
define
to inject environment-specific values - Hot reloading: Development server with proper alias resolution
The Critical Detail
Make sure your wrangler deploy --outdir
matches your Vite build.outDir
. In my case, Vite outputs
to .vite/
and Wrangler deploys from .vite/your_worker/
. This took me way too long to debug when
they were mismatched.
Development vs Production
For development, wrangler dev
automatically detects and uses your Vite config. For production, the
two-step vite build && wrangler deploy
ensures sourcemaps are generated and uploaded before the
worker goes live.
Sentry Integration with Release Tracking
Here's how I wire up Sentry in the actual worker code to use the deployment metadata for release tracking:
interface Env {
Variables: {};
Bindings: Readonly<Cloudflare.Env>;
}
const app = new Hono<Env>();
app.get("/_error", () => {
throw new Error(`😵💫 im an error on ${__ENV__}`);
});
app.get("/_error/sentry", () => {
Sentry.withScope((scope) => {
scope.setExtra("foo", "bar");
Sentry.captureException(new Error("foobar"));
});
return new Response("😖 im a sentry error");
});
const handler: ExportedHandler<Cloudflare.Env> = {
fetch: app.fetch,
}
export default Sentry.withSentry((env) => {
const typedEnv = env;
const { id: versionId } = typedEnv.CF_VERSION_METADATA;
return {
dsn: typedEnv.SENTRY_DSN,
release: versionId,
environment: __ENV__,
sendDefaultPii: true,
};
}, handler);
The magic here is CF_VERSION_METADATA
- this gives you access to Cloudflare's deployment metadata,
including a unique version ID for each deployment. This becomes your Sentry release ID, which links
errors to specific deployments and sourcemaps.
To enable this binding, add it to your wrangler.jsonc
:
{
"version_metadata": {
"binding": "CF_VERSION_METADATA"
}
}
Now when errors occur, they're automatically tagged with the exact deployment version, making it easy to correlate issues with specific releases.
The Complete Picture
With this setup:
- Vite builds your worker and generates sourcemaps
- Sentry plugin uploads sourcemaps tagged with version metadata
- Wrangler deploys with the version metadata binding
- Runtime errors get tagged with the deployment version
- Sentry shows proper stack traces with source file/line numbers
This setup has made debugging production issues so much easier. No more guessing which minified function threw an error. The combination of Vite's modern tooling and Sentry's excellent plugin support really does make a difference.
Useful Resources
- Cloudflare Vite Plugin - Official Vite plugin for Cloudflare Workers
- Sentry Vite Plugin - Documentation for Sentry's Vite integration
- Sentry Cloudflare Integration - Setting up Sentry with Cloudflare Workers