İçeriğe geç
wedevit

October 30, 2026 · 9 min read · software

İlhan Buğra Aslan

The page looks fine in the browser but the HTML is empty: JavaScript rendering and search visibility


If your page renders perfectly in a browser but "view source" shows an empty shell, the content is not coming from your server. Google will usually still index it, because Googlebot runs JavaScript in a headless browser. The crawlers behind ChatGPT and Claude will not: they read the raw HTML and nothing else. So the question is not whether single-page apps are acceptable. The question is which pages need their content in the first HTML response, and that answer is per page type, not per site.

Start with a diagnosis, not an opinion

The Elements tab in DevTools shows you the rendered DOM, meaning the page after JavaScript has run. Most bots never get that far. Test the raw response instead:

curl -sL https://yoursite.com/products/abc | grep -i "<h1"

If the product name, price, description and <title> are missing from that output, the server is not sending them. Two more checks worth five minutes: load the page with JavaScript disabled, and run the live test in Search Console's URL Inspection tool, which shows the HTML Google ended up with plus any resources that failed to load during rendering.

A large "Crawled, currently not indexed" bucket in Search Console does not prove a rendering problem on its own, but on a JavaScript-heavy site it is the first place to look.

Google renders, but rendering is not free

Google's own documentation splits the work into three stages: crawling, rendering, indexing. A page that returns 200 goes into a render queue, headless Chromium executes the JavaScript, and the resulting HTML is parsed again for content and links. The same page notes that you cannot tell from the outside which queue a URL is sitting in.

That queue has three practical consequences. Delay: server-rendered pages can be indexed on the first pass, while JavaScript-generated content waits for a second one. Fragility: if an API call times out during rendering, Google indexes the half-empty page it got. And robots.txt: Google's JavaScript SEO basics guide states plainly that Google Search will not render JavaScript from blocked files or on blocked pages. One careless Disallow: /static/ line can empty out an entire template.

The same document flags a detail that catches teams out regularly: when Google encounters a noindex tag it may skip rendering and JavaScript execution entirely. Shipping noindex in the initial HTML and removing it client-side does not work.

The AI side behaves differently

This is where assumptions fail. Vercel's analysis of AI crawler traffic, published on 17 December 2024 from its own network and nextjs.org logs, found that none of the major AI crawlers render JavaScript. The ChatGPT and Claude bots do download JavaScript files, 11.50% and 23.84% of their requests respectively, and never execute a line of them. The volumes are not marginal: in the month measured, GPTBot made 569 million requests, Claude 370 million and PerplexityBot 24.4 million, against 4.5 billion for Googlebot.

Gemini is the exception, since it runs on Googlebot's infrastructure and inherits its rendering.

The consequence is concrete. A page that ranks well on Google can be completely absent when someone asks ChatGPT the same question. If the text is not in the initial HTML, the model never reads it. Everything else you do for visibility in AI search depends on clearing this bar first.

Link previews do not wait for JavaScript either

When a link is shared in Slack, WhatsApp or LinkedIn, the bot that builds the preview card fetches the raw HTML. If you inject Open Graph tags at runtime with something like React Helmet, the card comes back blank or shows the same generic title on every URL. Title, description and image tags belong in the HTML your server sends. The test is the same one-liner: grep the curl output for og:title.

Four rendering options and what each one costs

Client-side rendering (CSR). The server sends a shell, the browser builds the page. Cheapest to run, simplest to deploy, and invisible in the first response.

Static generation (SSG). Pages become HTML at build time and are served from a CDN. Fastest and cheapest path. The limit is rebuild time: with tens of thousands of catalogue pages, a full rebuild on every content change stops being practical.

Server-side rendering (SSR). HTML is produced per request. Always current, always complete in the first response, and it costs you a running server, database load and a deliberate caching strategy. Uncached SSR is one of the most common causes of a bad TTFB.

Incremental regeneration. Pages are generated statically and refreshed in the background on a schedule or a trigger. For content that is read often and changes rarely, catalogues and blogs in particular, this usually hits the best balance.

These are not four competing architectures. Modern frameworks let you pick per route, and a healthy setup is almost always a mix.

Which pages deserve which

One distinction does most of the work: if a page is public and you want it found, its content belongs in the first HTML response. If it sits behind a login, client-side rendering is fine.

  • Marketing pages, blog, documentation: static generation. Low change frequency, high search value.
  • Product and category pages: static or incrementally regenerated. Price and stock can refresh on the client, but the product name and description should be in the HTML.
  • Internal search results and filtered lists: render on the client and keep them out of the index. Useful for users, mostly noise for search engines.
  • Dashboards, account screens, admin tools: CSR, without hesitation. Nobody is searching for these.

