Modern JavaScript frameworks make it possible to build very rich interfaces, but they move a significant share of the work from the server to the browser. For a user with a recent device the difference is almost invisible; for a search engine it can be huge. The point is not whether engines "can read JavaScript", since the main ones execute a good deal of it, but how easy and reliable you make their job.
Rendering strategies
| Strategy | Where the HTML is built | What the crawler receives on the first request |
|---|---|---|
| Client-side rendering (CSR) | in the browser | an almost empty shell plus JavaScript files |
| Server-side rendering (SSR) | on the server, on every request | the complete page |
| Static generation (SSG) | at build time | the complete page, ready-made |
| Incremental regeneration | static, refreshed periodically | the complete page |
Client-side rendering
The server sends minimal HTML, often with a single <div id="app">, and the content is built by JavaScript. To be indexed, the engine has to download the scripts, run them and wait for data from the APIs. It's possible, but it adds steps that can fail or be postponed, and not all crawlers, including those that generate previews for social networks and messaging apps, execute JavaScript.
Server-side rendering
The server produces the complete HTML and the browser, after displaying it, "hydrates" the page to make it interactive. The crawler immediately sees the title, text, links and structured data. The cost is a server that works on every request and more complex caching.
Static generation
Pages are created once during the build and served as files. It is the fastest and cheapest solution for content that changes little: blogs, documentation, corporate sites. For large catalogues or frequently changing data it is combined with periodic regeneration.
In my work, for content sites the default choice is static generation; I reserve client-side rendering for private areas and application parts that shouldn't appear in results.
Dynamic rendering
Some sites serve a pre-rendered version to crawlers and the JavaScript version to users. Google has described this as a workaround, not a recommended long-term practice. It introduces two versions to maintain and the risk that they diverge. Better to aim for SSR or static generation.
Mistakes that make content invisible
Links that aren't links
<span onclick="router.go('/products')">Products</span>
This element is clickable for the user, but it contains no URL to follow. Crawlers discover pages through <a href="/products">. The framework's router can intercept the click, but the href has to be there.
Hash URLs
Older single-page applications used paths like /#/products. The part after # is generally not treated as a distinct URL. Use real paths with the History API.
Wrong status codes
In a client-side application, a nonexistent page often returns 200 and shows a "not found" message generated by JavaScript. To the engine, that's a soft 404. With SSR or static generation the server can return a real 404; with CSR alone, one solution is to redirect to a URL that returns 404 or to add a meta robots noindex to the error view.
Content loaded only after interaction
Text that appears only on click, scroll or hover may not be seen. Important content must be in the DOM once the initial load completes.
Blocked resources
If robots.txt blocks the script folders or the APIs that provide the data, the engine cannot rebuild the page.
Metadata set late
Title, meta description, canonical and meta robots changed via JavaScript after load are read less reliably. In particular, a noindex in the initial HTML can mean the page is never even rendered, even if JavaScript later removes it.
How to check
- Disable JavaScript in the browser and open the page: what you see is what anyone who doesn't run scripts receives.
- View the source (not the inspector): it shows the initial HTML before any execution.
- Use the URL inspection tool in webmaster tools: it shows the rendered HTML and the resources that couldn't be loaded.
- Run a crawler in JavaScript mode and compare the result with a crawl without rendering: the differences show what depends on scripts.
In short
- Client-side rendering adds steps that can fail or be postponed.
- SSR and static generation hand the crawler the complete page straight away.
- Links must be real
<a href>elements with real URLs, without a hash. - Nonexistent pages and metadata must already be correct in the initial HTML.
- Check by comparing the source, the no-JavaScript version and the rendered HTML.