JavaScript Checker — Example Report Run Your Own Audit →
📋 Example Report — Demo Data

JavaScript Checker Example: Errors, Speed and Security

This example shows the aiwebpageseo JavaScript Checker for a SaaS application. The report finds 14 issues including 3 console.log statements in production, a synchronous XHR that blocks the main thread for 200ms, an eval() call that is a security risk and 2 event listeners never removed.

4
Errors
10
Warnings
5
Info
38
Passed
Errors (4)
ERROR

eval() used — security risk — line 1,847

eval() executes arbitrary code and is an XSS attack vector if any user input reaches it. Replace with JSON.parse() for data parsing. Remove or refactor immediately.

ERROR

Synchronous XHR blocking main thread — line 234

new XMLHttpRequest() called with async:false blocks all JavaScript execution until the request completes. This freezes the UI for 200–800ms. Replace with fetch() or async XHR.

ERROR

document.write() in production code — line 567

document.write() after page load blocks the HTML parser and replaces the entire document on some browsers. It is deprecated, and Chrome blocks it for parser-inserted scripts on slow connections because it defeats the preload scanner. Remove and use DOM manipulation instead.

ERROR

Async function without try/catch — line 892

An async function runs without error handling. Unhandled rejections will be swallowed silently. Wrap the function body in try/catch and handle errors explicitly.

Warnings (10)
WARN

3 console.log() calls in production — lines 124, 445, 1,203

console.log() in production code exposes internal data in the browser console. Remove all debugging statements before deployment.

WARN

2 addEventListener without corresponding removeEventListener

Event listeners added in component setup code are never removed, causing memory leaks when components are unmounted. Always pair addEventListener with removeEventListener in cleanup.

WARN

setInterval without clearInterval on unmount

A polling interval is started on page load and never cleared. This runs indefinitely, wasting CPU cycles and potentially causing errors if the callback references unmounted DOM elements.

WARN

Large script file — 847KB unminified (line 1)

The main bundle is 847KB unminified. Minification would reduce this to approximately 280KB. Code splitting would allow non-critical code to load after initial render.

Why a JavaScript linter sits in an SEO tool

Nothing in the report above mentions search, and yet this is the audit with the largest potential SEO consequences on the whole platform. The reason is rendering.

Googlebot processes pages in two waves. It crawls the HTML immediately. It renders — actually executing your JavaScript — later, from a queue, whenever capacity allows. That delay can be minutes or considerably longer, and everything that only exists after JavaScript runs is invisible until the second wave arrives.

consequence

A JavaScript error is an indexing error

If a script throws before it finishes building the page, the render fails and Google indexes whatever was in the DOM at that moment — frequently an empty shell. The page looks perfect in your browser, because your browser recovered. Googlebot does not care that the rest of the page would have worked.

This is why the eval(), the document.write() and the unhandled rejection above are not merely code-quality issues. Any of them can silently prevent your content from being indexed at all, and no SEO report will tell you that, because to a crawler the page simply appears to be empty.

AI crawlers do not render at all

This is the part that has changed, and it changes the calculus.

Googlebot renders JavaScript, eventually. Most AI retrieval crawlers do not. They fetch the served HTML, parse it, and move on. There is no second wave, no render queue, no execution.

The consequence is stark: a client-side-rendered page that Google eventually indexes may be completely invisible to answer engines. Not ranked lower — absent. They saw an empty <div id="root"></div> and nothing else, and there is no queue that will fix it later.

The test takes ten seconds. curl your own URL and read the response, or view source rather than inspecting the DOM. Is your content there? If your headline, your body copy and your structured data only exist after hydration, you have written a page for browsers and not for machines. Server-side rendering, static generation or prerendering all solve it; nothing else does.

The 847KB bundle is the most expensive line in the report

It is filed under warnings, below four errors. It is almost certainly costing more than all of them combined, and here is the mechanism.

A JavaScript bundle is not like an image. An image is downloaded and displayed. A bundle is downloaded, parsed, compiled and executed — and every one of those stages happens on the main thread. The same single thread that has to respond when the user taps something.

StageCost
DownloadBandwidth. The part everyone optimises.
Parse & compileMain thread. Scales with bytes.
ExecuteMain thread. Frequently the largest cost of all.

This is why INP is so often bad on pages that look fast. The content painted quickly, so the user tapped a button — and the main thread was still chewing through 847KB of JavaScript, so nothing happened for half a second. A mid-range Android phone can take several times as long as your development machine for the same bundle.

Minification is the smallest available win. 847KB → 280KB reduces bytes on the wire; it does not reduce the amount of code that must be executed. Code splitting does, because code that is never loaded is never parsed, compiled or run. Ship the code the first screen needs, and load the rest on demand. That is the fix, and minification is a footnote to it.

The four errors — what is right, and one thing that is not

eval() — correct, and the fix given is the right one

It turns any string that reaches it into executable code, which makes it an XSS vector, and it defeats the engine's optimiser because the code cannot be analysed ahead of time. JSON.parse() for data is exactly right. The same applies to the Function() constructor and to passing strings to setTimeout — both are eval wearing a different hat.

