Skip to content

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.

<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, if configured, 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 (if configured) https://host/ <code>/ link to share The page that loads everything goes up last, so a new link never points at files that are not uploaded yet.
Files are overwritten in place; index.html uploads last.
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 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.

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:

Terminal window
npm run publish:project -- --project "My Line" --library prj_shared_conveyors=../SharedConveyors

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

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

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.

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.

Set these three values in a gitignored .env to configure publishing:

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.

Use the same command with your own account. Every account-specific value comes from the environment; none 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/").

The server must meet these three requirements:

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

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.

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:

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 realvirtualConfidential extensions, notice removable
Community (AGPL)build and host yourselfnoAGPL obligations apply: see licensing the viewer
Commercialnpm run publish:project in the delivered repositoryon requestyes

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.