Skipping this triage and declaring "we will server-render everything" buys complexity and server cost with no return on the pages that never needed it.

Serving bots separate HTML is not the answer

An old technique is still circulating: detect the bot user agent, hand it prerendered HTML, give users the SPA. Google gutted its own page on this and what remains is unambiguous. Dynamic rendering was a workaround and not a long-term solution, with server-side rendering, static rendering and hydration listed as the alternatives.

The reasons are practical. Two outputs mean two things to maintain, and they drift apart. Once the HTML you serve bots differs materially from what users get, you are flirting with cloaking. And the prerender service becomes a failure point of its own: when it goes down, bots see error pages and nobody notices for weeks.

As a bridge during a migration it is defensible. As a destination it is not.

Four mistakes that keep repeating

Fragment URLs. Addresses shaped like site.com/#/products/abc are not reliably crawled. Google recommends the History API, which in practice means every piece of content lives at a real URL.

Links that are not links. A div or button wired to onClick is not a link to a crawler. Google finds links in <a> elements that carry an href. Using a router does not change that; the rendered output still needs a real href.

Soft 404s. Client-side routing cannot return a status code, so a missing product renders "Not found" with a 200. Google's guidance is to either redirect to a server URL that returns 404 or inject a noindex meta tag on the error view. Otherwise you accumulate hundreds of indexed empty pages.

Content hidden behind interaction. Text that only loads on a tab click, an accordion toggle or an infinite scroll event is not in the rendered HTML, and what is not in the rendered HTML does not get indexed.

The bill for SSR: speed and resilience

Moving rendering to the server does not remove performance problems, it relocates them. Budget for three things from the start.

TTFB. A page that hits the database on every request is slower than a file on a CDN. Without cache headers and per-route cache lifetimes, SSR does not scale.

Hydration. Building the HTML on the server and then reviving the same component tree in the browser occupies the main thread, and that shows up directly in INP, the metric that measures how long the interface takes to respond to a click. This is the number you watch in Core Web Vitals and field data. Every component you keep on the server is JavaScript you do not ship, so make each one justify why it needs to be interactive.

Resilience. In a CSR app, one slow API delays one widget. In SSR, the same slow API holds up the whole page. Stream the non-critical parts or leave them to the client, and confirm the behaviour under load testing rather than in theory.

Getting out of a client-rendered app

No rewrite required. Go route by route.

Start from data, not instinct: which pages bring organic traffic, and which ones should but do not. Pull bot user agents out of your server logs (Googlebot, GPTBot, ClaudeBot, PerplexityBot) and see which paths they fetch. That list usually collapses into five or ten templates.

Then convert the most valuable template first. Fixing one product page template repairs thousands of URLs in a single change.

Verify after each step. Does the curl output contain the content, does URL Inspection agree, is the structured data still valid? If the URL structure changes too, follow the redirect plan in protecting SEO through a redesign or migration.

Leave the rest alone. Converting the admin panel helps no one.

Make it a test, not a memory

Rendering regressions are quiet. A library upgrade, a component that slips back to the client, a misplaced useEffect, and the page empties out again. You find out weeks later when traffic drops.

Add a few lines to your deployment pipeline instead. After every release, fetch three to five critical URLs from the server and assert that the HTML contains an <h1>, a <title>, an og:title and a page-specific string. Fail the deploy if it does not. Run the same check in both environments to catch differences between staging and production, since prerender and cache behaviour drifts between them without announcing itself.

For a new build, treat the rendering model as part of the technology stack decision rather than something to bolt on once the SEO report arrives.

Three things to do this week

One: fetch your five highest-traffic pages with curl and check whether the content is in the HTML. Five minutes, and it settles half the argument.

Two: read your robots.txt for lines that block JavaScript, CSS or data endpoints. If one is there, Google may not be rendering those pages at all.

Three: extract which URLs GPTBot and ClaudeBot fetched in the last 30 days from your server logs. If the raw HTML of those URLs is empty, you are not visible on the AI side.

At Wedevit we map how an existing application renders page type by page type, prioritise which templates belong on the server, plan the migration route by route, and add the pipeline checks that catch rendering regressions before traffic does. A concrete first step: check today whether the raw HTML of your best-selling product page actually contains the price.


Need help with this topic?

get in touch →← all posts