Reflectionbot runs JavaScript only when the HTML is empty
Table of contents
JavaScript execution has two depths sorts crawlers by which of our two beacon channels they fire. Reflectionbot was not in it: until 2026-10-25 our classifier filed it under other-bot. Reflection AI says the crawler visits public web pages to help build and improve its models. Re-classified, it turns out to run JavaScript on one kind of page only.
What it rendered
Between 2026-09-18 and 2026-10-25, Reflectionbot made 69 visits to our test cells. A visit is one page, fetched once or twice within a few seconds.
| Page | Visits | Visits followed by a beacon |
|---|---|---|
| Client-side rendered: the HTML is an empty shell until the React app runs | 13 | 12 |
Server-rendered, main content needs JavaScript (click-reveal, js-fetched, mixed) |
19 | 0 |
| Server-rendered or static, main content in the HTML (the other five patterns) | 37 | 0 |
How it renders
Two fetches, then the React app. On 12 of the 13 client-side-rendered visits, two different addresses fetched the page, within a second of each other. A beacon followed 0.4 to 2.6 seconds after the second fetch (median 0.8 seconds). The one visit without a beacon was also the only one with a single fetch. Every server-rendered visit was a single fetch. The second fetch looks like a renderer loading the page, and Reflectionbot seems to send a page to it only when the HTML it got is empty.
Only the React app. All 12 beacons came through the React client component and none through the inline <script> at the end of <body>. The inline script is on these pages too: browsers, headless Chrome and ShapBot fired it there. That is how Applebot and Googlebot’s renderer behave, the bundle-evaluator tier in the two-depths post. Reflectionbot is narrower, because it skips server-rendered pages even when their content is missing from the HTML. ShapBot, the other renderer we found after that post’s window, rendered 10 of 32 such fetches and ran both channels.
It rendered every client-side-rendered page it fetched, 7 of our 10.
Who sent it
Reflection publishes its crawler’s IP ranges at reflection.ai/bot/ips.json and says its addresses resolve to hostnames under reflection.ai. Every Reflectionbot request we logged came from 49 addresses in 16.216.88.0/24, which sits inside a range on that list (the file is dated 2026-09-22). On 2026-10-25, each of the 49 resolved to a reflectionbot-*.reflection.ai hostname that pointed back to the same address. No other crawler’s user agent came from them. The tracker does not check Reflection’s list yet, so this check was done by hand.
11 of the 12 beacons came from addresses that also fetched pages as Reflectionbot; the last came from an address that made no other request to check. Only 2 beacons left from an address that fetched the same visit’s page. The rest used a third address from the same pool, so the renderer does not keep one address per page.
What this means for site owners
If a page reaches Reflectionbot as an empty client-side shell, it runs the React app, and whatever the app renders can end up in what Reflection collects for its models. If the page is server-rendered, Reflectionbot reads the HTML and nothing else: content behind a click, or fetched by JavaScript after load, does not reach it.
The usual advice treats client-side rendering as the risk. For Reflectionbot it is the other way round: a site that renders on the server and adds content with JavaScript loses that content, while a fully client-side site does not. Content in the HTML is still the only kind that reaches every crawler on our test bed.
What this does not prove
- This is an exploratory result. Its data comes after the planned Phase 1 window, which ended on 2026-08-07, so it is a lead for a new test, not a test of our pre-registered hypotheses (methodology).
- The sample is small: 69 visits, 12 beacons, 7 rendered pages, one domain, five weeks.
- The two-fetch reading is an inference from timing and addresses. We see two fetches and a beacon, not the crawler’s pipeline.
- A beacon proves the JavaScript ran. It does not prove Reflection kept the content that JavaScript produced.
- The rule may change. A crawler that decides per page what to render can change that decision at any time; this is what it did between 18 September and 25 October.
Method notes
Requests are deduplicated: every Cloudflare-log row counts, plus a middleware row only when Cloudflare has no row for the same address, path and user agent within 3 seconds. That leaves 81 test-cell fetches, grouped into 69 visits: fetches of the same cell less than 10 seconds apart. A visit counts as rendered when a beacon for the same cell arrives within an hour; all of them arrived within 3 seconds. Reflectionbot also made 30 robots.txt requests, 7 beacon calls and 5 other requests in the same period. Beacon rows carry a hashed IP but no address or ASN, so each beacon address was checked through the HTTP requests it made, by its hashed IP. The cell groups are the ones used for ShapBot in the two-depths post. The data is the tracker export of 2026-10-25, 08:35 UTC.
Data availability: methodology, tracker code, classifier and beacon source are in the repository at github.com/Qbeczek1/jsseo-dev, private for now; access on request at [email protected]. Live dashboard at /dashboard/.
Bias disclosure: I run JS SEO Lab as an independent technical SEO research project, and do paid technical SEO and AI-visibility audits through FratreSEO. No framework vendor, crawler vendor, search engine, or AI company funds this work.