JavaScript SEO and Rendering
JavaScript SEO and rendering is the question of whether Google sees page content that appears only after scripts run. The documentation's short answer: yes, Google runs JavaScript with an evergreen version of Chromium, but the process has stages and conditions that determine the result. It is often written that Google "comes back in a few days" and that a particular framework is needed, but such claims are not in the documentation. Here is only what it describes, and concrete checks.
The basis is Google's guide to the JavaScript SEO basics and the help page on finding and fixing problems.
Three phases: crawling, rendering, indexing
Google processes JavaScript web apps in three main phases, and Googlebot queues pages for both crawling and rendering:
- Crawling. Googlebot takes a URL from the queue and first checks robots.txt. If the URL is disallowed, no request is made, and Google won't render JavaScript from blocked files or on blocked pages. Then it looks for links in the href attributes of the resulting HTML;
- Rendering. All pages with a 200 status code go to the rendering queue, whether or not they have JavaScript, except those where a robots meta tag or header says not to index. The page may wait a few seconds, but it can take longer. When resources allow, a headless browser executes the scripts. For non-200 responses, such as error pages, rendering might be skipped;
- Indexing. Google uses the rendered HTML to index the page and extracts links from it again.
The documentation adds: server-side or pre-rendering is still a great idea because it makes the site faster for users and crawlers, and not all bots can run JavaScript.
Ways to deliver content
Google names three recommended solutions: server-side rendering, static rendering and hydration. In addition, dynamic rendering was a workaround and not a long-term solution, and the three solutions above are recommended instead. Such methods are often ranked: SSG and ISR "excellent", client-side "risky". Those ratings are not in the documentation, and client-side rendering is not forbidden as such: Google sees that content together with the rest of the HTML. What matters is that the needed content is available in the final page and follows the rules below.
Rules for JavaScript sites
- Links. Google finds links only in
aelements with an href attribute. It is fine to inject links into the DOM with JavaScript as long as they follow the crawlable-links best practices. More in the lesson on search and navigation and on the page about links; - History API instead of fragments. In single-page apps route with the History API: Googlebot can't reliably resolve addresses with a fragment after the hash;
- Title and description. They can be set and changed with script; they should be unique and descriptive;
- Canonical. Best set in HTML. Script is possible, but you shouldn't use JavaScript to change the canonical URL to something else than the one in the original HTML; if you can't set it in the HTML, leave it out there and set it only with script, and make sure there is just one element. More in the lesson on canonical;
- Status codes. Use meaningful codes: 404 for a page not found, 401 for pages behind a login, redirects for moved pages;
- Structured data. JSON-LD can be generated and injected with script; always test the result. See the lesson on structured data;
- Caching. Google caches aggressively and may ignore caching headers, so it can use outdated JavaScript and CSS. The fix is to put a fingerprint of the content in the file name, such as
main.2bb85551.js.
Robots meta traps
Script can add a robots meta tag or change it. But when Google encounters noindex, it may skip rendering and JavaScript execution, which means using JavaScript to change or remove the noindex may not work as expected. If you want the page indexed, do not put noindex in the original code. This same rule explains another trap covered in the lesson on robots.txt and meta robots.
A soft 404 in a single-page app
With client-side routing a server status code is often impossible. So error pages do not become soft 404s, Google suggests two ways: redirect with JavaScript to a URL for which the server responds with a 404, or add noindex to the error page. On codes and redirects see the lesson on redirects.
How to check
- In Search Console open URL inspection or the Rich Results Test: you can see loaded resources, JavaScript console output, exceptions and the rendered DOM.
- Compare that with the HTML the server returns: our page analyzer and crawler read exactly the server response and do not execute scripts. If important text, links or the canonical exist only in the rendered view, that is a dependency on rendering.
- Check that menus and links are a elements with an href, not event handlers.
- Make sure scripts, CSS and data are not blocked in robots.txt.
- Check the status codes of nonexistent addresses.
- For a bulk check of indexing statuses, the URL audit. Speed and stability are covered by Core Web Vitals.
What is not in the documentation
- A "few days" period for rendering: it says only that the queue can take longer than a few seconds;
- A ranking of SSR, SSG and ISR with "best" and "good compromise" ratings;
- A recommendation of particular frameworks;
- A claim that client-side rendering as such blocks indexing.
Checklist
- The needed content and links are in the rendered HTML.
- Links are a elements with an href; routing uses the History API.
- There is one canonical and it matches the HTML.
- There is no noindex in the source of pages that should be indexed.
- Nonexistent addresses give 404 or noindex.
- Resources are open to Googlebot; files carry a fingerprint in the name.
Mistakes
- Thinking Google does not execute JavaScript.
- Assuming client-side rendering is safe by itself without checking the result.
- Putting noindex in the HTML and hoping to remove it with script.
- Building navigation on fragments and event handlers.
- Blocking scripts in robots.txt.
What comes next
Next is the module on advanced technical optimization: structured data, Core Web Vitals and site migrations.