Reachability Pillar · Implementation Guide
Put critical content directly in the HTML, so agents read it on first fetch — not after JavaScript renders.
Whether critical content — hours, eligibility, contact, schema, calls-to-action — renders in the HTML the server sends, before any JavaScript runs in the browser.
There are two phases to how a page loads:
Humans with a browser see both phases as one page. Many AI agents only see phase one.
LLM-based agents (ChatGPT, Perplexity, AI Overviews, voice assistants) typically fetch raw HTML and do not execute JavaScript. If hours, eligibility, or services are JS-injected, agents see none of it.
People on slow connections, in low-bandwidth areas, or using older devices see the initial HTML first. JS-injected content arrives late or not at all.
Agents cross-check JSON-LD schema against visible content as an anti-cloaking signal. Schema that claims hours while the visible page shows a loading state breaks that trust — the schema gets discounted or ignored.
These are the content types that most often fail R2 in audits:
Hours of operation
Hours fetched from a CMS API on page load. Agent asked "is the food bank open today?" cannot answer.
Eligibility / cost
Income limits or service criteria loaded from a content widget. Agent helping someone in crisis cannot confirm eligibility.
Availability / inventory
Appointment slots or shelter bed counts fetched at render time. Agent cannot route someone to an open spot.
Reviews / ratings
Review widget injected client-side. AggregateRating schema declares a count that cannot be verified against visible content.
Use the View Source method to confirm what is and is not in the initial HTML:
Why "View Source," not "Inspect": DevTools Inspect shows the rendered DOM after JavaScript runs — what humans see. View Source shows the raw response — what agents see. They diverge whenever JavaScript injects content.
Whatever framework powers the site, the fix is the same: fetch data on the server and interpolate it into the HTML before the response leaves the server.
The anti-pattern is fetching critical data inside useEffect, onMounted, or any browser-lifecycle hook. That code runs in the browser, after JavaScript loads — well after the agent has read the HTML and moved on.
Server-side fetching only works if the data is available server-side. Common architecture problems:
Final check before sign-off:
Where data comes from determines whether it can be in the initial HTML at all. The recommended pattern for any site pulling data from third-party APIs (Google Places, Yelp Fusion, third-party CMSes):
Third-party API → first-party database (sync) → page render
The third-party API is a sync source, not a render-time dependency. A scheduled job (nightly cron) updates the first-party database. The page renders from the first-party data only — fast, ToS-clean, predictable.
Google Places data is licensed by Google, not owned by the site operator. Their Terms of Service impose caching limits that collide with standard SSR / SSG patterns:
Reviews and ratings are the pinch point. They cannot legally be cached, so they cannot be in static HTML, so they fail R2 unless fetched fresh server-side per request (expensive) or collected first-party (the recommended path).
When working with a development team or vendor, these surface the data-flow decisions early:
A community food bank publishes hours on its website. The hours come from a content management system that renders client-side via a JavaScript widget.
❌ Failing — Hours load via JavaScript
What an agent sees on first fetch (View Source)
<!-- What an agent sees on first fetch -->
<body>
<div id="app">
<!-- empty: hours load from API after page mount -->
</div>
<script src="/app.js"></script>
</body>
Agent output when asked "is the food bank open today?":
"I could not find current hours for this organization."
✓ Passing — Hours in initial HTML
What an agent sees on first fetch (View Source)
<!-- What an agent sees on first fetch -->
<body>
<main>
<h1>Community Food Bank — Springfield</h1>
<section aria-labelledby="hours-heading">
<h2 id="hours-heading">Hours</h2>
<p>Mon-Fri 9am-6pm</p>
<p>Saturday 9am-2pm</p>
</section>
<a href="tel:+12175550100">Call: (217) 555-0100</a>
</main>
<script type="application/ld+json">
{ "@context": "https://schema.org",
"@type": "LocalBusiness",
"name": "Community Food Bank",
"openingHoursSpecification": [ ... ] }
</script>
</body>
Agent output when asked "is the food bank open today?":
"Yes — Community Food Bank is open today 9am-6pm. You can call (217) 555-0100."
The food bank's CMS already had the hours. The fix was an architectural one, not a content one:
openingHoursSpecification block, also populated from the same CMS dataGooglebot does render JavaScript — but on a delay (sometimes days) and only when it has rendering budget. JS-rendered content is second-class. More importantly, LLM-based agents (ChatGPT, Perplexity, Claude, voice assistants) typically do not run JavaScript at all. R2 is about agent reach, not just Google ranking.
Reviews are the architectural pinch point. Three options: (1) collect first-party reviews and render server-side — the recommended path, cleanest across ToS, MX, and brand trust; (2) fetch the third-party reviews server-side per request and render with attribution — expensive but possible; (3) keep the JS widget and accept that reviews will not be in initial HTML — fails R2.
Not if it is architected well. Cached SSR (ISR, request-level caching) gives you the speed of static with the freshness of dynamic. The sites that struggle with SSR performance are usually fetching live from third-party APIs per request — the fix is to cache or sync, not to abandon SSR.
Interactive features can and should be JavaScript-powered. R2 is about critical declarative content — who, what, where, when, how to contact. A search input itself can be a hydrated client component, but the catalog it searches over should be rendered in initial HTML where possible.
Yes — as long as the critical content streams as HTML, not as JavaScript that then renders HTML. React Server Components and similar streaming approaches send progressively-complete HTML to the browser. Agents see the streamed HTML as it arrives. Streaming a "loading..." placeholder followed by client-side hydration of real content is the failure mode.
Three quick tests: (1) curl https://the-site.com — what comes back is what an agent sees; (2) browser View Source then Cmd+F for critical content; (3) DevTools → Disable JavaScript → reload — what remains visible is what most agents will read.
Use this to confirm R2 is implemented before moving on.