Skip to content

Commit 4dbc721

Browse files
authored
docs: frontend frameworks guide (#89)
## Summary - Asset canister ic_env cookie for runtime canister ID injection - React/Vite: icp.yaml, vite.config.ts with @icp-sdk/bindgen plugin, SPA routing - Vue/Vite, Svelte/SvelteKit, Next.js (output: export), Unity WebGL, Godot HTML5, static sites - Per-framework build output directory configuration ## Sync recommendation `informed by dfinity/icp-cli-templates — hello-world; dfinity/examples — hosting/react, unity-webgl-template, godot-html5-template`
1 parent d58e848 commit 4dbc721

1 file changed

Lines changed: 368 additions & 12 deletions

File tree

Lines changed: 368 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,377 @@
11
---
22
title: "Frontend Frameworks"
3-
description: "Integrate React, Svelte, Vue, Unity, and other frameworks with ICP canisters"
3+
description: "Integrate React, Vue, Svelte, Next.js, and game engines with ICP canisters using the asset canister and icp-cli"
44
sidebar:
55
order: 4
66
---
77

8-
TODO: Write content for this page.
8+
ICP hosts frontend applications as asset canisters — static files (HTML, CSS, JavaScript) deployed onchain and served with certified responses. Any framework that can produce a static build output works: React, Vue, Svelte, Next.js, and even game engines like Unity WebGL and Godot.
99

10-
<!-- Content Brief -->
11-
Integrate popular frontend frameworks with ICP. Cover Vite plugin setup for React/Svelte/Vue, agent configuration with @icp-sdk/core, auth setup with @icp-sdk/auth, and hosting configuration. Also cover game engines (Unity WebGL, Godot HTML5) for onchain games. Show framework-specific starter templates.
10+
This guide shows you how to configure your framework's build pipeline, wire up the ICP JavaScript SDK, and deploy to an asset canister.
1211

13-
<!-- Source Material -->
14-
- Portal: building-apps/frontends/existing-frontend.mdx
15-
- JS SDK: @icp-sdk/core (https://js.icp.build/core), @icp-sdk/auth (https://js.icp.build/auth)
16-
- Examples: hosting/react, svelte/svelte-motoko-starter, svelte/sveltekit-starter, hosting/godot-html5-template, hosting/unity-webgl-template
12+
## Prerequisites
1713

18-
<!-- Cross-Links -->
19-
- guides/frontends/asset-canister -- deployment target
20-
- guides/authentication/internet-identity -- auth setup per framework
21-
- getting-started/project-structure -- Vite plugin in hello-world template
14+
- [icp-cli](https://cli.internetcomputer.org/) installed: `npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm`
15+
- A backend canister deployed (or a static-only site with no backend)
16+
- Familiarity with [asset canisters](asset-canister.md)
17+
18+
## The deployment model
19+
20+
Every frontend framework integration follows the same pattern:
21+
22+
1. Configure `icp.yaml` to point at your framework's build output directory
23+
2. Optionally add a Vite plugin (`@icp-sdk/bindgen`) to generate typed canister bindings at build time
24+
3. Use `@icp-sdk/core` in your app to read canister IDs and the root key at runtime from the `ic_env` cookie served by the asset canister
25+
4. Deploy with `icp deploy`
26+
27+
The asset canister injects an `ic_env` cookie into every HTML response. This cookie carries the root key and any `PUBLIC_CANISTER_ID:<name>` environment variables you set — so your frontend never needs canister IDs baked into the build artifact.
28+
29+
## React with Vite
30+
31+
The [hello-world template](../../../getting-started/project-structure.md) uses React with Vite. It demonstrates the full stack: backend canister, auto-generated TypeScript bindings, and a React frontend that reads canister IDs at runtime.
32+
33+
### icp.yaml
34+
35+
```yaml
36+
canisters:
37+
- name: frontend
38+
recipe:
39+
type: "@dfinity/asset-canister@v2.1.0"
40+
configuration:
41+
build:
42+
- npm install
43+
- npm run generate --prefix app
44+
- npm run build
45+
dir: app/dist
46+
```
47+
48+
The `build` array runs before the asset canister uploads files. `npm run generate` regenerates TypeScript bindings from the backend `.did` file; `npm run build` runs Vite.
49+
50+
### vite.config.ts
51+
52+
```typescript
53+
import { defineConfig } from "vite";
54+
import react from "@vitejs/plugin-react";
55+
import { icpBindgen } from "@icp-sdk/bindgen/plugins/vite";
56+
57+
// Change these values to match your local replica.
58+
// The `icp network start` command prints the root key;
59+
// the `icp deploy` command prints the backend canister ID.
60+
const IC_ROOT_KEY_HEX = "<IC_ROOT_KEY_HEX>";
61+
const BACKEND_CANISTER_ID = "<BACKEND_CANISTER_ID>";
62+
63+
export default defineConfig({
64+
plugins: [
65+
react(),
66+
icpBindgen({
67+
didFile: "../../backend/backend.did",
68+
outDir: "./src/backend/api",
69+
}),
70+
],
71+
server: {
72+
headers: {
73+
// Simulate the ic_env cookie that the asset canister injects in production.
74+
"Set-Cookie": `ic_env=${encodeURIComponent(
75+
`ic_root_key=${IC_ROOT_KEY_HEX}&PUBLIC_CANISTER_ID:backend=${BACKEND_CANISTER_ID}`
76+
)}; SameSite=Lax;`,
77+
},
78+
proxy: {
79+
"/api": {
80+
target: "http://127.0.0.1:8000",
81+
changeOrigin: true,
82+
},
83+
},
84+
},
85+
});
86+
```
87+
88+
The `icpBindgen` Vite plugin regenerates TypeScript bindings whenever the `.did` file changes during development.
89+
90+
The `server.headers` block simulates the `ic_env` cookie during `vite dev`. In production, the asset canister injects this cookie automatically — your code reads it without any build-time environment variables.
91+
92+
Install the required packages:
93+
94+
```bash
95+
npm install @icp-sdk/core
96+
npm install -D @icp-sdk/bindgen @vitejs/plugin-react
97+
```
98+
99+
### Reading canister IDs and the root key
100+
101+
```typescript
102+
import { getCanisterEnv } from "@icp-sdk/core/agent/canister-env";
103+
import { createActor } from "./backend/api/backend";
104+
105+
interface CanisterEnv {
106+
readonly "PUBLIC_CANISTER_ID:backend": string;
107+
}
108+
109+
// Reads from the ic_env cookie injected by the asset canister (production)
110+
// or the Set-Cookie header set in vite.config.ts (development).
111+
const canisterEnv = getCanisterEnv<CanisterEnv>();
112+
const canisterId = canisterEnv["PUBLIC_CANISTER_ID:backend"];
113+
114+
const actor = createActor(canisterId, {
115+
agentOptions: {
116+
// In production, use the root key from the ic_env cookie.
117+
// In development (import.meta.env.DEV), fetch it from the local replica.
118+
rootKey: !import.meta.env.DEV ? canisterEnv.IC_ROOT_KEY : undefined,
119+
shouldFetchRootKey: import.meta.env.DEV,
120+
},
121+
});
122+
```
123+
124+
The `createActor` function is generated by `@icp-sdk/bindgen` from your `.did` file. It returns a fully typed actor. See the [JS SDK docs](https://js.icp.build) for the full `HttpAgent` and `Actor` API.
125+
126+
### SPA routing
127+
128+
React apps use client-side routing. Without a fallback, refreshing on `/about` returns a 404 from the asset canister. Add a `.ic-assets.json5` file to your `public/` directory so it ends up in `dist/`:
129+
130+
```json5
131+
[
132+
{
133+
// Apply security policy to all paths. Two separate rules are needed because
134+
// `security_policy` and `enable_aliasing` interact: the aliasing rule must
135+
// be evaluated last so it only applies to paths with no matching file.
136+
"match": "**/*",
137+
"security_policy": "standard",
138+
"allow_raw_access": false
139+
},
140+
{
141+
// SPA fallback: serve index.html for any path that has no matching file.
142+
"match": "**/*",
143+
"enable_aliasing": true
144+
}
145+
]
146+
```
147+
148+
See [asset canister configuration](asset-canister.md) for the full `.ic-assets.json5` reference.
149+
150+
## Vue with Vite
151+
152+
Vue and Vite follow the same pattern as React. The only difference is the Vite plugin:
153+
154+
```bash
155+
npm install @icp-sdk/core
156+
npm install -D @icp-sdk/bindgen @vitejs/plugin-vue
157+
```
158+
159+
```typescript
160+
// vite.config.ts
161+
import { defineConfig } from "vite";
162+
import vue from "@vitejs/plugin-vue";
163+
import { icpBindgen } from "@icp-sdk/bindgen/plugins/vite";
164+
165+
export default defineConfig({
166+
plugins: [
167+
vue(),
168+
icpBindgen({
169+
didFile: "../backend/backend.did",
170+
outDir: "./src/backend/api",
171+
}),
172+
],
173+
server: {
174+
proxy: {
175+
"/api": { target: "http://127.0.0.1:8000", changeOrigin: true },
176+
},
177+
},
178+
});
179+
```
180+
181+
If your Vue app calls `getCanisterEnv()` to read canister IDs, add the same `server.headers` block from the React section to simulate the `ic_env` cookie during local development — otherwise `getCanisterEnv()` will throw because the cookie is absent. The `icp.yaml` configuration is the same as the React example — point `dir` at `dist`.
182+
183+
## Authentication
184+
185+
Authentication with Internet Identity is framework-agnostic — the `@icp-sdk/auth` package works the same way in React, Vue, Svelte, and Next.js static export mode. See the [Internet Identity guide](../authentication/internet-identity.md) for integration steps.
186+
187+
## Svelte and SvelteKit
188+
189+
For SvelteKit, you must configure static export mode before deploying — the asset canister serves static files and does not support server-side rendering.
190+
191+
### SvelteKit with static adapter
192+
193+
```bash
194+
npm install -D @sveltejs/adapter-static
195+
```
196+
197+
```javascript
198+
// svelte.config.js
199+
import adapter from "@sveltejs/adapter-static";
200+
201+
export default {
202+
kit: {
203+
adapter: adapter({
204+
pages: "build",
205+
assets: "build",
206+
fallback: "index.html", // enables SPA mode
207+
}),
208+
},
209+
};
210+
```
211+
212+
```yaml
213+
# icp.yaml
214+
canisters:
215+
- name: frontend
216+
recipe:
217+
type: "@dfinity/asset-canister@v2.1.0"
218+
configuration:
219+
build:
220+
- npm install
221+
- npm run build
222+
dir: build
223+
```
224+
225+
For Svelte (without SvelteKit), Vite is the standard build tool. The `vite.config.js` setup is the same as Vue — swap `@vitejs/plugin-vue` for `@sveltejs/vite-plugin-svelte`.
226+
227+
## Next.js
228+
229+
Next.js requires static export mode. Server components, API routes, and `getServerSideProps` are not supported in an asset canister — the canister only serves static files.
230+
231+
Enable static export in your Next.js config:
232+
233+
```javascript
234+
// next.config.js
235+
const nextConfig = {
236+
output: "export",
237+
};
238+
239+
module.exports = nextConfig;
240+
```
241+
242+
This outputs static files to the `out/` directory.
243+
244+
```yaml
245+
# icp.yaml
246+
canisters:
247+
- name: frontend
248+
recipe:
249+
type: "@dfinity/asset-canister@v2.1.0"
250+
configuration:
251+
build:
252+
- npm install
253+
- npm run build
254+
dir: out
255+
```
256+
257+
:::note
258+
Only Next.js pages that can be statically generated are compatible with ICP. Any page using dynamic server-side features (server actions, route handlers, middleware) will not work in a static export.
259+
:::
260+
261+
## Game engines
262+
263+
Game engines that export HTML5 or WebGL builds can be deployed as asset canisters without a backend canister. The build output is pre-generated in the export step of the engine — `icp.yaml` just copies the files into place.
264+
265+
### Unity WebGL
266+
267+
Export your game from Unity Editor: **File → Build Settings → WebGL → Build**. This creates a folder with `index.html`, `Build/`, and `TemplateData/`.
268+
269+
```yaml
270+
# icp.yaml
271+
canisters:
272+
- name: unity_webgl_template_assets
273+
recipe:
274+
type: "@dfinity/asset-canister@v2.1.0"
275+
configuration:
276+
dir: dist
277+
build:
278+
- mkdir -p dist
279+
- cp -r src/unity_webgl_template_assets/assets/* dist/
280+
- cp -r src/unity_webgl_template_assets/src/* dist/
281+
```
282+
283+
The `build` commands copy the Unity WebGL export into `dist/`. Point `dir` at that directory.
284+
285+
See the [Unity WebGL example](https://github.com/dfinity/examples/tree/master/hosting/unity-webgl-template) for the full project structure.
286+
287+
### Godot HTML5
288+
289+
Export your game from Godot Editor: **Project → Export → HTML5 → Export Project**. This creates an `index.html` and supporting files.
290+
291+
```yaml
292+
# icp.yaml
293+
canisters:
294+
- name: godot_html5_assets
295+
recipe:
296+
type: "@dfinity/asset-canister@v2.1.0"
297+
configuration:
298+
dir: dist
299+
build:
300+
- mkdir -p dist
301+
- cp -r src/godot_html5_assets/assets/* dist/
302+
- cp -r src/godot_html5_assets/src/* dist/
303+
```
304+
305+
See the [Godot HTML5 example](https://github.com/dfinity/examples/tree/master/hosting/godot-html5-template) for the full project structure.
306+
307+
### Deploying game builds
308+
309+
Both game engine templates deploy with standard icp-cli commands:
310+
311+
```bash
312+
# Start local network
313+
icp network start -d
314+
315+
# Deploy the asset canister
316+
icp deploy
317+
318+
# Access your game locally
319+
# http://<canister-id>.localhost:8000
320+
```
321+
322+
No Vite plugin or JS SDK integration is needed for game builds — the asset canister serves the pre-built HTML and JavaScript files directly.
323+
324+
## Static sites
325+
326+
For sites with no backend canister (portfolios, landing pages, documentation):
327+
328+
```yaml
329+
# icp.yaml
330+
canisters:
331+
- name: frontend
332+
recipe:
333+
type: "@dfinity/asset-canister@v2.1.0"
334+
configuration:
335+
build:
336+
- npm install
337+
- npm run build
338+
dir: dist
339+
```
340+
341+
No JS SDK integration is needed. The asset canister serves your files, and you can configure headers and caching in `.ic-assets.json5`.
342+
343+
See the [React hosting example](https://github.com/dfinity/examples/tree/master/hosting/react) for a minimal static frontend without a backend canister.
344+
345+
## Deploy
346+
347+
```bash
348+
# Start local network
349+
icp network start -d
350+
351+
# Deploy all canisters
352+
icp deploy
353+
354+
# Deploy to mainnet
355+
icp deploy -e ic
356+
```
357+
358+
After deployment, the asset canister URL depends on your canister ID:
359+
360+
| Environment | URL |
361+
|-------------|-----|
362+
| Local | `http://<canister-id>.localhost:8000` |
363+
| Mainnet | `https://<canister-id>.ic0.app` |
364+
365+
Get your canister ID with:
366+
367+
```bash
368+
icp canister settings show frontend -i
369+
```
370+
371+
## Next steps
372+
373+
- [Asset canister](asset-canister.md) — configure headers, caching, and SPA routing in `.ic-assets.json5`
374+
- [Internet Identity](../authentication/internet-identity.md) — add authentication to your frontend
375+
- [Project structure](../../getting-started/project-structure.md) — explore the hello-world template with React, Vite, and a Motoko backend
376+
377+
<!-- Upstream: informed by dfinity/icskills — skills/asset-canister/SKILL.md; dfinity/icp-cli-templates — hello-world/frontend/; dfinity/examples — hosting/react, hosting/unity-webgl-template, hosting/godot-html5-template, svelte/svelte-motoko-starter; dfinity/icp-js-sdk-docs — core/canister-environment.mdx, bindgen/plugins/vite/index.md, auth/quick-start.md -->

0 commit comments

Comments
 (0)