Publishing a Project
Deployment options describes the four ways to run realvirtual. This page covers the first of them in practice: publishing a project from a workspace to the web with one repeatable command.
Commercial customers and partners who receive realvirtual 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 compares files with the existing deployment and uploads only changes. Use --dry-run first to validate credentials, resolve the project and print the target URL without changing the CDN.
Libraries travel with the project
Section titled “Libraries travel with the project”A project that uses other projects as libraries is published together with them. The publish puts a copy of each library under p/libraries/<library id>/, next to your project, and the published viewer loads the libraries from there. Only what the project actually uses is copied: the objects it places, the objects those objects need from further libraries, and their thumbnails. The files are copied unchanged.
The publish finds each library in your workspace by its project id. A library that lives somewhere else is named on the command line:
npm run publish:project -- --project "My Line" --library prj_shared_conveyors=../SharedConveyorsIf a library the project uses cannot be found, or an object the project places is missing from its library, the publish stops before anything is uploaded and names the object and the library. The realvirtual library is not copied: it is part of every viewer build and is published with the viewer when your project uses it.
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 changed model is uploaded. Project documents are compared by
sha256 from project.json, not by file size. A changed re-export with the
same byte count is still recognised, while the unchanged runtime is not
uploaded again. After staging, one manifest read, one upload and a 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 your own storage zone inside realvirtual’s CDN account. You also get a hostname such as your-name.web.realvirtual.io and the zone’s password. The password grants write and delete access only within your zone. It cannot access other CDN content, and nobody else’s key can access your zone.
Set these three values in a gitignored .env to configure publishing:
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”Use the same command with your own account. Every account-specific value comes from the environment; none 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/").
The server must meet these three requirements:
| 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 |
Links that start a use case
Section titled “Links that start a use case”A published link can open straight into one use case. Add entry=usecase to start the simulation without the start dialogs, and view=<preset> (iso, top, front, back, left, right, home) to choose the camera. Combine them with doc= and mode=; see the URL parameter reference.
On the public demo, the visitor first accepts the terms of use. They are shown once per terms version and stored in the browser. A project you host yourself shows no terms step of its own.
Password protection
Section titled “Password protection”An unguessable URL is not always enough. You can publish an encrypted project. Every GLB is encrypted with AES-256-GCM, and the viewer asks for a password before showing 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 | Confidential extensions, notice removable | |
|---|---|---|---|
| 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 |
Model data stays yours in both cases.
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
- Library and Catalogs — using other projects as libraries
- Opening a twin — what your users see on the link