Unshipped features hide in their JS bundle
A five-step dig through a competitor's public frontend code, and how to tell a real leak from dead leftovers.
Shipped code arrives before shipped features. Here is a five-step dig through a competitor's public JavaScript, and how to separate signal from leftovers.
A product team builds a feature behind a flag, merges it, and ships the flag to production weeks before anyone is allowed to see the screen. The gate is on the server. The strings are in the browser.
That gap — between code being live and a feature being announced — is where this playbook operates. It is a public-asset dig only: everything below reads files a competitor's web server hands to any anonymous visitor. Nothing here involves signing in, defeating a paywall, or touching an API that expects credentials. If a step requires an account to work, you have left the playbook.
Budget forty-five minutes the first time on a given competitor, ten minutes on every repeat.
Step 1 — Collect the assets
Open the competitor's app marketing site and their web app's login page in a browser, and look at what the page pulls in.
- In DevTools, filter the Network tab to JS and CSS. Save the main bundle and any lazily-loaded chunks that the login route fetches on its own.
- Grab
/_next/static/,/assets/, or whatever the framework's static directory is. Many builds expose a manifest listing every chunk by name. - Pull the source map if one is served (
.js.mapnext to the bundle). Teams disable these in production inconsistently, and a map turns minified soup back into readable module and folder names. - Note the build hash in the filenames. That hash is your version marker for Step 5.
Keep the files. The whole point is comparing this copy to the next one.
Step 2 — Search for names, not logic
You are not reading the code. You are reading the vocabulary the code carries.
- Feature flag keys. Search for
flag,enable,isEnabled,experiment,beta,gate. Most flag SDKs ship the key names to the client even when the value resolves to false. - Route strings. Search for path fragments —
/settings/,/admin/,/billing/. A route that exists in the router but appears in no navigation menu is a screen someone built. - i18n files. Localization bundles are the single richest target. Copywriters write the strings for a feature before launch, and translation files are almost always shipped whole. A label for a plan tier you have never seen on the pricing page is a loud signal.
- API field names. GraphQL fragments and TypeScript response types leak the shape of the backend. New fields on an existing object tell you what they started collecting.
- Third-party keys. Analytics, support widget, and payment provider identifiers tell you what the stack is made of, which is a slower-moving but more durable signal than any feature.
Step 3 — Cross-check against surfaces that can confirm
A string on its own proves someone typed it. Confirmation comes from a second, independent surface saying the same thing.
- Check the sitemap. An unlinked page with a matching slug turns a guess into a fact — the same logic covered in unannounced pages in a competitor's sitemap.
- Check the public API docs and changelog. New endpoints or objects that match your leaked field names are the strongest corroboration available, which is why the changelog leaks a roadmap.
- Check job postings. A flag named for a compliance certification lands differently when they are also hiring a security engineer.
- Check the help center. Support articles get written and published early more often than product teams intend.
Two surfaces agreeing is a finding. One surface alone is a note.
Step 4 — Rule out the leftovers
This is the step people skip, and it is the reason bundle digs have a bad reputation.
- Dead flags. A flag that shipped, rolled out to everyone, and never got cleaned up looks exactly like a flag for an unreleased feature. If the feature is already in the product, the flag is garbage.
- Vendor code. Half the strings in a bundle belong to a dependency, not to the company.
enableExperimentalModeinside a charting library says nothing about the roadmap. - Abandoned work. Code for a feature that got killed stays in the repo for months. This is the same failure the rejection pile teaches: evidence that something was built is not evidence it is coming.
- Enterprise-only screens. A route you cannot reach may simply be gated to a plan you are not on. Already shipped, just not to you.
Downgrade anything you cannot separate from these four. Most of what you find will be leftovers, and a program that reports leftovers as intel stops being trusted — the broader trap in signals worth ignoring.
Step 5 — Diff on a schedule, not on a hunch
One bundle is a snapshot. Two bundles are intelligence.
- Re-pull when the build hash changes, and diff the string tables — not the minified code, which changes on every build for no reason.
- Track additions to i18n files above everything else. New user-facing copy is the closest thing to a pre-announcement you will get.
- Log the date you first saw a string. When the feature launches publicly, you now know their build-to-launch lag, which makes the next leak forecastable.
The honest constraint: nobody does this weekly by hand across five competitors. It decays into a thing you did once. That is the argument for letting continuous monitoring set the schedule — Seeto watches a competitor's public surfaces and reports what changed as discrete, dated events, so a pricing edit or a new docs page arrives as an alert rather than a discovery. Seeto will not read a minified bundle for you and will not tell you what a flag means. What it does is tell you when something moved, which is the trigger for opening DevTools at all. The dig stops being a chore on the calendar and becomes a response to a signal.
That is the whole shift worth making here. Stop scheduling the excavation. Schedule the watch, and dig when the watch says something is different.