Do AI Crawlers Render JavaScript in 2026? Why Your Next.js SPA Is Invisible to ChatGPT (and How to Fix It)
In short
As of June 2026, no major AI crawler renders JavaScript. GPTBot, OAI-SearchBot, ClaudeBot, and PerplexityBot read only your initial HTML, so anything painted after hydration is invisible to ChatGPT, Claude, and Perplexity. Test it with curl -A "GPTBot/1.0", then move your content into Next.js Server Components. Details and a CI harness in my web development services.

On this page
Yes, in 2026 the major AI crawlers still fetch your initial HTML and stop there. GPTBot, OAI-SearchBot, ClaudeBot, and PerplexityBot do not run your JavaScript, so anything your Next.js app paints after hydration is invisible to ChatGPT, Claude, and Perplexity. If your important content lives inside 'use client' components that fetch on mount, an AI engine sees an empty shell. The fix is to make sure the words you want cited exist in the server-rendered HTML, which the App Router gives you by default if you stop fighting it.
I have spent the last year cleaning up "why are we invisible to ChatGPT" tickets, and the root cause is almost always the same: a beautiful SPA that ships a near-empty and fills it in with client-side fetches. Let me show you how to confirm the problem in two minutes, and then how to actually fix it in the App Router.
Do AI crawlers render JavaScript in 2026?
No. As of June 2026, no major AI crawler reliably renders JavaScript. They request your URL, read the HTML the server returns, and index that. They do not spin up a headless browser, wait for hydration, or execute your useEffect data fetches.
This is the dangerous part, because it looks fine to you. You open the page in Chrome, JavaScript runs, the content appears, and you assume the crawler sees the same thing. It does not. The crawler is closer to running curl than running a browser.
There is a useful piece of context here. In March 2026, Google removed the old SEO warning it used to show about client-side rendering, because Googlebot does render JS through a deferred second pass. That change quietly convinced a lot of teams that "rendering is solved." It is solved for Googlebot. It is not solved for the AI crawlers, and the audits I have run in June 2026 confirm none of the big four execute your scripts. Conflating Googlebot's behavior with GPTBot's behavior is the false-confidence trap that gets sites left out of AI answers.
Which AI crawlers fetch HTML only?
All of the ones that matter for AI visibility right now. Here is how the four primary bots behave and what user-agent string they send.
| Bot | Operator | User-agent contains | Renders JS? | What it powers |
| GPTBot | OpenAI | GPTBot | No | Model training corpus |
| OAI-SearchBot | OpenAI | OAI-SearchBot | No | ChatGPT search results |
| ChatGPT-User | OpenAI | ChatGPT-User | No | Live user-triggered fetches |
| ClaudeBot | Anthropic | ClaudeBot | No | Claude training and retrieval |
| PerplexityBot | Perplexity | PerplexityBot | No | Perplexity answer engine |
| Content type | Where it should live | Why | ||
| Headings, body copy, prices, FAQs | Server Component (RSC) | Must be in initial HTML for crawlers | ||
| Static marketing pages | SSG via revalidate / static export | Fast and fully pre-rendered | ||
| Personalized or auth-gated data | Server Component with request-time fetch | Still server-rendered, not crawler-relevant anyway | ||
| Buttons, modals, carousels, form state | 'use client' leaf component | Interactivity only, no indexable content |
The rule I give every team: 'use client' is for behavior, not for content. Push the directive as far down the tree as possible so it wraps a button, not a whole page. If a paragraph of text only exists inside a client component that fetches on mount, that paragraph is invisible to every AI crawler, full stop.
A few more things that bite people:
next/dynamicwith{ ssr: false }does exactly what it says and removes the component from server HTML. Do not use it for anything you want cited.- Content hidden behind tabs or accordions is fine as long as it is in the DOM. The crawler reads the markup, not the visual state, so collapsed content still counts if it is server-rendered.
- Infinite-scroll or "load more" content that only fetches on interaction is invisible past the first batch. Paginate with real, crawlable URLs instead.
If you want a second set of eyes on whether your stack is server-rendering the right things, this is the kind of audit I do as part of my web development work ↗.
What about JSON-LD and structured data?
Put your structured data in the server-rendered HTML, not injected by client script. AI engines and search crawlers both read JSON-LD straight from the initial response, and it gives them clean, unambiguous facts to cite.
In the App Router this is a server component that simply renders a script tag:
export default async function ProductPage({ params }: { params: { id: string } }) {
const product = await getProduct(params.id);
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Product',
name: product.name,
description: product.description,
offers: { '@type': 'Offer', price: product.price, priceCurrency: 'USD' },
};
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<article>{/* ... */}</article>
</>
);
}
Because this renders on the server, the JSON-LD is in the raw HTML that GPTBot and PerplexityBot read. If you instead inject it with a client-side analytics-style snippet, you are back to square one and the bots never see it.
The short version
If you remember one thing: open a terminal, curl your most important page with -A "GPTBot/1.0", and read what comes back. That output is your entire presence to AI engines today. Everything painted after hydration is a private show for human visitors with JavaScript on.
Most "invisible to ChatGPT" problems I see are not penalties or bans. They are architecture. The content is real, it is just on the wrong side of the hydration boundary. Move it to the server, keep 'use client' for behavior, and verify with the bot-UA harness in CI so it never silently regresses.
If you are staring at a Next.js SPA and not sure which content is actually reaching the crawlers, that is a quick thing to check together. Reach out through my contact page ↗ and we can run the audit on your real pages.
FAQ
Do AI crawlers render JavaScript in 2026?
No, as of June 2026 no major AI crawler including GPTBot, OAI-SearchBot, ClaudeBot, and PerplexityBot reliably executes JavaScript; they index only the initial HTML your server returns.
Why does my page rank on Google but stay invisible to ChatGPT?
Googlebot runs a deferred second-pass renderer that picks up hydrated content, but AI crawlers never execute your client JavaScript, so client-rendered content can be visible to Google and invisible to ChatGPT at the same time.
How do I test what an AI crawler sees on my site?
Run curl with the bot's user-agent, for example curl -A "OAI-SearchBot/1.0" against your URL, then grep the output for your expected content; if it is missing, the bot cannot see it either.
Why does my 'use client' content disappear from crawlers?
A 'use client' component that fetches data on mount renders only a loading state in the server HTML, so the real content arrives after hydration and the crawler, which never hydrates, only ever sees the placeholder.
How do I fix an invisible Next.js App Router page?
Keep indexable content in Server Components, which render into the initial HTML by default, and reserve 'use client' for interactive leaf components like buttons and forms.
Working on something like this?
I build web apps, AI features, and mobile products for clients. If this article matches a problem you have, tell me about it.
Start a conversationMalik Hamza Shabbir · Full-Stack & AI Engineer
I build full-stack and AI products solo: a reputation SaaS in production, RAG pipelines, and React Native apps. I write from what I ship, not from documentation summaries.
Related articles
Google AI Mode Is Now the Default: What Query Fan-Out Means for How You Structure Pages
Google AI Mode is now the default, and it splits one query into many sub-queries before retrieving per sub-query. Here is what query fan-out actually does under the hood, from someone who builds fan-out RAG pipelines, plus a concrete playbook for restructuring your pages to get surfaced.
Building Next.js Apps for AI Agents: AGENTS.md, @vercel/next-browser, and Agent DevTools in 16.2
Next.js 16.2 makes AI agents first-class users: create-next-app writes a version-matched AGENTS.md (100% vs 79% eval pass rate), @vercel/next-browser hands an LLM screenshots, network, and console in one call, and experimental Agent DevTools exposes framework state. Here is how I set it all up.
Adding Semantic Search to an Existing SaaS in a Weekend: pgvector Before You Reach for Pinecone
If your SaaS already runs on Postgres, you can add real semantic search in a weekend with pgvector and one Next.js route, no second database. Here is the embed-on-write migration, the HNSW query, a semantic cache, and the vector count where Pinecone or Milvus actually starts to make sense.