Appearance
Dashboard & install service
The web/ package is deployed as one Cloudflare Pages project — for this repository, react-native-deploy.pages.dev:
| Path | What it is |
|---|---|
/deploy/dashboard/ | The dashboard: project overview, builds list with filters, build detail with phase timings, and the Install modal with its QR code |
/deploy/docs/ | This documentation |
/install/<dispatch-id> | The public install page for one internal build |
/install/<dispatch-id>/manifest.plist | The iOS over-the-air manifest |
/install/<dispatch-id>/app.ipa, app.apk | The binary, streamed from R2 |
/api/uploads/… | Upload API for the build workflow, protected by a bearer token |
Using the dashboard
Open /deploy/dashboard/, choose Connect a repository and enter owner/name. A public repository needs no token. For a private one, or to avoid GitHub's anonymous limit of 60 requests per hour, add a read-only token. It stays in your browser's local storage and is only sent to api.github.com.
Until you connect, the dashboard shows clearly-labelled demo data.
Deploy
You need a Cloudflare account, logged in with npx wrangler login. Pick a project name; it becomes <name>.pages.dev. The commands below use react-native-deploy.
sh
cd web
npm install
npx wrangler r2 bucket create rn-eas-builds # once
npx wrangler pages project create react-native-deploy \
--production-branch master --force # once (see below)
# once: the token the build workflow uploads with
openssl rand -hex 32 | tr -d '\n' > upload_token
npx wrangler pages secret put UPLOAD_TOKEN --project-name react-native-deploy < upload_token
npm run deploy # builds dashboard + docs, then `wrangler pages deploy`Then connect your app repository to it:
sh
gh variable set EAS_INSTALL_URL --body "https://react-native-deploy.pages.dev"
gh secret set EAS_INSTALL_TOKEN < upload_token
rm upload_tokenThree things that catch people out
- Secrets apply to the next deployment.
pages secret putdoesn't change the running deployment; runnpm run deployagain after setting or rotatingUPLOAD_TOKEN. Until then, uploads get401. --forceonproject create. Recent wrangler versions turnpages project createinto a Workers deployment (a*.workers.devURL) unless you pass--force. Onlyproject createneeds it.- Accounts allow 100 Pages projects.
project createfails with code8000027at the limit; delete an unused project first. Projects with custom domains must have those removed before they can be deleted.
The configuration is web/wrangler.jsonc: the build output directory and an R2 binding named BUILDS. npm run deploy writes .pages-dist/_worker.js, which re-exports web/worker/index.js: Pages "advanced mode", so every request reaches the Worker code. It routes /install and /api, serves the dashboard and docs from the static assets, and gives the dashboard's client-side routes a fallback.
How uploads work
The workflow's Publish for install step uploads in 50 MiB parts with R2 multipart, so large IPAs fit under the Workers request-size limit. meta.json is written last, and the install page appears only once it exists, so a page never points at a half-uploaded binary.
The upload API only accepts builds/<uuid>/meta.json, app.ipa or app.apk, so a leaked token cannot overwrite anything else in the bucket. The token check is constant-time.
Retention
R2 objects don't expire on their own, unlike GitHub artifacts (90 days). Add an R2 lifecycle rule on builds/ if you want them to.