If your content only appears after JavaScript runs, headless AI crawlers see a blank page — your words have to be in the raw HTML response that comes back from the server.
By Christopher Foster-McBride, Founder, AI Knowledge Signal · Reviewed 2026-10-13
Why it matters
When an AI crawler requests a page, it reads the HTML the server returns — and most of them stop there. Unlike a browser, a headless crawler typically does not execute JavaScript. So if your headings, paragraphs, and tables are rendered client-side by a single-page-app framework, the crawler receives an almost-empty shell: a <div id="root"></div> and a script tag. Your content is invisible, regardless of how good it is.
This is the most under-diagnosed crawlability failure, because the page looks perfect to you — your browser runs the JS. The crawler doesn't. The fix is to deliver the substance in the initial HTML response through server-side rendering (SSR) or static generation, so what the crawler reads matches what the reader sees.
Google is the main exception (Applebot also renders): Googlebot renders JavaScript, so Google Search — including AI Overviews and AI Mode — usually does see client-rendered content, just later and less reliably. Every other AI assistant you want to be cited by should be treated as no-JS.
What good looks like
The content is present on first response — before any JavaScript executes:
View Source (not DevTools "Elements") shows your real headings, paragraphs, lists, and tables as HTML.
The page is delivered via SSR or static generation, not client-only rendering.
Where JS enhancement is genuinely needed, a full HTML fallback exists underneath it.
Your JSON-LD (Phase 5.1) is in the raw source too — not injected after load.
A curl of the URL returns the content, not just an empty app shell.
Step by step
Test what's in your raw HTML
Don't trust the rendered page. Use the browser's View Source (Ctrl/Cmd+U) or run curl https://yourdomain.com/page and read what actually comes back. If you see your article text, you're fine; if you see a near-empty <div id="root"> and a bundle script, your content is JS-only.
Choose SSR or static generation
For content pages, render on the server. Static generation (build-time HTML) is ideal for pages that don't change per request — guides, articles, docs. SSR (per-request HTML) suits pages with dynamic data. Most modern frameworks (Next.js, Nuxt, SvelteKit, Astro) support both; pick the one that emits real HTML for the route.
Render the content, not just the shell
Make sure the rendered HTML contains the substance — the headings, body copy, lists, and tables — not just the layout chrome. A server-rendered navbar around a client-rendered article still leaves the article invisible.
Provide a fallback where JS is unavoidable
If a component truly must be client-side, ensure a meaningful HTML fallback is in the source (e.g. <noscript> content or progressively enhanced markup) so the core information survives without JavaScript.
Verify as a crawler would
Re-run curl --user-agent "GPTBot" https://yourdomain.com/page and confirm all headings, paragraphs, and tables are present in the response. This reproduces what a no-JS crawler reads — though your WAF may treat the real, IP-verified bot differently (Phase 1.4).
Worked examples
1 Before / after: app shell vs. rendered HTML
The same article route. The "before" is what a default client-rendered SPA ships; the "after" is what the crawler needs.
<body>
<article>
<h1>Generative Engine Optimisation</h1>
<p>GEO is the practice of...</p>
<table>...</table>
</article>
</body>
Full content in the source on first load.
2 The two-minute crawler test
A quick command-line check anyone can run to confirm a page is readable by a headless crawler — no tooling required.
# Fetch the raw HTML as an AI crawler would (no JS executed)
curl -s --user-agent "GPTBot" https://yourdomain.com/your-page \
| grep -i "<h1\|the first sentence of your article"
# If grep finds your heading and copy → server-rendered, good.
# If it returns nothing but you SEE the content in a browser → JS-only, fix it.
Common mistakes
Mistake
Fix
Judging by the rendered page in DevTools
Check View Source or curl — DevTools shows the post-JS DOM, which the crawler never sees.
Client-only SPA for content pages
Use SSR or static generation so the HTML response carries the content.
Server-rendering only the layout shell
Render the substance (headings, body, tables), not just the navbar and footer.
JSON-LD injected by JavaScript
Emit it server-side so it's in the raw HTML (Phase 5.1).
Assuming all crawlers run JS
Googlebot (and, per the Vercel/MERJ study, Applebot) render JavaScript; most AI crawlers don’t. Design for the no-JS case.
Key text hidden behind tabs, accordions or "load more"
Put the text in the initial HTML and toggle visibility with CSS; don’t fetch it on click.
Limits of this advice
Crawler rendering behaviour changes. Re-test with curl rather than relying on any published list of which bots run JavaScript.
SSR makes content readable; it does not make it citable on its own.
Checklist
View Source shows real headings, paragraphs, lists, and tables
Content pages use SSR or static generation, not client-only rendering
The rendered HTML includes the substance, not just layout chrome
A meaningful HTML fallback exists wherever JS is unavoidable
JSON-LD is present in the raw source (Phase 5.1)
curl --user-agent "GPTBot" <url> returns the content, not an empty shell