SEO

6 Fixes for JavaScript Pages AI Crawlers Can’t Read

Six ways to put a page’s facts into the HTML AI crawlers read, what each costs, and where each goes stale, starting with the price.

Author:
Vlad Shvets
Contributors
Vlad Shvets
Date:
November 3, 2026

Before you pick a fix, open your pricing page the way an AI crawler opens it. The crawler takes the HTML your server sends and reads that. It runs none of the scripts that draw the page in a browser, so anything those scripts add never reaches it.

  1. Fetch the page raw. Use curl or view-source, never the Elements panel in your browser’s developer tools, which shows the page after its scripts ran.
  2. Count the words that are left. Strip the scripts and styles first, then set the count beside the words a visitor sees. A page that keeps most of its words is readable; one that keeps a header and a footer is a shell.
  3. Search it for the answer. Look for the price, the plan names and the limits a buyer asks an engine about. The count catches an empty page. Only the search catches a page that names its plans and never prices them.

On 29 October we ran the first two steps on the pages AI engines cited in answers about our own category. Of 339 pages, 292 carried nine-tenths or more of their words before any script ran, and 14 carried less than half. One of four partner audits we ran this autumn found a pricing page that showed no price until its scripts ran.

The crawlers are why that gap matters. In one month of late 2024, Vercel and MERJ counted 569 million GPTBot fetches and 370 million from Claude on Vercel’s network, and none of the major AI crawlers they measured rendered JavaScript. Gemini, which crawls with Googlebot, was the exception.

Empact Partners runs this check in the website audits of our SEO work, which we call Search Everywhere Optimization because search now includes AI engines. We are a consultancy doing go-to-market work inside software companies’ marketing teams. Six fixes follow. Each makes its HTML in a different place, which decides what it costs and how it goes stale.

Fix Where the HTML is made Price on 3 Nov 2026 How it fails
Prerendering service By the service, when a crawler asks From $59 a month (Prerender.io) Serves a snapshot from before the price changed
Edge rewrite At the CDN, as the page streams past From $5 a month (Cloudflare Workers) Writes the data into a script the crawler skips
Prerendered routes Once, at build Build minutes, no service fee Leaves out what the browser fetches later
Regenerating static pages At build, then on a timer or an event Vercel Pro at $20 a month, plus usage One stale copy after each window
Server rendering On the server, for every request Compute on every request A loading message, or a widget still empty
New marketing site At publish A migration; Webflow from $15 a month A component that renders only in the browser
Vlad Shvets
Founder @ Empact Partners
A rendering fix is finished when the price is in the first response, not when the page stops looking empty. A pricing page that names three plans and never says what they cost passes the test most teams run, and it still cannot answer the one question a buyer brings to an engine about it.

A Prerendering Service for Crawlers

Best for: a single-page app that nobody will rebuild this quarter.

A prerendering service sits between your site and the crawlers. When a known crawler asks for a page, the service loads it in a headless browser, waits for the scripts and hands back finished HTML. Visitors keep getting the app as it is. Google calls this dynamic rendering, “a workaround and not a recommended solution.”

Who delivers it: Prerender.io, which also installs as a Netlify extension, and Netlify’s own Prerender extension.
What it renders: whatever a headless browser sees in the time it waits. Prerender.io waits up to 20 seconds by default, then caches what it has.
What it costs: Prerender.io charges $59 a month for 30,000 renders, $169 for 100,000 and $419 for 500,000. Netlify’s extension costs only the functions it runs.
Cache and freshness: a cached copy until it expires, from 24 hours to 7 days on Prerender.io’s Starter plan and down to 6 hours on Pro.
How it fails: the crawler gets a snapshot taken before the price loaded, or a price that changed after the last re-render.

The bill is set by freshness, and Prerender.io’s pricing page does the arithmetic itself: 1,000 pages re-rendered daily come to 30,000 renders a month, 60,000 once mobile gets its own copy. A price that changes on Tuesday reaches crawlers at the next re-render.

The other failure is timing. A page that loads its prices from an API after the first paint can be captured before they arrive. Prerender.io documents a prerenderReady flag for this: the page holds it at false until its content is in, and the service waits.

Verdict: the fastest way to give AI crawlers readable text when the app cannot change this quarter. Treat it as a bridge: the crawlers read a copy, and the copy needs its own refresh schedule. Skip it if the pages that sell change daily, or if your team can change the build, because every fix below puts the facts in the page itself.

