Deploying
Build, shrink, ship.
Build
flutter build web --wasm --release --base-href /kite/ --dart-define=KITE_DEMO=true
node tools/strip-fonts.mjs build/web
python3 tools/subset-lucide.py build/web
--base-href must match the path you serve from, or every asset
404s. Omit it when serving from a domain root.
The two font tools
They are not optional; together they take the payload from about 2.2 MB to 1.6 MB.
strip-fonts.mjs
shadcn_ui depends on lucide_icons_flutter, which
declares seven font families — a base face plus six
variable-weight instances. Flutter web preloads every family named in
FontManifest.json, so all seven download on first paint even
though nothing references the numbered weights.
Saves about 1.15 MB.
subset-lucide.py
Then subsets the base face to the glyphs actually drawn.
--tree-shake-icons only manages ~12% here, because shadcn reaches
its icons through wrappers that defeat Flutter's static analysis — but it
only ever draws eleven of them.
1,776 glyphs to 12. 732 KB to 2 KB. Needs pip install fonttools.
Keep the base Lucide family. Dropping it entirely leaves tofu boxes where shadcn's own chevrons and checkmarks should be. The tools keep exactly one family; CI fails the build if that count changes in either direction.
Measuring
Do not measure by summing build/web. It ships six
mutually-exclusive renderer variants plus .symbols debug files —
the directory total is roughly 5.5× what a browser actually downloads.
npm i playwright && npx playwright install chromium
node tools/measure-bundle.mjs build/web "label" throttle
That serves the build, loads it in headless Chromium and reports real encoded transfer plus FCP, throttled to Fast 4G with 4× CPU.
Any static host
Serve build/web. The only requirement is the
index.html fallback for unknown paths — Cloudflare Pages, Netlify
and Vercel do it by default. Locally:
node tools/serve.mjs build/web 8722
python3 -m http.server will not do: it serves .wasm
as octet-stream and the app refuses to boot.
Cloudflare R2
How this project deploys. rclone sync deletes remote files missing
from the source — dry-run first, every time.
rclone sync build/web/ r2pro:dashboardpack-demos/kite/ --dry-run
rclone sync build/web/ r2pro:dashboardpack-demos/kite/
rclone check build/web/ r2pro:dashboardpack-demos/kite/ # expect "0 differences"
ssh hetzner sudo cf-purge-url https://demo.dashboardpack.com/kite/
Cloudflare caches per colo, so replacing a file without purging means visitors
keep the old one. See docs/DEPLOY.md in the repo for the docs
bucket and the SEO worker's behaviour.
Removing the sidebar promo
Kite is free, and the sidebar carries a card pointing at the paid DashboardPack templates. It is one flag to remove from your own build:
flutter build web --wasm --release --dart-define=KITE_PROMO=false
To take it out permanently, delete
lib/shared/layout/promo_card.dart and its two references in
app_shell.dart. Nothing else depends on it.
CI
Every push runs eleven jobs:
| Job | Checks |
|---|---|
analyze | dart format --set-exit-if-changed . then flutter analyze --fatal-infos |
test | 26 tests with coverage |
architecture | No shadcn_ui import outside lib/kite_ui/ |
fonts | Exactly one Lucide family survives the build |
build ×6 | web-wasm, web-js, iOS, Android, macOS, Windows |
size | App payload under budget |
A weekly scheduled run catches Flutter SDK drift before a user reports it.