leed site build [options]leed site build renders your site with Eleventy and, with --serve, keeps rendering it while you edit. This page is the exhaustive one: every flag including the hidden ones, every directory the build touches, and the rule that decides whether a build counts as the one leed site commit is waiting for. For the loop this command sits in — build, look, edit, repeat — read local development instead.
Run it from the project folder (the one holding leed.config.json) or from raw-content/. Both work; nothing else does.
Options
| Flag | Type | Default | Hidden from --help? | What it does |
|---|---|---|---|---|
-d, --debug | boolean | false | no | Skips minification. Output HTML and CSS stay readable, the build is substantially faster, and the build is not recorded |
--serve | boolean | false | no | Builds, then starts a webserver and rebuilds incrementally on every file change |
--port <number> | number | 8080 | no | Port for --serve (and for --wrangler) |
--skip-search | boolean | false | no | Skips the search plugin, so no search index is generated |
--skip-ai | boolean | false | no | Skips the AI support plugin |
--redirects | boolean | false | no | Registers a dev-server redirect handler that honors your built _redirects file locally |
--wrangler | boolean | false | yes | Stages the customer-site worker into .build/ and runs wrangler dev against the output |
--preview | boolean | false | yes | Forces a preview (staging) build. Conflicts with --public |
--public | boolean | false | yes | Forces a public (production) build. Conflicts with --preview |
The global options apply as well: -v and -n <filter> for logging, and --json for a machine-readable result document. See machine-readable output for what the document contains.
What a build produces
| Path | Contents | In git? |
|---|---|---|
raw-content/src | Everything Eleventy reads: your templates, pages, styles and static files | Yes |
raw-content/src/_data | Data files the CMS writes — menus, page types, labels, users, forms | Yes, and read-only to you |
raw-content/.build | The build root. Also holds the generated wrangler.jsonc and worker source on a CI or --wrangler run | No — gitignored |
raw-content/.build/site | The rendered site. Deleted and rebuilt from scratch on every run | No — gitignored |
Before Eleventy runs, the build copies Leed’s shared site content — sitemaps, the leed/ partial tree, the versioned client JavaScript, the Tailwind sources — into src/. Those copies are gitignored and are removed again when the build finishes or when you stop --serve with Ctrl+C. That is why git status stays quiet even though your src/ directory briefly holds hundreds of files that are not yours.
Two retired templates, leed-stubs.hbs and leed-cta.hbs, are deleted from src/ before Eleventy starts if they are found there. They are leftovers from an older release, they are gitignored so nothing shows them to you, and Eleventy would otherwise build them and throw. Pruning them up front is what stops an upgraded repository wedging itself on a file you cannot see.
Preview or public
Every build is one of two variants, and which one you get is normally decided by your git branch rather than by a flag:
- An explicit
--previewor--publicwins. - Otherwise the branch decides —
WORKERS_CI_BRANCHwhen the build is running in CI, the checked-out local branch otherwise. - Anything other than
mainis a preview build.
The CLI says which it chose:
Branch 'staging' detected — preview buildleed site init leaves you on staging, and leed site commit accepts no other branch, so in practice every local build you run is a preview build. What differs between the two is which site the output belongs to: the public variant resolves your live domain and the preview variant resolves your preview domain, and each ships to its own worker. Preview site vs live site covers the distinction once published.
Which builds count
leed site commit refuses when your newest validated edit is newer than your last recorded successful build, and leed site import refuses when your last recorded build is older than the OpenAPI set you generated. Both read the same timestamp, and the rule for writing it is short:
A completed build is recorded as your last successful build when it is not a --debug build, not running under CI, and there is company configuration to record against.
flowchart TD
A["leed site build"] --> B{"--debug?"}
B -- yes --> N["Not recorded<br/>output skipped minification"]
B -- no --> C{"CI set?"}
C -- yes --> N2["Not recorded<br/>nobody reviewed this build"]
C -- no --> D{"company config<br/>available?"}
D -- no --> N3["Not recorded<br/>nowhere to record to"]
D -- yes --> E{"--serve?"}
E -- yes --> R1["Recorded when the<br/>INITIAL build succeeds"]
E -- no --> R2["Recorded when the<br/>build completes"]
R1 --> G["leed site commit<br/>build gate passes"]
R2 --> G
R1 --> H["leed site import<br/>build guard passes"]
R2 --> H
N --> X["commit keeps saying<br/>'You must run the following<br/>command before changes<br/>can committed'"]
N2 --> X
N3 --> X
Two details in that graph are worth stating in words, because both are routinely got wrong.
Under --json the decision is reported directly, as data.recordedAsLastBuild.
Serving
leed site build --serve
leed site build --serve --port 3000--serve does a full build, announces Initial build complete, starting webserver..., then watches src/ and rebuilds incrementally as you save. The site is at http://localhost:8080 unless you moved it with --port.
Ctrl+C shuts it down cleanly. The shutdown handler removes every leed-* file the build copied into src/ — the partial tree, the generated Handlebars templates, the Tailwind sources, the versioned JavaScript, the computed-data file — before exiting, which is why a clean stop leaves git status clean and a kill -9 does not.
For faster iteration, -d is worth the trade. It skips minification, which is a large share of a typical build’s wall time, at the cost of the build not counting toward your next commit. Add --skip-search when you are not touching search, and --skip-ai when you are not touching the AI support surface.
What the build ships that depends on your plan
The two clients are mutually exclusive — only one is copied into the output — and the choice is made before anything is written. Resolution is fail-closed by construction: an unrecognized, empty or missing tier keeps the prebuilt Lunr index rather than shipping a site with a search box and nothing behind it. The build says which it took:
Live docs search unavailable (tier "free"): building the Lunr search indexWhat a reader sees on either path is described in live documentation search, and which tier includes what is owned by feature availability by plan.
Every build also injects a fixed set of third-party libraries into the page head:
| Library | Version | What it powers |
|---|---|---|
| highlight.js | 11.11.2 | Syntax highlighting in code blocks |
| KaTeX | 0.18.4 | Math rendering — script and stylesheet |
| Cloudflare Stream embed SDK | latest | Video embeds |
| Mermaid | 11.17.2 | Diagrams, loaded as an ES module with your theme applied inline before the first render |
| Lunr | 2.3.9 | Querying the prebuilt search index — below Starter only |
These pins move with the CLI release. Run a build and read the emitted <head> if you need to know what your installed version is actually shipping.
Extra highlight.js languages are additive: any language named in your site configuration is looked for at src/static/js/hljs-<language>.js, and added to the head with a content hash if it is there. A language that is configured but missing is logged as a warning and skipped.
Diagnostics
leed site build -v # everything under the leed:* namespaces
leed site build -n "leed:site:*" # one namespace family
DEBUG=Eleventy* leed site build # Eleventy's own internals-v is equivalent to -n leed:*. -n takes a debug-style namespace filter and defaults to *:error, which is why a normal build is quiet.
An inherited DEBUG is merged with what -v and -n ask for, not replaced. And when the merged result enables no Eleventy channel at all, DEBUG is deleted from the environment before the build starts — Eleventy reads the raw variable to decide whether to run character-set detection on every template read, and that detection is slow and its result discarded. The consequence is the one to remember: setting DEBUG alone does not always do what you expect, and DEBUG=Eleventy* is the form that reliably does.
When a build fails, the CLI does not dump the whole transcript. In human mode the failure reaches the global handler and is reported as one line:
The following error occurred: Error - <whatever the failing step said>Under --json, and wherever the extract is reported onward, it is summarized rather than truncated. It scans the output for lines that begin a failure — ✘, [ERROR], Error:, Failed:, fatal:, and git’s ! [rejected] form — carries their indented and hint: continuation lines with them, and caps the extract at 2000 characters. If no marker is found anywhere it falls back to the last 2000 characters, on the reasoning that a failure is usually near the end.
In CI the same extract is posted back to the CMS, prefixed [site-build], before the error is re-thrown — so the build workflow stops waiting rather than hanging on a build that will never report. That prefix is why the same sentence you saw in your terminal turns up on the deployment row, decoded in when a deployment fails.
The hidden flags, and why you will not use them
Three options are registered but hidden from --help, because they exist for Leed’s own build pipeline rather than for your terminal.
--wrangler stages the customer-site worker source into .build/, writes a wrangler.jsonc for it, installs its dependencies there, and spawns bunx wrangler dev against the rendered output on --port. It is how the worker in front of your site is exercised locally, and it needs a Cloudflare toolchain configured to be useful.
--preview and --public force the variant that the branch would otherwise decide. They conflict with each other and Commander rejects the pair. In ordinary use the branch is the right answer and setting the flag by hand only creates a way for your local build and your deployed site to disagree.
Every flag here is one command’s worth of a larger surface; the whole tree with its global options is at the CLI command reference. A recorded build is a precondition for leed site commit and for importing an OpenAPI spec. Changes under tailwind/ recompile through the pipeline described in Tailwind build, and every published deployment repeats this same full build — nothing is incremental once it leaves your machine, as every deployment is a full rebuild explains.