---
title: Build a static site
description: "Produce a deployable `output/` directory by running the same app in build mode and reading the `BuildReport` for failures."
canonical_url: https://usepennington.net/how-to/deployment/static-build/
sidecar_url: https://usepennington.net/how-to/deployment/static-build.md
content_hash: sha256:8db1b5510f278af1b634c5d690dfe53575f4d38157d2c5db7ad123b896147ae8
tokens: 837
uid: how-to.deployment.static-build
reading_time_minutes: 2
---

Guides
# Build a static site

Produce a deployable `output/` directory by running the same app in build mode and reading the `BuildReport` for failures.

 
To turn a working Pennington site into a folder of static HTML for a static host, run the app in build mode. For why the same `Program.cs` works in both dev and build, see [Dev mode and build mode share one code path](https://usepennington.net/explanation/core/dev-vs-build.md); for platform-specific upload steps, see [Deploy to GitHub Pages](https://usepennington.net/how-to/deployment/github-pages.md); for sub-path sites, see [Host under a sub-path (base URL)](https://usepennington.net/how-to/deployment/base-url.md).

 
## Before you begin

 
 - A working Pennington site that serves under `dotnet run` (see [Create your first Pennington site](https://usepennington.net/tutorials/getting-started/first-site.md) if not).
 - The host composes `RunOrBuildAsync` directly or via `RunDocSiteAsync` / `RunBlogSiteAsync` (most apps do — confirm `Program.cs` ends with one of those calls).
 - A writable local directory — the build deletes and re-creates `output/` by default.
 
 
---

 
## Steps

 
**Invoke the build verb**

 
Pass `build` as the first argument to `dotnet run`. The argument is parsed into `OutputOptions` via `FromArgs`; without it, the app starts as a dev server instead. Three argument shapes are supported:

 
```bash
# defaults: BaseUrl = "/", OutputDirectory = "output"
dotnet run -- build
  
# positional: base URL, then output dir
dotnet run -- build /my-site dist
  
# named flags (order-independent, preferred for scripts)
dotnet run -- build --base-url=/my-site --output=dist
```

 
See [CLI and build arguments](https://usepennington.net/reference/host/cli.md) for the full grammar.

 
**Read the `BuildReport` printed to stdout**

 
When the crawl finishes, `RunOrBuildAsync` writes a human-readable report and exits with a non-zero code when the build failed; see [Pennington.Generation.BuildReport](https://usepennington.net/reference/api/build-report.md) for the fields and the exact failure conditions. Fix the routes it lists before deploying.

 
For custom CI presentation (a GitHub Actions summary, a Slack message), use `BuildHost.PrintBuildReport` in `examples/SubPathDeployableExample/BuildHost.cs` as a starting point.

 
---

 
## Verify

 
 - `dotnet run -- build` exits `0` and the stdout report opens with `Build Complete — N pages in Xs` followed by `N pages generated`, with no `ERRORS` or `WARNINGS` section. A broken internal link is reported as a warning, so an empty `WARNINGS` section means every internal link resolved.
 - `output/index.html` and `output/404.html` both exist — open `index.html` in a browser to spot-check the rendered output.
 
 
## Related

 
 - Reference: [CLI and build arguments](https://usepennington.net/reference/host/cli.md)
 - Reference: [Build report fields](https://usepennington.net/reference/api/build-report.md)
 - Background: [Dev mode and build mode share one code path](https://usepennington.net/explanation/core/dev-vs-build.md)
 
 
[Previous
                
                Add tags to the document head](https://usepennington.net/how-to/response-pipeline/head-contributor.md)[Next
                    
                Deploy to GitHub Pages](https://usepennington.net/how-to/deployment/github-pages.md)