What Your Visitors Get

A deployment publishes two things to your domain: a complete static site, and a small Cloudflare Worker sitting in front of it. Everything a visitor, a crawler or an AI client can reach comes from one of those two. This page is the inventory — what exists, where it lives, and which page owns the control that changes it.

Nothing here is gated. Every file, script and behavior on this page ships on every plan, including Free. The only two conditional pieces are the agent manifests and which search client your build ships, and both are called out where they appear.

Deployment, build and page type are used as exact terms throughout this category — they are the same terms the Core Concepts page defines.

What one deployment writes

The build output falls into four groups that do not overlap.

flowchart TD
  D["Deployment"] --> H["Page HTML<br/>one index.html per page URL"]
  D --> S["/static/**<br/>stylesheet · Leed JS · your images · fonts"]
  D --> M["Machine-readable files<br/>sitemaps · feeds · robots.txt · llms.txt"]
  D --> E["Edge configuration<br/>_headers · _redirects"]
  S -. "a search index is bound" .-> SI["/static/search/id-index.json<br/>/static/search/id-documents.json"]
  M --> MD["page-url/index.md<br/>one per feed-eligible page"]
  E -. "MCP is enabled" .-> SV["_services/webmcp.json<br/>_services/agent.json"]
OutputPathAlways?Owned by
Page HTML<page-url>/index.htmlYesyour page types and layouts
Static assets/static/**Yesyour repository, plus Leed’s stylesheet and scripts
Sitemap index/sitemap.xmlYesthe builder
Per-page-type sitemap/sitemap-<pageTypeSlug>.xmlOne per page type with a dated pagethe builder
RSS feed/rss.xmlYesthe builder
Atom feed/atom.xmlYesthe builder
JSON Feed/feed.jsonYesthe builder
robots.txt/robots.txtOnly if src/robots.hbs existsyou
AI index/llms.txtYesthe builder
AI full text/llms-full.txtYesthe builder
Flattened page markdown<page-url>/index.mdFeed-eligible pages onlythe builder
OpenAPI operation spec<api-page-url>/index.md, plus a copied openapi.yamlAPI pages onlyyour spec file
Header rules/_headersYesthe builder
Redirect rules/_redirectsYesthe builder, from your aliases
Search index/static/search/<indexId>-index.jsonOnly on the prebuilt-index paththe builder
Search documents/static/search/<indexId>-documents.jsonOnly on the prebuilt-index paththe builder
WebMCP manifest/_services/webmcp.jsonOnly when MCP is enabledthe builder
A2A AgentCard/_services/agent.jsonOnly when MCP is enabledthe builder
A terminal listing of a built site directory, showing index.html, static, sitemap.xml and a per-page-type sitemap, rss.xml, atom.xml, feed.json, robots.txt, llms.txt, llms-full.txt, _headers, _redirects and 404.html

Files Leed adds to your repository only for the build

At the start of a build, Leed copies its own templates into your src/ directory: the leed-*.hbs templates that produce the sitemaps, feeds, _headers and _redirects; the _includes/leed/** partial tree; tailwind-*.css; the versioned l.*.min.js scripts; and _data/eleventyComputed.js. When the build finishes, the cleanup step deletes every one of them again.

They are gitignored, so they never appear in git status, and editing one is pointless — the next build overwrites it and then removes it. Anything you want to change about that output belongs in your own templates and layouts instead.

How a request is served

The generated Worker sits in front of a Cloudflare static-asset binding pointed at the built site. Two asset-binding settings shape every URL on your domain:

  • html_handling: "force-trailing-slash" — every page URL ends in /. A request for /docs/quick-start is redirected to /docs/quick-start/, so both work and only one is canonical.
  • not_found_handling: "404-page" — an unmatched URL is answered with your own built 404.html, not a Cloudflare error page.

Static subresources — stylesheets, scripts, images, fonts — stream straight from the asset binding and never execute Worker code. The Worker owns a fixed set of paths and nothing else:

PathWhat it handles
/api/*form submissions, event tracking, session init, recommendations, CTAs, live docs search
/f/*file and document downloads
/s/*short links
/e/*email open, click and opt-out tracking
/mcp, /mcp/*the Docs MCP endpoint
/.well-known/*MCP OAuth discovery, plus the WebMCP manifest and AgentCard

On a preview site the Worker deliberately refuses some of these: POST /api/event and POST /api/clerk log the body and return 204 without recording anything, and every MCP path returns 404. Preview traffic is never counted and preview forms never create a contact — one of several ways a preview site differs from a live one.

Your 404 page is a page you own

The 404.html served for an unmatched URL is built from src/404.hbs in your repository. It carries "permalink": "404.html" and "pageId": "404" in its front matter, and it renders through your own layout — so it inherits your header, footer, fonts and colors like any other page, and you edit it the same way you edit anything else in your site repository.

Two exclusions follow from that 404 page id: the 404 is skipped when the search index is built, and 404 views are excluded from the distinct-pages-per-session analytics roll-up, so a burst of bad links does not inflate your engagement numbers.

The scripts every page loads

Leed writes a fixed block of script tags into every page head — eleven slots, always in this order. Nine are unconditional. One is the WebMCP registrar, present only when MCP is on. One is a choice between two search clients. And a twelfth tag appears below the block when a form on your site uses Turnstile.

ScriptSourceServed asWhat it doesLoaded when
Alpine.js 3.16.3cdnjs—the interaction layer behind menus, tabs, the docs shell and zoomalways, defer
js-cookie 3.0.8cdnjs—cookie read/write for the session and consent-free identityalways
zoomable.jsLeed/static/js/l.zoomable.5.min.jsclick-to-zoom for images and Mermaid diagramsalways
fp/fp.jsLeed/static/js/l.fp.1.min.jsbrowser fingerprint used to stitch a sessionalways
detectIncognito.jsLeed/static/js/l.priv.2.min.jsprivate-browsing detectionalways
timeme.jsLeed/static/js/l.timer.2.min.jsactive time on page, paused when the tab is hiddenalways
tracker/whisper.jsLeed/static/js/l.whisper.24.min.jsthe first-party tracker: page views, clicks, scroll depth, mediaalways
tracker/webmcp.jsLeed/static/js/l.webmcp.3.min.jsin-page WebMCP tool registrationonly when MCP is enabled, defer
utilities.jsLeed/static/js/l.utils.31.min.jscode-block copy buttons, forms, recommendation and CTA regionsalways
documentation.jsLeed/static/js/l.docs.22.min.jsdocs sidebar, table of contents, tab-group syncingalways
lunr-search.js or live-search.jsLeed/static/js/l.search.10.min.js or /static/js/l.livesearch.2.min.jsthe reader-facing search boxalways, one or the other
TurnstileCloudflare—the form challenge widgetonly when a Turnstile public key is set

Version numbers are part of the filename, which is how Leed cache-busts them: a new release changes the URL, so no visitor is ever served a stale script. The numbers above are the ones shipped with site-management 4.102.0 and will move with later releases; the file paths and the order will not.

The tracker in that list is the whole of Leed’s analytics — there is no third-party tag, and because it posts to your own domain it is not blocked the way third-party trackers routinely are. How Leed Tracks Visitors covers what it records and what it does not.

What Leed writes into every <head>

Leed’s head block always begins with a hook you control and ends with the docs logo style block. Between them the order is fixed:

The full head emission order, slot by slot
  1. header-includes.hbs, if the file exists in your _includes/ directory. This is a plain file-existence check — no CMS flag, no feature to switch on, no tier gate. Create the file and its contents are rendered first on every page of your site, documentation included.
  2. The Tailwind stylesheet, as a preload hint and a stylesheet link pointing at the same /static/css/tailwind.css?v=<hash> URL. Split into two tags on purpose: the dev server’s hot reload matches link[rel="stylesheet"] exactly, and the combined rel="preload stylesheet" form matched nothing.
  3. leed/metadata/siteMetadata — the four feed and sitemap discovery links, the favicon link, charset, generator, title, keywords, description, the canonical link and the published meta.
  4. leed/metadata/twitterCard — the twitter:* properties.
  5. leed/metadata/ogCard — the og:* and article:* properties.
  6. The JSON-LD block, or an HTML comment saying none was generated.
  7. An inline script setting pid (this page’s id) and pt (its page type id) as page-level constants, plus the search index id and content hashes on the prebuilt-search path.
  8. The twelve script tags above, in the order listed.
  9. leed/docs/style — a <style> block defining --nav-logo-url from your documentation logo pair.

Two consequences are worth knowing before you write your own template.

Leed does not emit a <title> element. It emits <meta name="title">, which is not the same thing and is not what a browser tab or a search result shows. The <title> belongs to your own master template, and the conventional form is {{ title }} | {{ siteTitle }}.

header-includes.hbs is the only hook that reaches every page. Documentation pages render through the docs shell rather than your site template, so anything declared in site-template.hbs reaches your marketing pages alone. Web fonts, verification tags, a web manifest, an icon set — if it must be true on every page of the site, it goes in that file. Customizing the <head> covers it in detail.

Behaviors readers get without you building them

None of these need a plugin, a script tag or a setting. They are consequences of the scripts already in the head.

  • Copy buttons on every code block, added at load time.
  • Click-to-zoom on images and on Mermaid diagrams, which open into a full-screen dialog — so a diagram can carry more detail than fits the reading column.
  • Synced tab groups: a reader who picks Python in one tabbed example gets Python in every other container bound to the same group, on that page and on the next one.
  • Rendered math and Mermaid diagrams, from the same Leed Markdown you write in the editor.
  • Form submission to /api/clerk, with the fill recorded against a contact.
  • Short links at /s/<code>, gated file downloads at /f/<assetId>/<fileName>, and email tracking links at /e/*.
  • Recommendation and CTA regions. Put data-leed-recommendations or data-leed-cta on an element in your template and the client fills it after the page session initializes. Note that both are Growth features and both degrade silently: below Growth the endpoints answer 200 with an empty result, so the region simply stays empty rather than showing an error. Recommendations and Dynamic CTAs each cover what fills them.

The documentation chrome — sidebar, breadcrumbs, table of contents, previous/next — is a larger thing than any of the above and has its own page.

The machine-readable copy of your content

Every build writes a plain-text rendition of your site for AI clients and crawlers, in three shapes.

/llms.txt — an index. The site title as an H1, the site description as a blockquote, then one ## <page type name> heading per page type (with the page type’s description as a blockquote under it, if it has one), and beneath each heading one bullet per page:

# Leed

> Leed brings together your marketing workflows, website, and customer journey into a seamless, accelerated experience informed by AI.

## Blog

> Product news, releases and field notes.

- [Publishing your first page](https://leed.ai/blog/publishing-your-first-page/index.md): A walk through the Deploy screen, from pending change to live URL.
- [What a full rebuild buys you](https://leed.ai/blog/what-a-full-rebuild-buys-you/index.md): Why one edit rebuilds the whole site.

## Documentation

- [What Your Visitors Get](https://leed.ai/docs/published-site/what-your-visitors-get/index.md): Everything a deployment puts on your domain.

Every link in llms.txt points at the flattened markdown, not the HTML — the page URL with index.md appended.

A browser showing the llms.txt file for leed.ai, with the site title as a heading, the site description as a blockquote, a page-type heading and several bulleted page entries

/llms-full.txt — the same headings and the same grouping, but with each page’s body inlined as plain markdown between <!-- Start page (n) source: <url> --> and <!-- End page (n) --> comments. Each page’s headings are pushed down two levels so they nest correctly under the page-type heading.

<page-url>/index.md — one flattened file per page: an H1 of the title, then the body with Leed Markdown extensions reduced to plain markdown and embeds replaced by HTML comments. An API page gets a bonus: its OpenAPI operation is appended as a fenced yaml block under a ## OpenAPI heading, because Eleventy consumes that front matter to render the human page and strips it from the HTML. This is the only place the structured contract survives into the published site.

Readers’ AI clients can also talk to your site directly rather than reading these files, through the Docs MCP or the Site AI Agent.

What is not on your published site

Some absences surprise people, so they are worth naming.

  • No cookie-consent banner and no analytics opt-out control. The tracker is first-party and ships without either; if your jurisdiction requires one, it is yours to add.
  • Pages flagged preview-only emit no file on a live build. This is product behavior you may run into on an inherited site rather than something to reach for — there is no CMS control that sets it, and none of these documentation pages uses it.
  • A page type marked Do Not Render emits no file at all, on any build. The pages still exist in the CMS and are still reachable through the API; the builder just does not write HTML for them.
  • /stubs/* is noindex and carries no canonical link.
  • Nothing from the CMS itself. No admin route, no editor bundle, no draft content. The published site is output only.

Where the two conditional pieces come from

The agent manifests. When MCP is enabled for your site, the build writes _services/webmcp.json and _services/agent.json, and the Worker serves them at /.well-known/webmcp and /.well-known/agent.json. They are files rather than Worker variables because the WebMCP manifest exceeds Cloudflare’s text-binding limit. With MCP off, the build writes neither and both paths answer 404.

Which search client ships. Every site gets the same search box; what sits behind it depends on your plan. Most builds ship the prebuilt Lunr index described on Site Search for Readers; on some plans the build swaps in a different client that queries a live endpoint instead, which Live Documentation Search documents.

Once you know what exists, the rest of this category takes each piece in turn: feeds, sitemaps and robots.txt, the social card markup in that head block, the search box and the index behind it, and the caching and security headers that decide how long a browser keeps any of it — including the year-long immutable cache on everything under /static/.

ESC