Skip to content
Searchpedia SEO field notes Callum Bennett Callum

Site ops

Ajax SEO

I used to think AJAX was harmless for SEO if Google could render JavaScript, but after a client lost 80% of organic traffic, I now insist on verifying raw HTML content first.

Beginner3 min readUpdated 2026-07-27Notes by Callum Bennett

What I’d do first

  • Audit every AJAX call on your site and note if content, links, or metadata depend on it.
  • For each AJAX-loaded view that deserves indexing, create a unique, crawlable URL using the History API and ensure the server renders the content for that URL.
  • Ensure core content is in the initial HTML or server-rendered; test by viewing page source before trusting the render queue.
  • Use proper HTTP status codes for empty or error states, not 200 with a blank page, to avoid soft 404s.
  • Set up dynamic rendering or pre-rendering for AJAX-heavy sections if server-side rendering is not possible, but treat it as a temporary fix.

The path I'd take

I start by auditing every AJAX call on the site. Not just the obvious product pages, but also navigation menus, filter widgets, and infinite scroll sections. For each, I note whether the content it loads is critical for indexing or just a nice-to-have interaction. My rule: if it would hurt to lose that content from search, it must be in the initial HTML. For example, on a recent client project, we had a product catalogue where all descriptions loaded via an AJAX request on scroll. There were 1,200 products. After checking the raw HTML, I found only the first 10 descriptions were present. The rest came from a JSON endpoint. That is a risk I cannot take. I assigned each product a unique URL using the History API (e.g., /products/red-widget) and also ensured the server rendered the description into the HTML response for that URL. That is step two: for each AJAX-loaded view that deserves a page, create a dedicated, crawlable URL. Use history.pushState to update the browser URL when the user interacts, but always serve the full content server-side when that URL is requested directly. Step three is deciding on a rendering strategy. If the site is already built with a JavaScript framework, I evaluate server-side rendering (SSR) or static generation. For legacy sites, I sometimes use dynamic rendering via a middleware that serves pre-rendered HTML to crawlers. I have seen numbers that show organic traffic recovery of 30-50% after moving from client-rendered to server-rendered content. The effort is large, but the payoff is real. I treat this as a [technical SEO](/technical-seo/) issue and always verify with a [SEO audit](/seo-audit/) after deployment.

Watch-outs

The most common mistake I see is soft 404s from client-side routing. When a user or bot hits a non-existent route in a single-page application, the server returns a 200 status with an empty shell. The JavaScript then decides to show an error state. But the crawler never executes that logic and sees a 200 page with no content. That page can be indexed as a thin page, or worse, dilute the site's quality. I always check that my AJAX application returns a proper 404 HTTP status when a resource does not exist. Another watch-out is missing metadata. Titles, meta descriptions, and Open Graph tags must be present in the initial HTML, not injected later by JavaScript. I have seen sites where every page shares the same title because the title is set in a JavaScript function that Google's renderer may or may not execute. That is a quick way to lose click-through rates. Third, infinite scroll without [pagination](/pagination/) is a risk. If content loads as the user scrolls and the URL does not change, crawlers cannot reach beyond the first batch of items. I have tested this: using the Google URL Inspection tool, I saw that only the initial set of products was rendered. The remaining 300 products were invisible to search. The fix is either server-side pagination or a "load more" button that updates the URL fragment. Also be careful with [canonical tags](/canonical-tag/) on AJAX pages. If you use history.pushState to change the URL but the canonical tag stays the same, you risk having multiple URLs pointing to one canonical, which might not be the intended index page. I have seen this cause confusion where filter combinations were wrongly canonised to the unfiltered root page. Always set the canonical tag dynamically based on the current view.

What I got wrong

I used to believe that because Google can render JavaScript, AJAX-loaded content was safe. I thought the crawling pipeline would see it after the render queue. That belief cost a client dearly. After a core update in 2023, their entire product catalogue dropped out of search. The pages had been loading the price and title via AJAX from a separate API. The initial HTML contained only a loading spinner. I had assured them it was fine. When I saw the Google Search Console data — organic impressions went from 12,000 to 2,000 in three weeks — I panicked. The fix was not simple. We had to server-render all 800 product pages. That meant rewriting the product controller to include the API data in the initial response. It took a team of three developers two months. Traffic recovered to 9,000 impressions after six weeks, but it never fully returned to the pre-update level. I now run a SEO audit with view-source as the first check. If I do not see the core content in the raw HTML, I do not trust that Google will render it. I also submit a [sitemap](/what-is-a-sitemap/) to encourage discovery. Another thing I got wrong: I used to think that using the History API with replaceState was enough to make AJAX views indexable. It is not. The URL history does not create a unique page unless the server serves content when that URL is hit directly. I now treat every AJAX view as a separate page with its own accessible URL, not just a client-side route. I have learned to question my assumptions about rendering every time a Google core update is announced.

Next step

Quick answers

Can Google index content loaded via AJAX after user interaction?

Yes, if Googlebot executes the JavaScript and the content becomes part of the DOM, it can be indexed. However, I have found that content loaded only on user scroll or click may not be rendered in time, especially if the render queue is deep. I always pre-load critical content in the initial HTML.

Should I use pushState for every AJAX filter change?

Not always. If the filter combination is not a distinct page you want indexed, avoid pushState. For example, sorting by price may not warrant a unique URL. Only use pushState for views that have a stable URL and are likely to be shared or linked to.

Is dynamic rendering better than server-side rendering for AJAX sites?

Dynamic rendering (serving pre-rendered HTML to crawlers) is a faster fix for existing sites, but it adds complexity in detecting bots. Server-side rendering provides consistent experience for all users and is more sustainable. I prefer SSR for new builds.

Sources

Primary documentation is linked directly. Anything commercial is marked nofollow.

Notes from Callum Bennett.