Fix AI visibility / Next.js

Why AI crawlers can't read your Next.js site

Next.js hands AI crawlers fully rendered HTML when you use SSR, static generation, or server components, and an empty shell only when your content is rendered client-side or loaded in useEffect after the page mounts.

Next.js renders on the server by default, so it is one of the more AI-readable frameworks you can pick, right up until you fetch your content in the browser instead of on the server.

If ChatGPT, Perplexity, or Claude appear to read nothing on your Next.js site, the framework is almost never the cause. The cause is a specific rendering choice on specific pages. The fix is to make sure the words a crawler needs are in the HTML your server sends, before any JavaScript runs.

How Next.js renders

By default, Next.js sends crawlers real HTML. In the App Router, components are Server Components unless you mark them "use client", so they render on the server and their text lands in the raw HTML. In the Pages Router, getServerSideProps (SSR) and getStaticProps (SSG) do the same. Even a Client Component is prerendered on the server for its initial state, so its static markup is present in the first response, unless you opt that component out with ssr: false. Most Next.js pages are readable.

Content goes missing from the raw HTML only when it depends on JavaScript running in the browser. The common cases: data fetched inside useEffect, SWR or React Query used without a server-prefetched initial value, components imported with next/dynamic and ssr: false, and sections gated behind a mounted or hydration check that returns nothing on the server. Most AI crawlers, including GPTBot, ClaudeBot, and PerplexityBot, run no JavaScript, so for those sections they receive the empty initial state rather than the content. This is what Lantad detects as a gap between the raw fetch and the full browser render.

The fix

  1. Render on the server by default

    Keep pages and content as Server Components in the App Router, and reserve "use client" for the small interactive pieces (menus, forms, widgets). In the Pages Router, use getServerSideProps or getStaticProps so the page returns finished HTML. This puts your visible text in the raw response instead of leaving it for the browser to build.

  2. Move data fetching server-side

    Fetch your content where the server can see it. In the App Router, use an async Server Component and await the data, then pass it down as props. In the Pages Router, fetch in getServerSideProps or getStaticProps. Replace client-only useEffect fetches for anything a reader needs on first load. If you keep SWR or React Query, hydrate their cache with a server-fetched initial value so the first HTML already contains the content.

  3. Prefer static generation where content is stable

    For pages that do not change per request (marketing pages, docs, blog posts), generate them at build time with generateStaticParams, or produce a fully static site with output: 'export'. Static HTML is the most reliable thing you can hand a crawler. Note that static export does not support SSR features, so use it only where you do not need per-request rendering.

  4. Audit ssr: false and hydration gates

    Search your code for next/dynamic({ ssr: false }) and for patterns like if (!mounted) return null. Both remove content from the server HTML, which is the exact thing crawlers read. Note that ssr: false is not allowed inside a Server Component, so it appears only in Client Components. Keep it only for genuinely browser-only widgets (maps, charts, editors), never for primary text or product content.

  5. Verify the raw HTML, then scan

    Check what the server actually sends: run curl on the URL, or use view-source in the browser, and confirm your headings and body copy are present without JavaScript. Then run a free Lantad scan of the page. Lantad fetches the URL with no JavaScript the way AI crawlers do, renders it fully in a browser, and diffs the two. Your Prose Parity score shows how much of the rendered text is actually in the raw HTML, so you can confirm the fix landed rather than assume it did.

Common questions

Does adding "use client" make my page invisible to AI crawlers?
No. A Client Component is still prerendered to HTML on the server for its initial state, so its static markup is in the raw response. It only goes blank when the content depends on something that happens after the page mounts, such as a useEffect fetch or a browser-only value, or when you disable server rendering with ssr: false. Keep "use client" for interactivity, and make sure the text itself is rendered on the server.
Is the App Router or Pages Router better for AI readability?
Both can hand crawlers full HTML. App Router Server Components are readable by default, and the Pages Router is readable through getServerSideProps or getStaticProps. The failure mode is identical in both: fetching your content in the browser instead of on the server. Pick either, and keep data fetching server-side.
I use SWR or React Query. Do I have to remove them?
No. Keep them for interactivity and revalidation, but give them a server-fetched initial value so the first HTML already contains the content. Hydrate the cache from a Server Component or from getServerSideProps. Client-only fetching leaves the initial HTML empty for that section, which is what a no-JavaScript crawler sees.

See what AI reads on your Next.js site

Run a free scan: paste a URL and see exactly what AI crawlers can and cannot read, with a graded report and ranked fixes.