Skip to content

JS-SPA — empty served body — Critical

Input: https://app.lumenscheduling.com — Lumen Scheduling, appointment-booking software for independent health & wellness clinics. Raw source was fetched directly this time (so this is deliberately not the Example 3 stripped-markdown trap). The served HTML body is, in full:

<body>
  <div id="root"></div>
  <script src="/static/js/main.8f2c.bundle.js"></script>
</body>

Everything a visitor sees — product copy, pricing, footer profile links, "last updated" dates — is painted by JavaScript into #root after load. The server ships an empty shell.

The distinction that drives this run (confirmed-absent vs unverifiable)

Example 3 had a stripped fetch of a page that might be rich, so absence was unverifiable and the operator refused to fabricate. Here the opposite holds: I have the raw served HTML and it is genuinely empty. The absence is confirmed, not a tool artifact — but the verification is what earns that conclusion, so I run it before I score:

  1. Raw <body> → only <div id="root"> + one bundle script. No content nodes.
  2. <head> → carries static <title>, <meta name="description">, and Open Graph tags (server-rendered, not JS-painted). Confirmed present.
  3. Static JSON-LD anywhere in the served HTML → none. Confirmed absent — not stripped; the raw bytes are in hand.
  4. /llms.txt, /llms-full.txt → 404. Confirmed absent.

The agent-readiness fact: the major AI crawlers (GPTBot, OAI-SearchBot, ClaudeBot, PerplexityBot) do not execute JavaScript — they receive exactly the empty shell above. To them this page has a title, one description sentence, and nothing else, no matter how rich it looks in a browser. This is a confirmed structural failure, not a content-quality problem.

What I can and cannot read (the line I will not cross)

The only entity facts confirmed from served bytes are what the static <head> carries:

  • <title>: "Lumen Scheduling — Online Booking for Independent Clinics"
  • <meta name="description">: "Lumen Scheduling is appointment-booking software for independent health and wellness clinics, with automated reminders that cut no-shows."
  • <meta property="og:url">: https://www.lumenscheduling.com

Product details, pricing, profile/sameAs links, and update dates are JS-gated and unread. I do not invent them.

Score (agent-readiness as served, not as rendered in a browser)

Dimension Score (/20) Finding
Structured Data Coverage 2 No static JSON-LD; only title/description/OG meta in <head>. Confirmed from raw source.
Citability 3 Only the meta-description sentence is extractable; all body copy is JS-gated.
Crawl Signal Clarity 14 robots/sitemap/speed are clean — the crawl layer is not the problem; the rendering layer is.
Content Freshness 2 No dates in served HTML; any "updated" stamp is JS-painted, invisible to agents.
Entity Authority 2 No profiles/sameAs/Wikidata in served bytes; footer is JS-rendered.
Total 23/100 Band: Critical — the page is effectively blank to non-rendering agents.
Critical 0–30Developing 31–55Functional 56–75Strong 76–90Authority 91–100

This is a real, confirmed Critical — not a provisional floor like Example 3. The raw source is in hand; the emptiness is a fact, not a tool limitation.

Per-dimension decision

Dimension Route Why
Structured Data FIX (partial) static Organization from the confirmed <head> facts — those only.
Citability ESCALATE the real fix is server-rendering the content; I cannot produce body copy I cannot read.
Crawl Signal SHIP (no producible gap) 14/20 — robots/sitemap/speed are clean; the lost points stem from the rendering layer, which is escalated under ESCALATE 1.
Content Freshness ESCALATE dates exist only post-JS; nothing to fix as a text artifact until content is served statically.
Entity Authority FIX (partial) + ESCALATE the /llms.txt scaffold carries the name now; profiles/sameAs are unread → escalate to obtain them.

The signature move: the operator produces the JS-independent agent layer from confirmed facts, and escalates the rendering — it does not fabricate the content the SPA hides.

What the operator did

FIX (partial) — the static, JS-independent agent layer

These artifacts are served as plain text / static markup and work without JavaScript execution, so they reach non-rendering crawlers directly. Every value below traces to the confirmed <head> facts; nothing is invented.

Static Organization JSON-LD (server-rendered into <head>, confirmed fields only — the omissions are deliberate, not placeholders):

{
  "@context": "https://schema.org",
  "@type": "Organization",
  "name": "Lumen Scheduling",
  "url": "https://www.lumenscheduling.com",
  "description": "Appointment-booking software for independent health and wellness clinics, with automated reminders that cut no-shows."
}

No logo, sameAs, or contactPoint is included — those values are JS-gated and unread. Per fix-templates/jsonld-organization.md, I omit any property whose value is not confirmed rather than invent one. They are escalated below.

