Application frontends
Dapps often utilize a frontend interface to facilitate user interaction with the application through elements such as a dashboard, account page, media player, or other assets that are displayed in the web browser. These assets may be in the form of HTML, JavaScript, CSS, or use frontend frameworks like React.
Application frontends are hosted on ICP using asset canisters. Asset canisters compile frontend assets like CSS and JavaScript into a Wasm module that can be deployed on ICP as a canister.
The terms 'asset canister' and 'frontend canister' are sometimes used interchangeably. Asset canister is typically used to describe the Rust code dfx
uses in the background to compile a project's frontend assets into Wasm. Frontend canister is typically used as a general term to describe a project's frontend.
Application frontends exist in the following forms:
A frontend canister that communicates with backend canister(s) to provide a full-stack dapp.
Backend canisters that communicate with an external frontend application that isn't hosted on ICP. To facilitate this communication, the ICP JavaScript agent can be used. Learn more about the JS request API.
A frontend canister that doesn't communicate with any backend canisters and only provides web assets.
How it works
To host web assets, a canister must implement a method that accepts and consumes an HTTP request, including URL, HTTP method, and header data, then returns an HTTP response, including request status, header data, and body content. This method can return HTML, CSS, and JavaScript content in the HTTP response.
When a canister is configured in a project's dfx.json
file as "type": "asset"
, it already contains the boilerplate code necessary to facilitate these HTTP requests. Once a frontend canister is deployed to the mainnet, the frontend interface can be accessed at http://<canister id>.icp0.io
and http://<canister id>.raw.icp0.io
.
Default settings
By default, all projects created with dfx new
have the option to include a frontend canister in the project. This default frontend canister is written in Rust and may use one of the following frontend frameworks: SvelteKit, React, Vue, or Vanilla JS. The user can choose the framework through the dfx new
interactive prompt in dfx
versions 0.17.0 and newer.
For asset canisters created by dfx new
, the default configuration will include:
"source": [ "src/hello_frontend/dist" ]
: The default subdirectories containing asset files. For assets to be served in the frontend canister, their directories must be included in thissource
list."type": "assets"
: Defines the canister type asassets
.
In dfx.json
, these default settings will resemble the following:
{
"canisters": {
"hello_backend": {
"main": "src/hello_backend/main.mo",
"type": "motoko"
},
"hello_frontend": {
"dependencies": [
"hello_backend"
],
"source": [
"src/hello_frontend/dist"
],
"type": "assets",
"workspace": "hello_frontend"
}
},
"defaults": {
"build": {
"args": "",
"packtool": ""
}
},
"output_env_file": ".env",
"version": 1
}
Motoko asset canisters
The Motoko server and certified HTTP packages can be used for creating asset canisters using the Motoko language.
Rust asset canisters
The ic-asset
Rust crate can be used to develop your project's frontend canister.
Additionally, the ic-http-certification crate can be used to:
Serve certified assets from the same canister as their primary “backend” canister.
Embed assets directly into a canister’s Wasm rather than uploading them at runtime.
Create custom routing logic, such as:
Serving 404 pages in multi-page apps.
Serving multiple frontends from the same canister.
Certify more complex caching or streaming scenarios.
Other third-party options for serving assets include:
TypesScript asset canisters
The Express HTTP server package via Azle can be used to create asset canisters in TypeScript.
Limitations
The frontend canister can host roughly 1GiB in static files. It is recommended that you distribute your files across multiple canisters if the total size of all your assets begins to exceed this amount. Once you exceed this figure, your canister may fail to upgrade.
Application URLs
Frontend canisters serve the application's web assets via a URL that contains the canister's ID. Local deployments use local URLs such as http://127.0.0.1:4943/?canisterId=<canister-id>
, while applications deployed to the mainnet use public URLs containing the canister's ID followed by .ic0.app
, .icp0.io
or raw.icp0.io
.
Raw HTTP interfaces
The raw.icp0.io
domain provides a way to access the raw HTTP interface of that canister. For local deployments that want to simulate the behavior of the raw.icp0.io
domain, you must implement a method in your canister that consumes an HTTP request and outputs an HTTP response. Here is an example written in Motoko:
public shared(msg) func http_request(req: HttpRequest) : async HttpResponse {
{
status = { code = 200; reason = "OK" };
headers = [( "Content-Type", "text/plain" )];
body = Text.encodeUtf8("Hello, World!");
}
};
Dynamic URLs
Dynamic URLs are currently not supported by the default frontend canister.
If a developer would like to support dynamic URLs, custom logic can be implemented using third-party options.
404 pages and aliasing
You can use the ic-http-certification
Rust crate for serving 404 pages or configuring redirects through the crate's wildcard function, such as:
use ic_http_certification::HttpCertificationPath;
let path = HttpCertificationPath::wildcard("/js");
404 pages can only be served as raw content currently.
Learn more about the HTTP certification library.
Resources
Using raw HTML and JavaScript to display a simple HTML entry page.
Vite + React + Motoko template example.
Vite + SvelteKit + Motoko template example.
Deploying an existing Next.js application as a frontend canister.