Question
JavaScript SEO: Rendering Issues and Solutions
Short answer
Google can index content that JavaScript adds to a page, because it renders pages before indexing them. The problems come from three places: pages whose content or links appear only after a user action, signals in the original HTML that stop or contradict rendering (a noindex, an error status, a canonical that JavaScript later changes), and resources Google isn't allowed to fetch. Server-side rendering or static generation removes the dependency on rendering, and Google calls dynamic rendering "a workaround and not a recommended solution".
Sources
- 1developers.google.com
- 2developers.google.com
- 3developers.google.com
- 4developers.google.com
- 5developers.google.com
Ask next
- HTTP Status Codes: When to Use 301, 302, 404, and 410
- Canonical Tags: Solving Duplicate Content Issues
- Internal Linking Strategy: Link Equity Distribution and Crawl Optimization
- Pagination and Infinite Scroll: SEO Best Practices
- Image SEO: Complete Visual Optimization Guide
- Structured Data Fundamentals: JSON-LD, Microdata, and RDFa
How Google Processes JavaScript
Google’s JavaScript SEO basics describes three phases: crawling, rendering and indexing. The details that matter for diagnosis:
- Robots.txt comes first. If a URL is disallowed, Googlebot skips it, and “Google Search won’t render JavaScript from blocked files or on blocked pages.”
- Links in the raw HTML are found immediately. Googlebot parses the response for URLs “in the href attribute of HTML links” before rendering.
- Rendering is queued. “Googlebot queues all pages with a 200 HTTP status code for rendering, unless a robots meta tag or header tells Google not to index the page.” The page “may stay on this queue for a few seconds, but it can take longer than that.”
- Indexing uses the rendered page. After rendering, Googlebot parses the rendered HTML for links again, and “Google also uses the rendered HTML to index the page.”
- Error pages may not render. “If the HTTP status code is non-200 (for example, on error pages with 404 status code), rendering might be skipped.”
So JavaScript content is not indexed in a second-class way; it depends on rendering succeeding. Google still recommends server-side or pre-rendering “because it makes your website faster for users and crawlers, and not all bots can run JavaScript.” Google’s dynamic rendering page adds that “Other search engines may choose to ignore JavaScript and won’t see JavaScript-generated content”, which is a separate reason to put core content in the HTML.
Signals That Must Be Right Before Rendering
Some signals are read from the original HTML and the HTTP response, and JavaScript can’t reliably repair them afterwards. Google clarified three of them in December 2025, as recorded in its documentation changelog.
| Signal | Google's guidance | What it means in practice |
|---|---|---|
| <!–INLINECODE1–> | "When Google encounters the noindex tag, it may skip rendering and JavaScript execution, which means using JavaScript to change or remove the robots meta tag from noindex may not work as expected." | If a page should be indexed, don't ship <!–INLINECODE2–> in the original code and remove it with a script |
| Canonical | Don't "use JavaScript to change the canonical URL to something else than the URL you specified as the canonical URL in the original HTML" | Set it in the HTML, or only in JavaScript, never one value in each |
| Status code | Rendering "might be skipped" on non-200 pages | Return a real 404 for missing content instead of a 200 page that shows an error with JavaScript |
Titles and meta descriptions are more flexible: Google says “You can use JavaScript to set or change the meta description as well as the <title> element.”
For single-page apps that route on the client and can’t return a 404 status, Google gives two ways to avoid soft 404s: “Use a JavaScript redirect to a URL for which the server responds with a 404 HTTP status code”, or add a noindex robots meta tag to error pages with JavaScript. Status codes in general are covered in HTTP status codes, and canonical rules in canonical tags.
Rendering Options Compared
| Approach | What the first HTML response contains | Search risk | Fits |
|---|---|---|---|
| Client-side rendering | An app shell; content arrives after scripts run | Depends on rendering succeeding; other crawlers may see nothing | Logged-in apps, dashboards, tools that don't need to rank |
| Server-side rendering | Full content, rendered per request | Low; server cost and response time to manage | Content that changes often and needs to rank |
| Static generation | Full content, built ahead of time | Low; stale pages if rebuilds lag behind updates | Content that changes on publish, such as articles and documentation |
| Hydration | Server-rendered HTML, then scripts make it interactive | Low, if hydrated content matches the server HTML | Interactive pages that also need to rank |
| Dynamic rendering | A server-rendered copy for bots, client-rendered for users | Google calls it "a workaround and not a recommended solution" | A temporary bridge while moving to one of the options above |
Google’s dynamic rendering page now describes the technique in the past tense, as “a workaround and not a long-term solution”, and recommends “server-side rendering, static rendering, or hydration as a solution.” It also says dynamic rendering “creates additional complexities and resource requirements.” If you have it in place, plan its replacement; don’t build new sites on it.
Links and Routing
Client-side routing decides whether Google can find a JavaScript site’s pages at all. Google’s rules:
- “Google can only discover your links if they are
<a>HTML elements with an href attribute.” A navigation item that only has a click handler is not a link to Google. The full link rules are in Google’s crawlable links documentation and summarized in internal linking strategy. - “It’s fine to use JavaScript to inject links into the DOM, as long as such links follow the best practices for crawlable links.”
- For single-page apps, “use the History API to implement routing between different views”, and “don’t use fragments to load different page content.” Google says that with fragment routes such as
#/products, “Googlebot can’t reliably resolve the URLs”.
Lazy Loading and Infinite Scroll
Google’s lazy-loading guide says to load “all relevant content whenever it is visible in the viewport”, using browser built-in lazy loading for images and iframes, the IntersectionObserver API, or a library that loads data when it enters the viewport. The reason is stated plainly: these methods “don’t rely on user actions, such as scrolling or clicking, to load content, which is important as Google Search does not interact with your page.”
For infinite scroll the same guide asks for paginated loading: give each chunk “its own persistent, unique URL”, link the chunk URLs sequentially, and update the displayed URL with the History API when a new chunk becomes the primary visible element. The full pattern is in pagination and infinite scroll. Image-specific lazy loading is covered in image SEO.
Structured Data Generated With JavaScript
Generating JSON-LD with JavaScript is supported. Google’s JavaScript basics say you “can use JavaScript to generate the required JSON-LD and inject it into the page”, and its guide to generating structured data with JavaScript covers Google Tag Manager and custom scripts, tested with the Rich Results Test.
One exception is worth planning around. For Product markup, Google warns that “dynamically-generated markup can make Shopping crawls less frequent and less reliable, which can be an issue for fast-changing content like product availability and price.” Stores should put price and availability markup in the server-rendered HTML. Markup basics are in structured data fundamentals and product details in Product schema for e-commerce.
Resources, Caching and Web Components
- Don’t block what the page needs. Scripts, stylesheets and API endpoints that build the content must be crawlable; see robots.txt syntax and common mistakes.
- Fingerprint your files. Google says Googlebot “caches aggressively” and its rendering service “may ignore caching headers”, which can leave it with outdated JavaScript or CSS. Content fingerprints in filenames, such as
main.2bb85551.js, force a new file name for every change. - Check shadow DOM content. Google “flattens the shadow DOM and light DOM content”, but “can only see content that’s visible in the rendered HTML.” Use slots so light DOM content appears in the rendered output.
- Mind speed. Heavy scripts slow pages for users as well as crawlers; performance work is covered in Core Web Vitals.
Testing What Google Sees
- URL Inspection in Search Console. Run a live test and read the rendered HTML: is the main content there, are the links real
<a href>elements, are the canonical and robots meta tags what you expect? - Rich Results Test. It also shows rendered HTML, which helps on pages you don’t control in Search Console, and it validates JavaScript-generated structured data.
- Original HTML versus rendered DOM. View the page source to see what the server sends; inspect the DOM in browser developer tools to see what scripts produce. Anything important that exists only in the second depends on rendering.
- Error routes. Request a URL that shouldn’t exist and confirm it returns a 404 status or a
noindex, not a 200 with “not found” text. - Server logs. Confirm Googlebot requests the scripts and API endpoints the page needs; see log file analysis.
When content is missing from the rendered HTML, Google’s statement is unambiguous: “If the content isn’t visible in the rendered HTML, Google won’t be able to index it.”
Frameworks and Migrations
Framework defaults can change faster than SEO guides. One dated example: on February 14, 2025, the React team announced it was “deprecating Create React App for new apps,” and encouraged existing apps to migrate to a framework or a build tool such as Vite. Check your framework’s current documentation for its server rendering and static generation options rather than relying on what it did when your site was built.
When you migrate between rendering approaches, compare the rendered HTML of a sample of templates before and after: titles, canonicals, robots meta tags, structured data, internal links and status codes. A redesign that keeps every URL can still drop links or markup from the rendered output.
For each template, list what appears in the rendered DOM but not in the page source; everything on that list can be indexed by Google only if rendering succeeds, and other search engines may never see it.
Link profile
Links to 10 guides
- HTTP Status Codes: When to Use 301, 302, 404, and 410
- Canonical Tags: Solving Duplicate Content Issues
- Internal Linking Strategy: Link Equity Distribution and Crawl Optimization
- Pagination and Infinite Scroll: SEO Best Practices
- Image SEO: Complete Visual Optimization Guide
- Structured Data Fundamentals: JSON-LD, Microdata, and RDFa
- Product Schema and Rich Results for E-commerce
- Robots.txt: Syntax, Common Mistakes, and Advanced Usage
- Core Web Vitals: LCP, INP, and CLS Optimization
- Log File Analysis: Understanding Googlebot Behavior
Linked from 14 guides
- SEO for Startups: Building Organic Visibility from Zero
- Schema Markup Testing and Validation Tools
- SaaS SEO: Strategies for Software Companies
- Pagination and Infinite Scroll: SEO Best Practices
- Mobile-First Indexing: Technical Requirements and Checklist
- Log File Analysis: Understanding Googlebot Behavior
- Internal Linking Strategy: Link Equity Distribution and Crawl Optimization
- Image SEO: Complete Visual Optimization Guide
- HTTP Status Codes: When to Use 301, 302, 404, and 410
- How Google Crawls and Indexes Websites: The Technical Process Explained
- Generative Engine Optimization (GEO): Visibility in AI-Powered Search
- Crawl Analysis Tools: Identifying Technical Issues
- Core Web Vitals: LCP, INP, and CLS Optimization
- Canonical Tags: Solving Duplicate Content Issues
Cites 5 sources
- JavaScript SEO basics developers.google.com
- documentation changelog developers.google.com
- dynamic rendering page developers.google.com
- lazy-loading guide developers.google.com
- generating structured data with JavaScript developers.google.com