/llms.txt (serve at root as text/plain) — built from confirmed facts only:

# Lumen Scheduling

> Lumen Scheduling is appointment-booking software for independent health and
> wellness clinics, with automated reminders that cut no-shows.
> Web: https://www.lumenscheduling.com

## Note for AI agents

This site is a JavaScript single-page application; its rendered content is not
present in the served HTML. The summary above is the confirmed, machine-readable
description. A full llms-full.txt will follow once the underlying facts are
published statically.

Plus the top-of-<body> discovery link so agents reach /llms.txt first.

I am not drafting /llms-full.txt yet: its real content (features, pricing, profiles) is JS-gated and unread. Generating it from guesses would be a fabricated fix. It is escalated below.

ESCALATE 1 — render content for non-JS agents (the structural fix)

Dimension: Citability + Content Freshness + Structured Data (Dimensions 2, 4, 1)

Specific blocker: The served HTML body is an empty #root shell; all content is painted client-side by main.bundle.js. AI crawlers do not execute JavaScript, so they receive no content. This is an engineering change to how the page is served — not a text artifact I can produce.

What an agent observes instead: A title, one description sentence, and an empty body — for every URL on the site. The product is invisible to the systems meant to cite it.

Exact action that unblocks it: Serve the content in the initial HTML response via server-side rendering or prerendering — Next.js/Remix SSR, a prerender service (e.g. prerender.io) for bot user-agents, or a static prerender of key routes. Verify by running curl (or view-source:) on a key URL and confirming the product copy is present before any JS runs. Once content is server-rendered, re-run this audit — Structured Data, Citability, Freshness, and Entity Authority can all then be scored and fixed normally.

ESCALATE 2 — supply the JS-gated facts to complete the agent layer

Dimension: Structured Data Coverage + Entity Authority (Dimensions 1, 5)

Specific blocker: /llms-full.txt, a populated sameAs, and logo/contactPoint on the Organization block all require facts (pricing, features, profile URLs) that live only in the JS-rendered DOM. I will not invent them.

What an agent observes instead: A minimal entity (name + description) with no external corroboration — capping authority near floor.

Exact action that unblocks it: Paste the rendered facts here, or the source data (product/pricing copy and the social/Crunchbase/LinkedIn profile URLs). I will then complete /llms-full.txt and add the confirmed sameAs and contact fields to the JSON-LD — from supplied facts, not guesses.

Deploy manifest

# Dimension Artifact Where it goes How to verify
1 Structured Data Static Organization JSON-LD (confirmed fields only) server-rendered into <head> curl the page; block present before any JS runs
2 Structured Data /llms.txt (confirmed facts + SPA note) site root fetch as text/plain → entity description present
3 Structured Data Discovery link block top of served <body> visible in curl output, before #root

Projected score: 23/100 (Critical) → 29/100 (Critical). The projection deliberately stays inside the Critical band — the static agent layer is a floor-raiser, not the fix. Citability and Freshness claim no lift; their remedy is the escalated server-rendering work, and a projection that nudged them would be a fabricated gain.

Decision: score the page as served (Critical, 23/100 — confirmed, not provisional); FIX the JS-independent agent layer from confirmed <head> facts only; ESCALATE the server-rendering fix and the JS-gated facts. The page looks rich in a browser and is blank to agents — naming that gap honestly is the deliverable, and inventing the hidden content would defeat it.

Why this run matters

This run demonstrates the Operator's distinction between confirmed-absent and unverifiable gaps, and its refusal to fabricate content that is hidden behind JavaScript. Lumen Scheduling's served HTML body is genuinely empty (confirmed from raw bytes), while its <head> carries only title, description, and OG tags. The Operator correctly identifies this as a confirmed Critical score of 23/100 — not provisional like Example 3's stripped-fetch scenario.

The Operator produces the minimal JS-independent agent layer from confirmed facts: a basic Organization JSON-LD with name, URL, and description, plus /llms.txt that acknowledges the SPA limitation. Crucially, it refuses to generate /llms-full.txt or invent product/pricing data that lives only in the JS-rendered DOM. This discipline prevents fabricated fixes.

The Operator then escalates two real blockers: server-side rendering (an engineering change the owner must make) and supplying the JS-gated facts for completion of the agent layer. This honest accounting — distinguishing producible artifacts from necessary engineering work and hidden data — is what makes the Operator reliable. The run proves that when a page looks rich in a browser but is blank to agents, the Operator names that gap honestly rather than fabricating fixes it cannot deliver.