React · Vite · Webpack

Obfuscate a React production bundle without breaking it.

Your React source stays exactly as it is. Build with Vite or Webpack as usual, then protect the generated JavaScript in dist before it goes to the CDN — renaming, string protection, and optional VM protection applied to the code attackers actually download.

The Safe Pattern

Build → Protect → Smoke test → Deploy

Protection is a post-build step. React, JSX, TypeScript, tree-shaking, and code splitting all finish first.

Chunk safeAll emitted chunks are protected in one pass, so shared names stay consistent.
Hydration safeNames referenced by server-rendered markup go on the exclusion list.
DebuggablePrivate symbol mapping lets you decode production stack traces later.
Why the Bundle, Not the Source

Minification is not protection — and JSX is not the thing you ship

A minified React bundle is still trivially readable: component structure, API endpoints, pricing logic, license checks, and string literals all survive minification and pretty-print back in one click. Obfuscation rewrites the generated JavaScript so that structure, names, and strings no longer explain themselves — while the app behaves exactly the same.

What survives minification

String literals, API routes, JSX element structure, error messages, and the overall shape of your business logic. Anyone can un-minify a bundle with browser devtools.

What obfuscation changes

Identifiers become meaningless, strings are moved and protected, logic is restructured, and with Maximum mode the most sensitive functions can run inside a virtual machine layer.

What stays the same

Your React code, your build config, your framework versions, your test suite. Protection reads the finished dist folder and writes a protected copy.

Vite or Webpack — the workflow is the same
npm run build            # emits dist/assets/*.js
npx jso-protector \
  --preset maximum \
  --input dist \
  --output dist-protected
npm run smoke:protected  # serve dist-protected, click through
# deploy dist-protected, not dist
Step by Step

The five steps that keep a React bundle working

  • Build first. Let Vite or Webpack finish JSX, TypeScript, tree-shaking, and chunking before protection runs.
  • Protect all chunks in one pass. Point the tool at the whole dist folder so identifiers renamed in a shared chunk match every file that imports them.
  • Keep file names unchanged. Dynamic import() resolves chunks by URL — protection rewrites contents, not names.
  • Exclude the names the outside world calls. Global callbacks for analytics tags, embed widgets, and anything server-rendered markup references.
  • Smoke test the protected output. Serve dist-protected locally and exercise routing, lazy loading, forms, and third-party scripts before deploying.
React-Specific Pitfalls

The four things that actually break — and how to avoid each one

Lazy chunks renamed apart

Protecting chunks one at a time gives each file its own renaming and the app dies at the first dynamic import. Always protect the folder in a single pass so cross-file renaming stays consistent.

Hydration mismatches

SSR markup that references global functions by name needs those names preserved. Add them to the variable exclusion list instead of weakening the whole run.

Leaked source maps

A deployed .map file undoes every layer of protection. Strip maps from the deploy set and keep the protection run's private mapping for symbolication instead.

Third-party contract breakage

Payment SDKs, tag managers, and chat widgets call into your page by name. List those entry points once in the config and every future release preserves them automatically.

Where to Aim the Strong Settings

Protect the code that is worth stealing hardest.

  • License validation, entitlement checks, and feature gating — Maximum mode with VM protection.
  • Pricing, scoring, or matching algorithms your competitors would copy — strong control-flow settings.
  • General UI components and vendor chunks — standard renaming keeps size and speed unaffected.
  • Measure once with your real bundle: protect, run your Lighthouse or profiling pass, and compare.
CI: protection as a release gate
# .github/workflows/release.yml (excerpt)
- run: npm ci && npm run build
- run: npx jso-protector
       --preset maximum
       --input dist --output dist-protected
- run: npm run smoke:protected
- run: deploy ./dist-protected
Frequently Asked

React obfuscation questions, answered

Does obfuscation break code splitting and lazy loading?

No — as long as you protect the emitted chunks together in one pass so renamed identifiers stay consistent across files, and keep chunk file names unchanged so dynamic imports still resolve.

Should I obfuscate JSX or the built JavaScript?

Always the built JavaScript. Let Vite or Webpack compile JSX and TypeScript first, then obfuscate the generated files in dist. Protecting source files before the build breaks the toolchain and produces weaker results.

How much does it slow the app down?

Renaming and string protection are effectively free at runtime. Control-flow transforms and VM protection cost more — aim them at the functions worth protecting hardest and measure with your own bundle.

What about SSR and hydration?

Protect the client bundle. If server-rendered markup references global functions or component names, add those names to the exclusion list so hydration and inline handlers keep working.

Can I still debug production errors?

Yes. Keep the run's symbol mapping private and use symbolication to translate production stack traces back to readable names. Never deploy source maps.

Start Now

Paste one chunk and see the difference

Take a file from your last dist build, paste it into the online obfuscator, and compare readable input with protected output. When the result looks right, move to the npm CLI or desktop app for the whole folder.