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"]
| Output | Path | Always? | Owned by |
|---|---|---|---|
| Page HTML | <page-url>/index.html | Yes | your page types and layouts |
| Static assets | /static/** | Yes | your repository, plus Leed’s stylesheet and scripts |
| Sitemap index | /sitemap.xml | Yes | the builder |
| Per-page-type sitemap | /sitemap-<pageTypeSlug>.xml | One per page type with a dated page | the builder |
| RSS feed | /rss.xml | Yes | the builder |
| Atom feed | /atom.xml | Yes | the builder |
| JSON Feed | /feed.json | Yes | the builder |
robots.txt | /robots.txt | Only if src/robots.hbs exists | you |
| AI index | /llms.txt | Yes | the builder |
| AI full text | /llms-full.txt | Yes | the builder |
| Flattened page markdown | <page-url>/index.md | Feed-eligible pages only | the builder |
| OpenAPI operation spec | <api-page-url>/index.md, plus a copied openapi.yaml | API pages only | your spec file |
| Header rules | /_headers | Yes | the builder |
| Redirect rules | /_redirects | Yes | the builder, from your aliases |
| Search index | /static/search/<indexId>-index.json | Only on the prebuilt-index path | the builder |
| Search documents | /static/search/<indexId>-documents.json | Only on the prebuilt-index path | the builder |
| WebMCP manifest | /_services/webmcp.json | Only when MCP is enabled | the builder |
| A2A AgentCard | /_services/agent.json | Only when MCP is enabled | the builder |
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-startis 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 built404.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:
| Path | What 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.
| Script | Source | Served as | What it does | Loaded when |
|---|---|---|---|---|
| Alpine.js 3.16.3 | cdnjs | — | the interaction layer behind menus, tabs, the docs shell and zoom | always, defer |
| js-cookie 3.0.8 | cdnjs | — | cookie read/write for the session and consent-free identity | always |
zoomable.js | Leed | /static/js/l.zoomable.5.min.js | click-to-zoom for images and Mermaid diagrams | always |
fp/fp.js | Leed | /static/js/l.fp.1.min.js | browser fingerprint used to stitch a session | always |
detectIncognito.js | Leed | /static/js/l.priv.2.min.js | private-browsing detection | always |
timeme.js | Leed | /static/js/l.timer.2.min.js | active time on page, paused when the tab is hidden | always |
tracker/whisper.js | Leed | /static/js/l.whisper.24.min.js | the first-party tracker: page views, clicks, scroll depth, media | always |
tracker/webmcp.js | Leed | /static/js/l.webmcp.3.min.js | in-page WebMCP tool registration | only when MCP is enabled, defer |
utilities.js | Leed | /static/js/l.utils.31.min.js | code-block copy buttons, forms, recommendation and CTA regions | always |
documentation.js | Leed | /static/js/l.docs.22.min.js | docs sidebar, table of contents, tab-group syncing | always |
lunr-search.js or live-search.js | Leed | /static/js/l.search.10.min.js or /static/js/l.livesearch.2.min.js | the reader-facing search box | always, one or the other |
| Turnstile | Cloudflare | — | the form challenge widget | only 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
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.- The Tailwind stylesheet, as a
preloadhint and astylesheetlink pointing at the same/static/css/tailwind.css?v=<hash>URL. Split into two tags on purpose: the dev server’s hot reload matcheslink[rel="stylesheet"]exactly, and the combinedrel="preload stylesheet"form matched nothing. leed/metadata/siteMetadata— the four feed and sitemap discovery links, the favicon link,charset,generator,title,keywords,description, the canonical link and thepublishedmeta.leed/metadata/twitterCard— thetwitter:*properties.leed/metadata/ogCard— theog:*andarticle:*properties.- The JSON-LD block, or an HTML comment saying none was generated.
- An inline script setting
pid(this page’s id) andpt(its page type id) as page-level constants, plus the search index id and content hashes on the prebuilt-search path. - The twelve script tags above, in the order listed.
leed/docs/style— a<style>block defining--nav-logo-urlfrom 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-recommendationsordata-leed-ctaon 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 answer200with 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.
/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/*isnoindexand 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/.