Generate a WebSpatial-Specific Website
After integrating the WebSpatial SDK into the project's TS/JS compiler and Web build tool & Web server, your web project can produce a dedicated build for the WebSpatial App Shell, without affecting the original desktop/mobile site.
This build is essentially a standalone website that loads only inside a native spatial app (Packaged WebSpatial App) containing the App Shell built with WebSpatial Builder. Web code in this context can tightly cooperate with native parts of the app to deliver web-controlled spatial capabilities.
During simulator debugging
All examples below are based on Vite
Run the regular Dev Server
Run the project's dev
script as usual. The served site targets desktop/mobile platforms and regular browsers (including the default browser on XR platforms, such as Safari on visionOS).
- npm
- pnpm
- Yarn
npm run dev
pnpm run dev
yarn dev
- The HTML/CSS/JS output does NOT include WebSpatial SDK; all WebSpatial API calls are removed or ignored.
- Unsuitable for loading in the WebSpatial App Shell (no spatial effects).
Run the dedicated Dev Server
To build specifically for the WebSpatial App Shell on visionOS, set the environment variable $XR_ENV
from the WebSpatial SDK to avp
when running dev
.
- npm
- pnpm
- Yarn
XR_ENV=avp npm run dev
XR_ENV=avp pnpm run dev
XR_ENV=avp yarn dev
Best practice: add an npm script for this dedicated Dev Server. Like:
"dev": "vite",
"dev:avp": "XR_ENV=avp vite",
- The HTML/CSS/JS output does include WebSpatial SDK.
- To make sure the HTML/CSS works fine in regular browser engines that don't support the WebSpatial API (like the default system WebView), all WebSpatial API calls in the HTML/CSS source are either removed or ignored and replaced with non-standard JS Bridge API calls in the JS output.
- Only suitable for loading in the WebSpatial App Shell; not suitable for regular browsers like Chrome on your computer, since it lacks the WebSpatial App Shell and the web UI won't display correctly.
- The served URL automatically adds the base segment
/webspatial/avp/
to keep things consistent with the distribution phase while still making development efficient during debugging.Example: The regular Dev Server runs at
http://localhost:3000
; the dedicated Dev Server for visionOS runs athttp://localhost:3001/webspatial/avp/
. - If the project defines a custom base,
/webspatial/avp/
is not prepended.Example:
vite.config.jsimport { defineConfig } from "vite";
import vue from "@vitejs/plugin-react";
import WebSpatial from "@webspatial/vite-plugin";
export default defineConfig({
plugins: [react(), WebSpatial()],
base: "/my-project/",
});URLs for the regular and dedicated Dev Servers become
http://localhost:3000/my-project/
andhttp://localhost:3001/my-project/
.
Use the dedicated Dev Server
Combine the dedicated Dev Server with webspatial-builder run
(or the run:avp
script) to package and install a visionOS app in the simulator.
Pass the dedicated Dev Server URL as the --base
option (or $XR_DEV_SERVER
) to replace the original start_url
in the Web App Manifest.
- npm
- pnpm
- Yarn
npx webspatial-builder run --base=http://localhost:3001/webspatial/avp/
pnpm dlx webspatial-builder run --base=http://localhost:3001/webspatial/avp/
yarn dlx webspatial-builder run --base=http://localhost:3001/webspatial/avp/
- npm
- pnpm
- Yarn
XR_DEV_SERVER=http://localhost:3001/webspatial/avp/ npm run run:avp
XR_DEV_SERVER=http://localhost:3001/webspatial/avp/ pnpm run run:avp
XR_DEV_SERVER=http://localhost:3001/webspatial/avp/ yarn run:avp
Once the app starts in the visionOS simulator, it'll automatically load the URL from the dedicated Dev Server.
Because /webspatial/avp/
is injected as the base segment, every Web-build-tool-processed URL automatically includes webspatial/avp/
, for example:
<link rel="icon" href="/webspatial/avp/favicon.ico" sizes="any" />
<link
rel="apple-touch-icon"
href="/webspatial/avp/icons/apple-touch-icon.png" />
<script
type="module"
crossorigin
src="/webspatial/avp/assets/index-CpANHSXr.js"></script>
<link
rel="stylesheet"
crossorigin
href="/webspatial/avp/assets/index-B4Bp50KL.css" />
But when URLs appear within the JS logic, the web build tool won't automatically add that base part, you'll need to add it yourself.
In your JS code, you can use __XR_ENV_BASE__
to get that base string.
<button
onClick={() => {
window.open(`${__XR_ENV_BASE__}/second-page`, "secondScene");
}}>
If you're using client-side routing like in the Quick Example, you can set the base path centrally in the routing library so it's handled consistently.
Using react-router-dom
as an example:
return (
<Router basename={__XR_ENV_BASE__}>
<Routes>
In this case, prefer <Link />
over raw <a>
tags or window.open
, letting react-router-dom
handle the base automatically:
<Link to="/second-page" target="_blank">
Open Second Page with a Link
</Link>
During device testing and distribution
At this stage you must deploy the site to a web server accessible from real devices.
Examples below use Vite.
Multi-Web-Server mode
The quickest and easiest way is to deploy two sites on different domains, just like in the simulator debugging phase.
One site serves the desktop/mobile version:
- npm
- pnpm
- Yarn
npm run build
npm run preview
pnpm run build
pnpm run preview
yarn build
yarn preview
- The HTML/CSS/JS output does NOT include WebSpatial SDK; all WebSpatial API calls are removed or ignored.
- Unsuitable for loading in the WebSpatial App Shell (no spatial effects).
The second site serves the visionOS-specific version.
When building and starting it, you need to set the $XR_ENV
environment variable from the WebSpatial SDK to avp
.
- npm
- pnpm
- Yarn
XR_ENV=avp npm run build
XR_ENV=avp npm run preview
XR_ENV=avp pnpm run build
XR_ENV=avp pnpm run preview
XR_ENV=avp yarn build
XR_ENV=avp yarn preview
The files in the webspatial/avp/
path under the output folder (like /dist
) are specifically for the WebSpatial App Shell on visionOS.
- The HTML/CSS/JS output does include WebSpatial SDK.
- To make sure the HTML/CSS works fine in regular browser engines that don't support the WebSpatial API (like the default system WebView), all WebSpatial API calls in the HTML/CSS source are either removed or ignored and replaced with non-standard JS Bridge API calls in the JS output.
- Only suitable for loading in the WebSpatial App Shell; not suitable for regular browsers like Chrome on your computer, since it lacks the WebSpatial App Shell and the web UI won't display correctly.
Under default settings:
- The starting URL, along with all Web-build-tool-processed URL will automatically get
/webspatial/avp/
added to the base. For example:<link rel="icon" href="/webspatial/avp/favicon.ico" sizes="any" />
<link
rel="apple-touch-icon"
href="/webspatial/avp/icons/apple-touch-icon.png" />
<script
type="module"
crossorigin
src="/webspatial/avp/assets/index-CpANHSXr.js"></script>
<link
rel="stylesheet"
crossorigin
href="/webspatial/avp/assets/index-B4Bp50KL.css" /> - Just like when using the dedicated Dev Server, you need to manually add the base path to URLs in the JS logic. If you're using client-side routing, you can set this base path centrally through your routing library.
To use different domains to separate the two versions of the site and just use /
as the base (skipping webspatial/avp/
), configure as follows:
- Begin by configuring a custom base in the URL to differentiate the two site versions. With a custom base, WebSpatial SDK skips adding
/webspatial/avp/
automatically. - Always output to the root directory of
dist/
.
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import WebSpatial from "@webspatial/vite-plugin";
// https://vite.dev/config/
export default defineConfig({
base: process.env.NODE_ENV === 'production'
&& (
process.env.XR_ENV !== 'avp'
? 'https://myproject.com/'
: 'https://webspatial.myproject.com/'
) || ''
build: {
outDir: 'dist',
},
plugins: [
WebSpatial({
outputDir: "",
}),
react(),
Single-Web-Server mode
Another option is to use a single web server to serve both the desktop/mobile version and the version for the WebSpatial App Shell in visionOS. This avoids extra deployment steps, domains, and server resource usage.
In this case, you'll need to run the project's build script twice, one after the other.
- First build: generates the usual HTML and static files for desktop, mobile, and regular browsers.
- Second build: with
XR_ENV=avp
, generates the HTML and static assets specifically tailored for the WebSpatial App Shell in visionOS.
During the second build, the WebSpatial plugin keeps the first build's files and appends new files.
- npm
- pnpm
- Yarn
npm run build && XR_ENV=avp npm run build
pnpm run build&& XR_ENV=avp pnpm run build
yarn build&& XR_ENV=avp yarn build
Best practice: chain both builds in one npm script.
"build": "vite build && XR_ENV=avp vite build",
Output location is set by the web build tool's defaults and custom config. For example, Vite puts build output in the dist/
by default, you can change that with build.outDir
.
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
build: {
outDir: 'web-dist',
emptyOutDir: true,
assetsDir: 'static',
},
plugins: [
Example output:
web-dist
├── favicon.ico
├── icons
│ ├── icon-1024-maskable.png
│ └── icon-512.png
├── index.html
├── manifest.webmanifest
├── static
│ ├── index-B4Bp50KL.css
│ └── index-xAPzJf4I.js
└── webspatial
└── avp
├── favicon.ico
├── icons
│ ├── icon-1024-maskable.png
│ └── icon-512.png
├── index.html
├── manifest.webmanifest
└── static
├── index-B4Bp50KL.css
└── index-Bk-ZYFXx.js
- Root level: desktop/mobile files.
webspatial/avp/
: visionOS WebSpatial files (different hashes because the SDK is included).- Within
webspatial/avp/
, all web-build-tool-processed URLs prependwebspatial/avp/
.
Two serving approaches:
- Configure the server so all web page requests with
/webspatial/avp/
as the base will read HTML files from thedist/webspatial/avp/
directory.In this case, just like with the dedicated Dev Server or the multi-web-server mode, you'll need to manually add the base part to your web links in JS logic. If you're using client-side routing, you can set the base path centrally in the routing library for consistency.
- Detect the special User-Agent string of the WebSpatial App Shell. Serve HTML files from
dist/webspatial/avp
for those web page requests; otherwise serve fromdist/
.In this case you'll need to set a custom base URL in your web build tool's config, then the WebSpatial SDK won't automatically add
/webspatial/avp/
.vite.config.jsexport default defineConfig({
base: 'https://myproject.com/',
Usage 1: Use built-in Static Web server from your web build tool
E.g. with Vite or Rsbuild/Rspack:
- npm
- pnpm
- Yarn
npm run preview
pnpm run preview
yarn preview
Best aligned with the multi-Web-Server approach.
With custom routing (mapping /webspatial/avp/
requests to HTML files in dist/webspatial/avp/
directory), the single-Web-Server approach also works.
Usage 2: Use third-party static web hosting
E.g. with Vercel, Cloudflare Pages, or GitHub Pages.
Similar to Usage 1 and suits the multi-Web-Server approach.
For GitHub Pages, deploy the WebSpatial version separately:
npm install -D gh-pages
gh-pages -d dist/webspatial/avp
Usage 3: Use dynamic Web server with SSR
E.g. with Next.js:
- npm
- pnpm
- Yarn
npm run start
pnpm run start
yarn run start
Because in this case webpages share a single HTML template (or none), differentiating by template is impossible. The multi-Web-Server approach is recommended.
Deploy a dedicated SSR server for WebSpatial and set the root path for static web file URLs to point to the directory or CDN address where the WebSpatial-specific files are stored.
module.exports = {
assetPrefix: process.env.NODE_ENV === 'production'
? 'https://cdn.example.com/webspatial/avp'
: '/static/webspatial/avp',
Usage 4: Use self-hosted dynamic web server
E.g. with a Node.js server based on NestJS framework
Use the second serving option of the single-Web-Server approach: detect the WebSpatial App Shell's User-Agent string and serve HTML files from dist/webspatial/avp
directory, otherwise from dist/
directory.