On a SaaS site, the /features/reporting page looked perfect in the browser; the title, pricing cards, and screenshots were all in place. However, when the same URL was requested via curl, it returned only a <div id='root'></div>, a few JavaScript files, and an empty title. The problem was clear: the deep link carried no actual content in the raw HTML, and if the bot didn't execute JavaScript, the page remained essentially blank.
The short answer: The first thing you should check for AI readability on SPA sites is whether every important URL returns meaningful HTML from the server when accessed directly. If your React, Vue, or Angular application fetches content only on the client side via an API and injects it into the DOM later, some AI bots and answer engines might not see the product name, description, author info, tables, or FAQ content. The following 4 deep link tests focus on this raw HTML gap rather than general JavaScript errors.
Why is an empty app shell an AI readability issue?
In SPA architecture, the server typically returns the same file for all routes: a small HTML shell, a root element, and bundle links. The user's browser downloads the JavaScript, the router finds the correct page, API calls are made, and content is injected into the DOM. This flow is fast and perceptible for real users; however, the situation is different for bots.
AI search engines and agents do not all have the same rendering capacity. Some can render the page to a limited extent, while others extract content via raw HTML, meta tags, structured data, and link graphs. Furthermore, rendering is costly; assuming that a bot will download the entire bundle and wait for API responses on every URL is not a safe strategy. Therefore, the “it looks fine in the browser” test does not mean the content is accessible to AI.
Deep links are the keyword here. Your homepage might be SSR, but URLs like /blog/2025/report, /products/red-shoes, or /support/return-policy might return only an app shell when opened directly. AI readability audits should be performed specifically by looking at the raw HTML output of these sub-pages.
4 deep link tests
Test 1: Is there unique content in the raw HTML?
First, choose a page-specific phrase that doesn't appear elsewhere: a product name, an article H1, a model number, an author's name, or a table header. Request the URL from the terminal, not the browser, and search for this phrase within the HTML. If there is only navigation, a root div, and JavaScript files, the page produces a weak signal for AI.
In a successful result, the H1, at least a summary of the main paragraph, important list items, and internal links should appear in the raw HTML. Not all interactive components need to come from the server; however, the text describing what the page is about should be selectable even if the bot does not render JavaScript.
Test 2: Are the direct URL status and redirect behavior correct?
A common mistake in SPA deployments is returning a 200 status for all unknown routes to the index file. This looks practical for the client-side router, but from a bot's perspective, /products/real-product and /products/non-existent-product might return the same empty shell. Consequently, the actual page, soft 404s, and homepage copies get mixed up.
Check the HTTP status code, final URL, and canonical behavior for every important deep link. An existing page should return 200, a deleted page should return 404 or 410, and a moved page should redirect via 301/308 to the new address. If a product page redirects to the homepage with a 302 when opened directly, the AI bot may struggle to understand that the product is an independent resource.
Test 3: Does it return a different app shell for bot user-agents?
Some CDN, WAF, or middleware rules might return different responses based on the user-agent. This is not always intentional cloaking; security plugins, bot protection settings, or edge cache variants might be misconfigured. Therefore, compare the same deep link with requests using a normal user-agent and those mimicking known bot names.
The goal here is not to simulate exactly what a bot does, but to see if your server is stripping content when the user-agent changes. If a normal request shows product descriptions but a bot user-agent request returns only an app shell, you are providing incomplete material to the AI process right from the start. Note the differences in response size, title, canonical, and unique text.
Test 4: Are meta data and structured data in the initial HTML?
In SPAs, the title and meta description are often updated via a client-side head manager. Even if the correct title appears in the browser tab, the initial HTML might contain the same title for all pages. AI answer systems can derive signals from titles, descriptions, canonicals, hreflang, and schema data for page summarization and source evaluation; having these fields in the raw HTML provides a safer foundation.
Check for page-specific titles, meta descriptions, canonicals, Open Graph fields, and JSON-LD types like Article, Product, FAQPage, or BreadcrumbList in the deep link HTML. Structured data must be consistent with visible content; providing a product name via Product schema when it is absent in the raw HTML is technically weak and risky for maintenance.
How to fix the issue?
The solution depends on the framework and content type. For URLs that need to be indexed, such as news, blogs, categories, products, documentation, and location pages, prefer SSR, SSG, or prerendering. Approaches like Next.js, Remix, or Astro for React; Nuxt for Vue; or Angular Universal for Angular can be used for this purpose. If you are not migrating your existing pure SPA immediately, at least consider generating static HTML at build time for critical routes.
You don't have to generate the fully interactive version of every URL on the server. Ensure the main text, heading hierarchy, basic table/list data, and internal links are present in the initial HTML; filters, calculators, comment boxes, or personalized panels can remain client-side. This distinction provides a readable foundation for AI bot access while preserving client-side interaction.
Technical checklist
- Create an inventory of important URLs: select at least 20 deep links from product, blog, category, support, location, and pricing pages.
- Search for a unique H1 or product/article name in the raw HTML for each URL.
- Record the HTML response size; identical 3-8 KB app shell outputs are a strong sign that content is not being delivered.
- Check 200, 301, 404, and canonical behavior; ensure all routes don't fall back to the homepage.
- Compare output between a normal user-agent and a bot user-agent; if there is a difference, investigate CDN/WAF/cache rules.
- Look for page-specific titles, meta descriptions, canonicals, and basic schema data in the initial HTML.
- Test whether you can read the main content of the page using a browser with JavaScript disabled or a text-based browser.
- Do not leave critical content coming from the API solely client-side; move it to the SSR/SSG/prerender layer.
- Check robots.txt to ensure you aren't accidentally blocking JS, CSS, or API endpoints.
- Add these tests to your CI pipeline or weekly scan report after deployment.
Example scenario: product deep link test
Let's assume we are checking whether the following URL in a Vue SPA is accessible to AI: /products/red-running-shoes. The page looks full in the browser; in the terminal, we examine the raw HTML output with four short commands.
# Deep link to test
URL='https://www.example.com/products/red-running-shoes'
# 1) Download raw HTML
curl -L -s $URL -o raw.html
# 2) Status code and final URL
curl -L -s -o /dev/null -w '%{http_code} %{url_effective}n' $URL
# 3) Search for unique product name
grep -qi 'Red Running Shoes' raw.html && echo 'PASS: content in raw HTML' || echo 'FAIL: content missing'
# 4) Search for meta and schema traces
grep -Eio '<title>|meta name=.description.|application/ld[+]json|rel=.canonical.' raw.html
# 5) Compare with bot user-agent
curl -L -s -A 'GPTBot' $URL -o gptbot.html
diff -u raw.html gptbot.html | head -80In the failed example, only the app root, bundle files, and the general site title appear in the raw.html file. In the improved example, the same file contains the product H1, short description, price range, breadcrumb links, canonical, and Product schema. This output makes the page's core topic extractable even if the AI bot does not execute JavaScript.
Common mistakes
- Looking only at Lighthouse scores: An SPA with a good performance score might not carry content in its raw HTML.
- Testing only the homepage and assuming the whole site is fine: Issues usually appear in product, article, and support deep links.
- Returning all 404s to index.html: This behavior makes it difficult for bots to distinguish between a real page and an incorrect URL.
- Updating meta tags only client-side: The same title might be spread across hundreds of URLs in the initial HTML.
- Generating different content specifically for bots: Instead of serving text invisible to users only to bots, serve the visible main content via SSR/SSG.