JavaScript Errors Only in Production: How to Detect and Diagnose Them
A clean local run does not recreate a production browser. Preserve the failing release, page state and resource evidence before a refresh hides the problem.
Preserve the production conditions before changing code
A JavaScript failure that disappears locally is often a difference in inputs or delivery rather than a mysterious browser problem. Production may use a different build, cached chunks, CSP, consent state, API response or third-party script. Write down those conditions before a hard refresh removes the evidence.
Start with the exact route, release identifier, browser version and time. Note whether the tab was open before the deployment, whether the visitor was signed in and whether optional cookies had been accepted. Reproducing a fresh logged-out tab is only one test case.
A local fixture demonstrated the basic detection path: an HTML document returned 200, but its JavaScript referenced a missing configuration object and raised an uncaught exception. A listener attached before navigation captured the exception. The corrected page reached its explicit ready state without a runtime error. The fixture did not simulate every production-only cause.
Collect more than one kind of browser failure
An uncaught exception, a console error and a failed HTTP response are different observations. Collect them separately so the evidence does not lose its meaning.
In a Node script with Playwright installed, this bounded diagnostic visit records three signals without printing URLs, cookies or exception text:
import { chromium } from 'playwright';
const browser = await chromium.launch();
try {
const page = await browser.newPage();
const counts = { exceptions: 0, consoleErrors: 0, httpErrors: 0 };
page.on('pageerror', () => counts.exceptions++);
page.on('console', message => {
if (message.type() === 'error') counts.consoleErrors++;
});
page.on('response', response => {
if (response.status() >= 400) counts.httpErrors++;
});
const response = await page.goto(process.env.CHECK_URL, {
waitUntil: 'load',
timeout: 15000,
});
console.log({ documentStatus: response?.status(), ...counts });
} finally {
await browser.close();
}
Set CHECK_URL to a site you own or are authorized to inspect. This example stops at the load event. It does not wait for a later interaction, background refresh or consent-dependent script. Add a specific readiness assertion and a deliberate interaction for the flow you need to cover.
Playwright documents pageerror, console and response events (opens in a new tab). Attach listeners before navigating; otherwise early startup failures may occur before collection begins. Do not rely solely on requestfailed for missing assets: an HTTP error has a response, and browser-side blocking may produce additional events. Recording response status preserves the distinction.
Compare the deployed build with the failing tab
Before trying to reproduce the source code, confirm which code the browser executed. Check the JavaScript filename in Network, the release marker if your app provides one and the document's cache headers. An old tab can keep an old application instance running after new visitors receive the replacement.
Rebuild using the production command and configuration in a controlled environment. Development servers may change module loading, error presentation and optimization. Keep client configuration separate from secrets: values included in a browser bundle are visible to the visitor. Vite explains this boundary in its environment variable documentation (opens in a new tab).
If the stack points at a minified file, use source maps associated with that exact build. A map from another release can send the investigation to the wrong line. Decide whether maps should be public or uploaded privately to an error-reporting service; do not publish sensitive source material by accident.
Check policy and consent differences
A script allowed locally can be blocked by a production CSP. Identify the effective directive in the browser's message, the blocked resource and whether the policy is enforced or report-only. Adding the script's host to an unrelated directive will not help.
Consent creates another timing difference. A page may initialize successfully before analytics is allowed, then fail after acceptance when a third-party dependency starts. Test both fresh and stored consent states. Do not grant optional consent silently in a synthetic test and then describe it as the default visitor experience.
Also compare CDN transformations, browser extensions, locale and API data. Reproduce one difference at a time. Disabling all production protections at once can make the symptom disappear without explaining which condition mattered.
Distinguish an empty result from an incomplete inspection
A monitoring browser can encounter a challenge, a login screen or a different response from the one a user sees. A visit that never reached the application is inconclusive even if it produced no JavaScript exceptions.
Keep the document status and final page identity with diagnostics. When available, record whether a challenge was detected and how long observation continued. If an expected third-party script is absent, check whether it was delivered at all before concluding that it loaded cleanly.
DeployWatchers' JavaScript check inspects browser diagnostics for the monitored page. It complements production error reporting, which can observe real sessions and routes that a scheduled visit never reaches. Neither a single synthetic browser nor a single customer's report represents every account, device or interaction.
Make the fix close the original reproduction
After correcting the cause, repeat the failing route under the recorded conditions. Confirm both the absence of the original error and the presence of the expected behavior: a menu opens, a result renders or the form becomes usable.
For an older-tab defect, keep a tab open through the next controlled deployment and exercise the action that previously failed. For a consent defect, repeat the acceptance sequence. For a CSP defect, leave the intended protection active. A clean console is valuable, but a working feature is the outcome the customer needs.
Catch problems after your next deployment
Monitor availability, browser JavaScript errors and visual changes with DeployWatchers.