An Edge Rewrite of the HTML

Best for: a team that controls its CDN but cannot change the app’s build soon.

An edge worker runs in the content delivery network, between your server and everyone who asks for a page. It can rewrite the HTML as it streams past: fetch the current prices from your API and write them into the pricing table’s empty cells, for crawlers and visitors alike. The app’s code and build stay as they are.

Who delivers it: Cloudflare Workers, through its HTMLRewriter. Vercel now advises moving off its own Edge runtime, and Next.js 16.3 no longer supports it.
What it renders: only what the worker writes. It runs no scripts; it edits the HTML that was already on its way out.
What it costs: Cloudflare’s Workers Paid plan starts at $5 a month with 10 million requests included, then $0.30 per million.
Cache and freshness: as current as the data the worker fetches on each request, or as old as any cache you put in front of that fetch.
How it fails: the worker writes the data where the app wants it, inside a script, and the crawler gets nothing it reads.

That failure is in Cloudflare’s own documentation. Its single-page-app example fetches the data at the edge and writes it into the page as a script variable, so the app skips its first API call. A crawler that runs no JavaScript gets a blob inside a script tag. The rewrite has to write visible HTML: the price in the price cell.

The second risk is ownership. The worker finds elements by their names in the markup, and the app team can rename them in a release without telling whoever runs the worker. If a handler throws, Cloudflare documents that the visitor receives a truncated page.

Verdict: the right fix when the pages are fine for people, the gaps are a few known facts, and the CDN is yours. Skip it when the gaps are whole sections, because then you are maintaining a second copy of the page inside a worker.

Prerendered Routes in the App You Have

Best for: a handful of marketing routes in a single-page app on a static host.

The build renders a list of routes once, writes each one out as a finished HTML file, and leaves every other route to the app. React Router does it with its prerender setting, writing an HTML file per listed path and a fallback for the rest. Angular has its own prerendering, and teams with neither run a script after the build that does the same.

Who delivers it: React Router’s prerender option, Angular’s prerendering, or a postbuild script run against the built app.
What it renders: what the build fetches when it runs. Data the browser fetches later is not in the file.
What it costs: no service fee. Build minutes, and an engineer’s time to keep the route list and the build’s data sources current.
Cache and freshness: frozen at build. A new price waits for the next deploy, unless changing the price triggers one.
How it fails: the page ships its text and leaves out any fact the build never fetched, such as a price the browser reads from the database.

A July r/TechSEO thread shows how this fix fails. A SaaS founder found their homepage nearly empty to AI crawlers and prerendered the main marketing routes. A commenter’s scanner then read the pricing page: 194 words raw against 511 in the browser, plan names present, no price anywhere.

In the same thread the founder traced the cause. The prerender script described the tiers in prose, and the prices came from the database and rendered only in the browser. Angular’s guide states the limit for every tool of this kind: prerendering “requires that all information necessary to render a page is available at build-time.”

Verdict: the cheapest real fix for a single-page app whose marketing routes are few and change on release days. Make the build pull prices from the same source the browser uses, and search the built file for them before it ships. Skip it if prices or availability come from a live system: the snapshot is right on deploy day and wrong after.

Static Pages That Regenerate

Best for: pages whose facts change between deploys, on a framework that can rebuild one page at a time.

The page is built as static HTML and then rebuilt on its own, after a set time or when your system says something changed. Next.js calls this Incremental Static Regeneration, or ISR. Crawlers and visitors always get finished HTML from a cache, and the cache refreshes without a full deploy.

Who delivers it: Next.js, with its revalidate setting and its revalidatePath and revalidateTag calls, on Vercel or on your own Node.js server.
What it renders: everything the server fetched when it last rebuilt the page.
What it costs: Vercel Pro is $20 a month and includes 1 million ISR reads and 200,000 ISR writes, then from $0.40 per million reads and $4 per million writes. Each rebuild also runs as compute.
Cache and freshness: once the window passes, the first request still gets the old page while the new one builds. An on-demand call rebuilds it on the next request.
How it fails: a crawler fetches the page in that gap and keeps the old price, or a price change never triggers the call and the old price waits for the timer.

