Deploy a static site
Deploy a static site (a built frontend, docs, or any folder of files) to a
canister on the Internet Computer that serves it over
HTTP with response certification. You point
icp-cli at your build directory and run
icp deploy: it uploads the files, and the canister certifies and serves them.
Certification is what makes this different from a plain web host: every response the canister returns carries a cryptographic proof, and a verifying HTTP gateway checks that proof before handing the response to the browser. Visitors get content the canister has provably committed to, with nothing in between able to tamper with it in transit.
The canister certifies every response, always. Whether that proof gets checked
depends on the gateway a visitor goes through, so link to one that verifies and treat
raw URLs as a debugging tool.
You configure it through icp-cli using a
recipe, @dfinity/static-site, which pins a matched pair of the canister and the
sync plugin that uploads to it. Both are released from
dfinity/certified-assets, which is also
where the Candid interface and the build artifacts live. Most sites need nothing more
than this page.
Get started
Section titled “Get started”You will need icp-cli, and somewhere to deploy:
a local network for a first look (icp network start -d), or a configured environment
for a live one. Both are covered in the icp-cli docs.
1. Reference the recipe in your icp.yaml
Section titled “1. Reference the recipe in your icp.yaml”canisters: - name: frontend recipe: type: "@dfinity/static-site@<version>" configuration: dir: dist # the directory of files to serveReplace <version> with a released version (e.g. v0.3.3); see the
available versions.
Pick the version here: because the recipe pins a matched canister + plugin pair,
there is no separate canister version to choose.
2. Deploy
Section titled “2. Deploy”icp deployThis installs the canister, then runs the sync plugin to upload and certify every
file in dir. Re-running icp deploy syncs again: the plugin diffs your directory
against the canister and uploads only what changed.
Open the URL icp deploy prints and your site is there, certified.
Configuration
Section titled “Configuration”The recipe takes four configuration fields:
| Field | Type | Required | Description |
|---|---|---|---|
dir | string | Yes | The single directory of built files to serve. The canister owns its whole URL space, so this is one directory, not a list. |
build | array | No | Shell commands run before the canister exists to produce dir (e.g. npm run build). No canister IDs are available yet. |
presync | array | No | Shell commands run at sync time, after the canister exists, with the deployed canister IDs exported as env vars (ICP_CLI_CID, ICP_CLI_CID_<NAME>, ICP_CLI_NETWORK, ICP_CLI_ENVIRONMENT). Use it to build a frontend that must bake in a canister ID. |
metadata | array | No | name/value pairs baked into the canister wasm via ic-wasm. |
A fuller example with a build step and metadata:
canisters: - name: frontend recipe: type: "@dfinity/static-site@<version>" configuration: build: - npm ci - npm run build dir: dist metadata: - name: "frontend:framework" value: "react"
metadatais the only field that needsic-wasm. It ships withicp-cli, so if you installed the CLI you already have it.
Building against canister IDs (presync vs build)
Section titled “Building against canister IDs (presync vs build)”build runs before the canister is created, so it can’t know any canister IDs.
When a client-side app needs to embed the ID of a canister it will call, build it
in presync instead. That runs at sync time, once the IDs exist, and exports
them to your commands:
canisters: - name: frontend recipe: type: "@dfinity/static-site@<version>" configuration: dir: dist presync: - npm ci # $ICP_CLI_CID_BACKEND is the `backend` canister's principal. - VITE_CANISTER_ID_BACKEND=$ICP_CLI_CID_BACKEND npm run buildThe variables available to presync: ICP_CLI_CID (this canister’s principal),
ICP_CLI_CID_<NAME> (each project canister’s principal, with the name upper-cased
and non-alphanumerics replaced by _, e.g. backend → ICP_CLI_CID_BACKEND),
ICP_CLI_NETWORK, and ICP_CLI_ENVIRONMENT.
What you get automatically
Section titled “What you get automatically”You don’t have to configure any of these; they’re on by default:
- Response certification. Every response carries a proof, checked by a verifying gateway.
- Clean URLs.
/aboutserves/about.html,/blog/serves/blog/index.html, with redirects that keep one canonical URL per page. - Compression. Text, JS, JSON, SVG, and
wasm are stored gzip- and Brotli-compressed and negotiated per request via
Accept-Encoding. - ETag /
304 Not Modified. Each asset gets a content-hash ETag, so unchanged files aren’t re-downloaded. - A default
404page. A built-in, certified fallback you can replace by adding your own/404.html.
Customize further
Section titled “Customize further”When you need finer control, each topic has its own page:
- Routing & clean URLs. How request paths map to files, trailing slashes, and 404 handling.
- Single-page apps. The one
_redirectsrule a client-side-routed app needs, so every URL loads (and reloads) your shell. - Redirects & rewrites. The
_redirectsfile: send/oldto/new, serve one file at another path, set custom error pages. - Custom headers. The
_headersfile: cache-control, a Content Security Policy, and other security headers. - Site files & conventions. What gets uploaded, the special
_redirects/_headersfiles, excluded files, and custom domains. - Access protection. Put a login screen in front of a private/preview app with revocable, expiring access tokens.
- Verifying contents. The canister’s state hash: prove to a third party that it serves exactly a known build, from source.
Curious how it works underneath? See Under the hood.