Google can run JavaScript, but it renders in a queue and other crawlers may not render at all. A page that puts its headline, text, links and metadata in the initial HTML works for every reader, and JavaScript can then enhance it. Check what the server actually sends before you trust what the browser shows.
Two readers, two behaviors
A browser shows you the page after JavaScript has run. A crawler may see something else. Google describes a two-step process: Googlebot fetches the URL, reads the HTML and its links, then queues pages that return a 200 status for rendering, where a headless Chromium runs the JavaScript. Google then indexes the rendered HTML and queues any new links it finds.[1] Rendering can wait in the queue, and Google notes that server-side or pre-rendering remains a great idea because it makes a site faster for users and crawlers, and not all bots can run JavaScript.[1]
Google’s documentation on dynamic rendering says the same about other engines: they may choose to ignore JavaScript and then cannot see content that JavaScript generates.[2] The AI systems are in that group more often than not. A December 2024 analysis of crawler traffic on the Vercel network found that OpenAI’s crawlers, Anthropic’s ClaudeBot and PerplexityBot did not execute JavaScript, while Googlebot and Apple’s crawler did.[3] Those are observations from one platform, taken at one time, and the vendors do not publish promises either way. The prudent reading is to avoid depending on it.
What Google does with your scripts
Most JavaScript problems are not about the framework. They come from a handful of habits, and Google’s guide names them. Links must be <a> elements with an href, and single-page apps should use the History API, not URL fragments, to move between views.[1] You may set the title, meta description and canonical with JavaScript, but the canonical should not differ from the one in the original HTML, and HTML is the better place for it.[1] Client-side routing makes meaningful status codes hard, so Google suggests either redirecting to a URL the server answers with a 404 or adding a noindex robots meta tag to error pages, to avoid soft 404s.[1]
| Pattern | Risk | Fix |
|---|---|---|
Navigation with onclick and no href | Google cannot extract the URLs, so pages are not discovered.[1] | Use real anchors with real URLs, then enhance them. |
Routes like /#/products | Fragments are generally not resolved as separate pages.[1] | Use the History API with clean paths. |
| Unknown routes render a friendly “not found” view | The server answers 200, so Google may see a soft 404.[1] | Return a real 404, or redirect to one, or add noindex. |
| Content fetched when the user clicks a tab or button | Google does not interact with the page, so that content may never load.[4] | Ship the content in the HTML and hide it with CSS if needed. |
| Lazy loading that waits for scroll events | Content below the fold may never load for a crawler.[5] | Use native lazy loading or IntersectionObserver so content loads when visible. |
| Serving a special version to bots | Dynamic rendering is a workaround and not a recommended solution.[2] | Prefer server-side rendering, static rendering or hydration.[2] |
Google’s guidance for mobile-first indexing adds a related rule. Google indexes the mobile version of a page, so the mobile page needs the same primary content, metadata and structured data as the desktop page, and should not hide primary content behind interaction.[4]
Inspect the public route directly
You can find most of these problems in five minutes without any tool beyond a terminal and a browser. The goal is to compare three versions of the page: what the server sends, what the browser builds and what Google reports.
# 1. What the server sends, before any JavaScript
curl -s https://example.com/services/sports-massage/ | grep -i -E "<h1|canonical|robots"
# 2. The response code for a URL that should not exist
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/no-such-page-xyz
If the first command prints nothing, the headline and metadata are created in the browser. In Chrome you can also disable JavaScript in DevTools and reload to see what remains. Then ask Google. The URL Inspection tool and the Rich Results Test render the page and show the loaded resources, the JavaScript console output and exceptions, and the rendered DOM, so you can check that the rendered HTML has all the content you expect.[6]
| Version | How to see it | Pass looks like |
|---|---|---|
| Initial HTML | curl, or View source | The h1, main text, internal links, title, canonical and robots meta are present. |
| Rendered page | Browser inspector, or JavaScript disabled to see the fallback | Nothing important appears only after a click or scroll. |
| Google’s version | URL Inspection, View crawled page | The rendered HTML matches what a visitor sees, with no blocked resources. |
Choose a rendering strategy on purpose
The framework matters less than how the first response is produced. Google recommends server-side rendering, static rendering or hydration over dynamic rendering.[2]
| Strategy | Initial HTML | Fits | Trade-off |
|---|---|---|---|
| Client-side rendering | An empty shell | Dashboards and tools behind a login | Public content depends on rendering and on every crawler running scripts. |
| Server-side rendering | Complete, built per request | Public pages with frequently changing data | Needs a server and caching. |
| Static generation or pre-rendering | Complete, built ahead of time | Marketing sites, guides, documentation | Needs a rebuild when content changes. |
| Hydration | Complete, then made interactive | Public pages with rich interaction | More JavaScript shipped to the browser. |
For a typical studio or service-business site, static generation or server rendering is the simplest answer. The content exists as HTML, and JavaScript adds animation, filters and forms. This is also what makes pages fast, which the page experience chapter covers.
Keep URLs stable, too. In the same Vercel analysis, about a third of ChatGPT’s and Claude’s crawler fetches returned a 404, against about 8% for Googlebot.[3] Vercel’s advice is to keep redirects in place, keep sitemaps current and use consistent URL patterns.[3]
Test after every release
Rendering problems usually appear after a deploy, when a new component, a lazy-load change or a router update silently moves content out of the initial HTML. A tiny script that runs after each release catches this early. This one checks the response code, the headline, the canonical and the robots tag, and confirms that an unknown URL returns 404.
// check-pages.mjs run with: node check-pages.mjs
const base = 'https://example.com';
const pages = ['/', '/services/sports-massage/', '/guides/hamstring-strain/'];
for (const path of pages) {
const res = await fetch(base + path);
const html = await res.text();
const text = html.replace(/<script[\s\S]*?<\/script>|<[^>]+>/g, ' ').replace(/\s+/g, ' ');
const checks = {
'status 200': res.status === 200,
'has h1': /<h1[\s>]/i.test(html),
'has canonical': /rel=["']canonical["']/i.test(html),
'not noindex': !/name=["']robots["'][^>]*noindex/i.test(html),
'has text in the HTML': text.length > 300,
};
for (const [name, ok] of Object.entries(checks)) console.log(ok ? 'ok ' : 'FAIL', path, name);
}
const missing = await fetch(base + '/no-such-page-xyz');
console.log(missing.status === 404 ? 'ok ' : 'FAIL', 'unknown URL status', missing.status);
It does not replace a real audit. It does turn “the page disappeared from the HTML” from a surprise in Search Console three weeks later into a failing check on release day. Add the pages that matter most, and extend the script when a new failure teaches you something.
Apply it to your website
- The initial HTML of every important page contains the h1, the main text, internal links, the title, the canonical and any robots meta.
- Navigation uses real anchor elements with real URLs, and routing uses the History API and not fragments.
- Unknown URLs return a real 404, or the app redirects to one, or it adds noindex to error views.
- Nothing important loads only after a click, a tab change or a scroll event.
- The mobile page has the same primary content, metadata and structured data as the desktop page.
- Public content uses server rendering, static rendering or hydration, and no bot-specific version is served.
- A post-deploy check confirms the initial HTML of key pages and the response code for an unknown URL.
Sources and further reading
Sources were consulted in October 2026 and the numbered notes above point here. Platform documentation changes often, so check the linked page before you act on a platform-specific detail. Worked scenarios are illustrative unless identified otherwise; methods and worksheets are Vantle’s editorial guidance.
- Google Search Central: JavaScript SEO basicsThe crawl and render queue, links, History API, status codes, canonicals and soft 404 handling.
- Google Search Central: Dynamic rendering as a workaroundWhy dynamic rendering is not recommended, and that other engines may ignore JavaScript.
- Vercel: The rise of the AI crawler (December 2024)Observed crawler volumes, JavaScript behavior and 404 rates on the Vercel network.
- Google Search Central: Mobile site and mobile-first indexing best practicesSame content and metadata on mobile and desktop, and no primary content behind interaction.
- Google Search Central: Fix lazy-loaded contentLoad content when it is visible in the viewport.
- Google Search Central: Fix Search-related JavaScript problemsTesting with the URL Inspection tool and the Rich Results Test.