Synchronous XHR — correct, and worse than it sounds

It halts everything until the network responds. Not "slow" — frozen. No rendering, no input, no scrolling. And the duration is entirely at the mercy of the network, so on a poor mobile connection the freeze is not 200ms, it is however long the request takes. Browsers now warn about it and it is on a path to removal.

document.write() — right conclusion, wrong reason

The report says it is "disallowed by the Content Security Policy on modern sites". That is not accurate — CSP has no directive governing document.write. The real reasons are better: called after the document has finished parsing, it wipes the entire page and starts a new one; and Chrome actively blocks it for parser-inserted scripts on slow connections, because it destroys the preload scanner's ability to work ahead. It is one of the few genuinely harmful APIs still in the language.

Async function without try/catch — flagged too strongly

Unhandled promise rejections are not silently swallowed in modern browsers: they log to the console and fire an unhandledrejection event you can listen for globally. And wrapping every async function body in try/catch is not automatically right — a function that catches an error it cannot meaningfully handle, and then continues, is worse than one that lets it propagate to a caller who can. The right rule is that errors must be handled somewhere, deliberately, by whoever can actually do something about them. Not everywhere, reflexively.

Memory leaks: why they are a real bug, not a tidiness issue

Two orphaned addEventListener calls and an uncleaned setInterval look like housekeeping. On a traditional website they nearly are — every navigation is a fresh page load, and the leak is wiped out before it matters.

On a single-page application there is no fresh page load. The tab stays open for the entire session, and a listener attached on every component mount and never removed accumulates: ten mounts, ten listeners; a hundred, a hundred. Each one holds a reference to a component that has been unmounted, which keeps it, and everything it referenced, alive in memory forever.

the symptom

"It gets slower the longer I use it"

This is the signature, and it is genuinely difficult to diagnose because it never reproduces in a short test. You reload, you click through the flow twice, everything is fast. The user who has had the tab open since this morning is on their four-hundredth mount, with four hundred listeners firing on every scroll event, and the interface has become unusable.

The uncleaned setInterval is worse still, because it does not merely consume memory — it actively runs, forever, on a timer, executing code against DOM elements that no longer exist. That is not a leak; it is a background process nobody knows about.

The rule is symmetry. Every addEventListener has a matching removeEventListener. Every setInterval has a clearInterval. Every observer is disconnected. If you set something up on mount, tear it down on unmount — and if you cannot see where the teardown happens, it does not.

Frequently asked questions

Why is console.log bad in production?

console.log statements left in production code expose internal data to anyone who opens browser developer tools, slow down execution slightly and indicate code quality issues. They should be removed or replaced with a logging library that can be disabled in production.

What is a synchronous XHR?

Synchronous XMLHttpRequest blocks the browser's main thread until the network request completes. During this time, the page is completely unresponsive — no user interactions, no rendering updates. It is deprecated in browsers and causes very poor INP scores. Replace with async fetch() or async/await.

Why is eval() dangerous?

eval() executes arbitrary JavaScript strings, making it a significant security risk — any injected string becomes executable code. It also prevents JavaScript engines from optimising the code, causing performance degradation. Modern JavaScript has no legitimate use case for eval().

Can a JavaScript error stop my page being indexed?

Yes, and this is the most consequential thing on this page. Googlebot crawls your HTML immediately but renders JavaScript later, from a queue. If a script throws before it finishes building the page, the render fails and Google indexes whatever was in the DOM at that moment — often an empty shell. The page looks fine in your browser because your browser recovered from the error. Googlebot does not care that the rest of the page would have worked.

Do AI crawlers run my JavaScript?

Mostly not. Googlebot renders eventually; most AI retrieval crawlers fetch the served HTML, parse it and move on — no second wave, no render queue, no execution. So a client-side-rendered page that Google eventually indexes may be completely absent from answer engines, because all they saw was an empty root div. Test it in ten seconds: curl your URL, or view source rather than inspecting the DOM, and check whether your headline and body copy are actually there. Server-side rendering, static generation or prerendering solve this; nothing else does.

Is minifying my 847KB bundle enough?

No — minification is the smallest available win. A bundle is not like an image: it is downloaded, then parsed, compiled and executed, and all three of those happen on the main thread, which is the same thread that must respond when a user taps something. Minifying reduces bytes on the wire but not the amount of code that must be executed. Code splitting does, because code that is never loaded is never parsed, compiled or run. Ship what the first screen needs and load the rest on demand.

Do memory leaks really matter on a website?

On a traditional multi-page site, barely — every navigation is a fresh page load that wipes the leak out. On a single-page application there is no fresh load: the tab stays open for the whole session, and a listener attached on every mount and never removed accumulates indefinitely, each one holding an unmounted component alive in memory. The signature is "it gets slower the longer I use it", and it is hard to diagnose because it never reproduces in a short test. An uncleaned setInterval is worse still, since it actively runs forever against DOM elements that no longer exist.

Related Demo Reports

Run JavaScript Checker on Your Own Site

Get your real audit report with specific issues, fixes and actionable improvements — free to start.

⚡ Run Free Audit View Plans