The Next.js guide says what happens after the window: “the next request will still return the cached (now stale) page.” That stale answer can go to a crawler. The default cache is also per server instance, so an on-demand call clears only the instance that receives it, and a static export cannot regenerate at all.

Verdict: the fix we would choose for a pricing page already on Next.js, as long as the price change itself fires the on-demand call. Pair it with a long timer, so the page still refreshes if the call never arrives. Skip it on a static export, or on pages with so little traffic that the cache never pays for itself.

Server Rendering for the Pages That Sell

Best for: prices or availability that change by the hour, by region or by visitor.

The server builds the page on every request, with the data fetched at that moment, and sends finished HTML. Next.js does it for any route that reads live data, React Router does it with server rendering on, and Astro does it for any page that opts out of prerendering. Only the routes that need it have to work this way.

Who delivers it: Next.js dynamic routes, React Router with server rendering on, and Astro pages rendered on demand.
What it renders: everything the server fetches for that request. Anything the browser fetches after the page loads is still missing.
What it costs: compute on every request. Vercel Pro includes 4 hours of active CPU a month, then from $0.128 an hour, and 1 million function invocations, then from $0.60 per million.
Cache and freshness: current on every request, unless a cache sits in front of the server.
How it fails: the page streams a loading message first and the content later, or a pricing widget still loads its numbers in the browser.

Server rendering asks the most of the team so far, and still leaves two gaps. A Next.js route can stream: the server sends a fallback such as “Loading...” at once and the content later in the same file. A crawler that runs no scripts finds the fallback where the content should be, and the content further down, waiting for a script to move it into place.

The second gap is the pricing widget again. A component that fetches its numbers in the browser renders nothing on the server, whichever framework the page runs on. And every request now runs code, so someone has to own a server the team did not need before.

Verdict: worth it for the few pages whose facts change too often for any cache, such as plans priced by region or live availability. We would not move a whole marketing site to it to fix one pricing table, since regeneration or prerendered routes cover that for less. Skip it if nobody on the team will own a server that runs on every request.

A Marketing Site That Publishes HTML

Best for: a company whose marketing pages live inside the product’s own app.

The marketing pages leave the app for a system whose normal output is finished HTML, and the app keeps the pages behind the login. Astro prerenders every page at build unless a page opts out, and a CMS such as Webflow publishes HTML from the editor. Our own site runs on Webflow, and this page was HTML before any script on it ran.

Who delivers it: Astro, Next.js static generation, or Webflow and other CMSs that publish HTML.
What it renders: the whole page at publish time, except components built to render only in the browser.
What it costs: Astro and Next.js are free and open source. Webflow’s site plans start at $15 a month, or $25 with its CMS, billed yearly. The real cost is the migration.
Cache and freshness: current at each publish. A CMS publishes on save, a framework on rebuild.
How it fails: the new site re-creates the problem in one component, often the pricing calculator, built to render only in the browser.

Moving the pages takes the problem away at its source, because the marketing site stops depending on the app’s JavaScript. It does not stop a team from bringing the problem back. Astro’s client:only directive, for one, “skips HTML server rendering” by design, and it is the obvious choice for an interactive pricing calculator.

Verdict: the fix that removes the problem instead of working around it. Plan the move if the marketing pages share the product’s app only because they always have. If the site is large and the gap is one page, skip the migration this quarter: fix that page with a cheaper entry above, and move the site at its next redesign.

Which Fix Fits Your Site

Pick by what your team can change this quarter:

App can’t change: a prerendering service, re-rendering as often as the price moves.
CDN is yours: an edge rewrite that writes the missing facts as visible HTML.
A few routes: prerender them at build, with prices from the same source the browser uses.
Already on Next.js: regeneration, fired by the price change.
Facts change hourly: server rendering, on those routes only.
Marketing inside the app: a new site that publishes HTML.

Whichever fix you pick, run the three steps from the top of this page on every page that sells, and again after each price change. Four of these six fixes serve a copy of the page, and a copy is only as current as its last refresh. The check shows what a crawler would get, and your server logs show which crawlers came.

In our SEO work, we audit the pages a buyer’s questions lead to and list what each one lacks before any script runs. The roadmap then names the fix for the partner’s stack and its first step, and the consultant who owns the partnership runs the check again when the change ships and whenever the prices move.

If the pages that sell are reaching AI crawlers half empty, book a call with me, and let’s see whether we could work together.

Ready
To Connect?

Let's Partner