Skip to content

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.

<your zone>/<project code>/ the viewer runtime (your build)
<your zone>/<project code>/p/ your project: manifest, GLBs, documents, assets

The 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.

What a publish does The project in your workspace is built and staged, only changed files are uploaded in a fixed order with index.html last, and after a cache purge the project is live at its URL. Workspace projects/My Line/ project.json models/*.glb your machine Build + stage viewer build (skipped with --no-build) project into p/ Upload only what changed, in this order: 1 p/** project 2 runtime 3 catalogs 4 index.html last Live cache purged https://host/ <code>/ link to share The page that loads everything goes up last, so a visitor never gets a half-uploaded version.
Terminal window
npm run publish:project -- --list # what this workspace has
npm run publish:project -- --project "My Line" --dry-run # every step, uploads nothing
npm run publish:project -- --project "My Line" # build, stage, upload

A 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.

A new GLB out of Unity does not change the viewer, so nothing needs rebuilding:

Terminal window
npm run publish:project -- --project "My Line" --no-build

The 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.

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.

One zone per partner realvirtual's CDN account holds one storage zone per partner and one for realvirtual itself. Your zone password opens only your zone; the other zones stay out of reach. realvirtual CDN account Your zone rv-partner-your-name <code>/ viewer <code>/p/ project your-name.web.realvirtual.io Another partner rv-partner-other own projects, own password other.web.realvirtual.io realvirtual web.realvirtual.io public demo and hosted customer projects Your zone password in your .env, never in git write + delete no access
A zone password is the smallest credential the CDN issues, so every partner gets a zone of their own.

Three values in a gitignored .env, and publishing works:

Terminal window
BUNNY_STORAGE_ZONE=rv-partner-your-name
BUNNY_STORAGE_KEY=your-zone-password
RV_PUBLIC_BASE_URL=https://your-name.web.realvirtual.io

Your 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.

The same command, pointed at your own account. Every account-specific value comes from the environment, nothing is hardcoded:

Terminal window
BUNNY_STORAGE_ZONE=your-zone
BUNNY_STORAGE_KEY=your-zone-password
BUNNY_ACCOUNT_KEY=your-account-api-key # optional, only to purge the CDN cache
BUNNY_PULL_ZONE_ID=your-pull-zone-id # optional, only to purge the CDN cache
RV_PUBLIC_BASE_URL=https://your-hostname

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:

RequirementWhy
SPA fallback to index.htmlthe 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

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:

Terminal window
RV_DEPLOY_PASSWORD=the-password-your-users-type
npm 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.

The key is split in two The password travels over one channel and the link with its #k fragment over another. Only the browser that has both halves can decrypt the model. Password by phone, chat, in person Link by e-mail, ends in #k= Browser joins both halves into the key Model opens GLBs decrypted (AES-256-GCM) One half alone opens nothing.
Publish commandOwn CDN zone from realvirtualPrivate models and own branding
Community (AGPL)build and host yourselfnoAGPL obligations apply: see licensing the viewer
Commercialnpm run publish:project in the delivered repositoryon requestyes

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.