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.

Initial HTML <body><div id="root"></div><script src="app.js"> No headline, text,price or links JS runs After JavaScript £55Book Every crawlersees the raw HTML Browsers and Googlebotafter rendering
What a single-page app can look like from two sides. If the useful content only exists in the second card, any reader that stops at the first card gets a page with nothing 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]

Common JavaScript patterns and what to do about them
PatternRiskFix
Navigation with onclick and no hrefGoogle cannot extract the URLs, so pages are not discovered.[1]Use real anchors with real URLs, then enhance them.
Routes like /#/productsFragments are generally not resolved as separate pages.[1]Use the History API with clean paths.
Unknown routes render a friendly “not found” viewThe 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 buttonGoogle 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 eventsContent 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 botsDynamic 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]

A three-version comparison
VersionHow to see itPass looks like
Initial HTMLcurl, or View sourceThe h1, main text, internal links, title, canonical and robots meta are present.
Rendered pageBrowser inspector, or JavaScript disabled to see the fallbackNothing important appears only after a click or scroll.
Google’s versionURL Inspection, View crawled pageThe 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]

Rendering strategies compared
StrategyInitial HTMLFitsTrade-off
Client-side renderingAn empty shellDashboards and tools behind a loginPublic content depends on rendering and on every crawler running scripts.
Server-side renderingComplete, built per requestPublic pages with frequently changing dataNeeds a server and caching.
Static generation or pre-renderingComplete, built ahead of timeMarketing sites, guides, documentationNeeds a rebuild when content changes.
HydrationComplete, then made interactivePublic pages with rich interactionMore 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]

Share of fetches that returned 404, on the Vercel network, December 2024. One platform’s traffic at one moment, and not a measure of the whole web.[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.

  1. Google Search Central: JavaScript SEO basicsThe crawl and render queue, links, History API, status codes, canonicals and soft 404 handling.
  2. Google Search Central: Dynamic rendering as a workaroundWhy dynamic rendering is not recommended, and that other engines may ignore JavaScript.
  3. Vercel: The rise of the AI crawler (December 2024)Observed crawler volumes, JavaScript behavior and 404 rates on the Vercel network.
  4. Google Search Central: Mobile site and mobile-first indexing best practicesSame content and metadata on mobile and desktop, and no primary content behind interaction.
  5. Google Search Central: Fix lazy-loaded contentLoad content when it is visible in the viewport.
  6. Google Search Central: Fix Search-related JavaScript problemsTesting with the URL Inspection tool and the Rich Results Test.