Guides · 4 min read · Updated 2026-09-08

JavaScript SEO: what search engines see before and after rendering, and how to check

If your titles, links or content only exist after JavaScript runs, some search engines never see them and Google sees them late. How rendering works, what breaks, and how to diff raw versus rendered HTML across a whole site.

Search engines read your pages twice. First they fetch the HTML the server sends and index what’s in it. Later, when there’s capacity, Google renders the page in a headless browser and indexes what JavaScript added. Bing does some rendering; most other search engines and most AI crawlers do none. Anything that only exists after JavaScript runs is therefore invisible to part of the audience and late for the rest. For a marketing site built on a modern framework, “part” can be everything: the title, the meta description, the links to every other page, and the text itself.

What rendering actually means

A raw fetch returns the HTML from the server. A rendered fetch loads that HTML in a browser, runs the scripts, waits for the network to settle, and takes the resulting DOM. For a server-rendered page the two are the same, give or take a widget. For a client-rendered single-page app, the raw HTML is a <div id="root"></div> and a script tag, and everything else appears only after rendering.

Google’s rendering happens in a separate queue, historically hours to days after the first crawl, with a limited budget per site and a timeout on scripts. Content that renders slowly, depends on user interaction, or requires a request that fails for a bot is never indexed.

What breaks, in practice

Titles and meta descriptions set by JavaScript. The framework’s router updates document.title after the route loads. The raw HTML has the default title from the shell, so every page is indexed with the same title until rendering catches up, if it does.

Canonicals and robots directives injected client-side. A noindex added by script may be honoured or not depending on timing. A canonical added after render is a weak signal.

Links that aren’t links. <div onClick={navigate}> is a button to a crawler. Search engines follow <a href>; they do not click. A product grid built on click handlers has no crawlable path to the products.

Infinite scroll without paginated URLs. Content that loads on scroll is never loaded by a crawler that doesn’t scroll.

Content behind interaction. Tabs, accordions and “load more” buttons that fetch on click. If the content isn’t in the DOM after initial render, it’s not indexed.

JavaScript redirects. window.location = '/new/' is invisible to a raw fetch, which sees a 200 with a nearly empty page.

Blocked resources. If robots.txt blocks the JavaScript or API endpoints the page needs, rendering produces an empty page.

Errors that only happen for bots. A script that throws when a third-party isn’t available, or when localStorage is missing, can blank the page for the renderer while working for users.

The diagnosis: crawl twice and diff

The only reliable way to know what a search engine sees is to fetch each page both ways and compare. Doing this by hand with a browser’s “view source” versus “inspect” works for one page; for a site you need a crawler that renders.

Truelint crawls with your installed Chrome when rendering is enabled and stores, per page, the raw and the rendered version of the things that matter: title, meta description, canonical, robots directives, H1, word count, and the number of internal links. The JavaScript tab shows the differences: pages whose title changed after render, pages that gained most of their links after render, pages whose word count went from near zero to hundreds, and pages that redirected via JavaScript. It also records uncaught script errors during rendering, which are the “works for users, blank for bots” cases.

A site where nothing differs is fine. A site where everything differs needs server-side rendering or pre-rendering before it needs anything else.

The fixes

Server-side rendering or static generation for indexable pages. Every major framework supports it: Next.js, Nuxt, SvelteKit, Astro, Remix. The page arrives complete; JavaScript enhances it. This is the real fix and everything else is a workaround.

Put the head in the server response. Title, description, canonical, robots and hreflang must be in the raw HTML. Even on a client-rendered app, these can usually be templated per route on the server.

Real links. <a href="/products/123"> everywhere navigation happens. Attach the client-side router to the click; keep the href.

Paginated URLs for lists. Infinite scroll for users, ?page=2 links for crawlers, loading the same data.

Content in the initial DOM. Tabs and accordions should render their content hidden with CSS, not fetch it on click.

Server redirects, not script redirects. 301 at the server.

Don’t block scripts and APIs in robots.txt that rendering needs.

Test what the renderer sees, not what your browser sees with your session and your extensions.

When client-side rendering is fine

Logged-in dashboards, checkout flows, anything behind authentication, tools that aren’t meant to rank. Search engines don’t need to render those, and the effort of SSR buys nothing. The question for each template is: should this rank? If yes, it renders on the server.

Doing it in Truelint

Enable rendering in the crawl configuration (it uses the Chrome already on your machine). After the crawl, the JavaScript tab’s filters are the failure modes above; the Issues pane groups them with fix guidance. Compare the raw and rendered word counts across the whole site to find the templates that depend on JavaScript, and fix those templates first.