Publishing a Project
Deployment options describes the four ways to run realvirtual. This page is about the first of them in practice: taking a project out of a workspace and putting it on the web, repeatably, with one command.
Commercial customers and partners who receive realvirtual WEB as source get that command in their repository: npm run publish:project.
What a publish does
Section titled “What a publish does”<your zone>/<project code>/ the viewer runtime (your build)<your zone>/<project code>/p/ your project: manifest, GLBs, documents, assetsThe project code is the 32-character identifier in projects/<name>/project.json. It is random, so a published project sits on an unguessable URL: there is no login in front of it, and the code is what keeps it from being found. The folder is owned in full by the publish, so a document you removed from the project is removed from the CDN too.
npm run publish:project -- --list # what this workspace hasnpm run publish:project -- --project "My Line" --dry-run # every step, uploads nothingnpm run publish:project -- --project "My Line" # build, stage, uploadA second publish diffs against what is already there and uploads only what changed. --dry-run is worth making a habit: it validates credentials, resolves the project and prints the target URL without touching the CDN.
The usual update: only the model changed
Section titled “The usual update: only the model changed”A new GLB out of Unity does not change the viewer, so nothing needs rebuilding:
npm run publish:project -- --project "My Line" --no-buildThe build is skipped and only the model travels. Project documents are compared by
sha256 from project.json, not by file size, so a re-export that happens to have the
identical byte count is still recognised as changed — while the unchanged runtime is not
uploaded again. Stage, one manifest read, one upload, cache purge: the new model is live
within seconds of the purge rather than minutes.
Publish once without --no-build after a git pull that brought a new release, so the
viewer on the CDN matches the source you merged.
Three places to publish to
Section titled “Three places to publish to”A zone realvirtual provisions for you
Section titled “A zone realvirtual provisions for you”If realvirtual hosts your publishing, you get one storage zone of your own inside realvirtual’s CDN account, a hostname such as your-name.web.realvirtual.io, and the password of that zone. The password grants write and delete inside your zone only — no other content on the CDN is reachable with it, and nobody else’s key reaches yours.
Three values in a gitignored .env, and publishing works:
BUNNY_STORAGE_ZONE=rv-partner-your-nameBUNNY_STORAGE_KEY=your-zone-passwordRV_PUBLIC_BASE_URL=https://your-name.web.realvirtual.ioYour repository keeps receiving realvirtual releases; git pull brings them in and the next publish ships exactly the version you merged. Monthly traffic is capped per zone, so a publishing arrangement cannot turn into an open-ended bill on either side.
Your own CDN account
Section titled “Your own CDN account”The same command, pointed at your own account. Every account-specific value comes from the environment, nothing is hardcoded:
BUNNY_STORAGE_ZONE=your-zoneBUNNY_STORAGE_KEY=your-zone-passwordBUNNY_ACCOUNT_KEY=your-account-api-key # optional, only to purge the CDN cacheBUNNY_PULL_ZONE_ID=your-pull-zone-id # optional, only to purge the CDN cacheRV_PUBLIC_BASE_URL=https://your-hostnameAny other static host
Section titled “Any other static host”A publish is a folder of static files, so nginx, Apache, IIS, S3 with CloudFront, Netlify, Vercel, Cloudflare Pages or an intranet share all work. Build with npm run build, serve dist/, and place the project folder under p/ beside it with settings.json naming it ("projectUrl": "p/").
Three server-side details decide whether it works at all:
| Requirement | Why |
|---|---|
SPA fallback to index.html | the viewer is a single-page app, so a deep link must not answer 404 |
MIME types for .glb (model/gltf-binary) and .wasm (application/wasm) | the loader and the geometry kernel reject a wrong content type |
http(s), never file:// | model loading is blocked at origin null, and camera and XR need a secure context |
Password protection
Section titled “Password protection”An unguessable URL is not always enough. A project can be published encrypted — every GLB is wrapped with AES-256-GCM and the viewer asks for a password before it shows anything:
RV_DEPLOY_PASSWORD=the-password-your-users-typenpm run publish:project -- --project "My Line"The key is split in two. One half is that password; the other is a fragment the publish prints at the end of the URL as #k=…. Both are needed, which is why the link and the password should travel through different channels. Keep the printed fragment (RV_DEPLOY_FRAGMENT) if a later publish has to keep an already distributed link working.
Who can do this
Section titled “Who can do this”| Publish command | Own CDN zone from realvirtual | Private models and own branding | |
|---|---|---|---|
| Community (AGPL) | build and host yourself | no | AGPL obligations apply: see licensing the viewer |
| Commercial | npm run publish:project in the delivered repository | on request | yes |
The command is the same code in both cases; what differs is the licence under which the result may be kept private, and whether realvirtual operates the zone for you.
See also
Section titled “See also”- Deployment options — the four ways to run realvirtual
- Licensing the viewer — what AGPL asks, and when a commercial licence is needed
- Opening a twin — what your users see on the link