Can’t access my WebXR from Meta Quest
When I first tried building a basic WebXR app for my Quest 3 headset, I got caught by a few gotchas. I’ll describe them below.
I used BabylonJS & Vite for my setup, but while the code examples will be tailored toward that in some instances, the concepts will hopefully be universally relevant.
Access the page from your headset
You’re probably developing on your laptop or desktop, in which case, at many points along the way you will want to open the page in your headset.
Vite’s defaults will give you an address that will only work on the device you’re running the process on though. ie…
http://localhost:3000
You turn that into a link accessible from any device online by adding the following host value to your vite.config.js file. It’s not enough though, so keep reading.
import { defineConfig } from 'vite';
///////////////////
export default defineConfig({
root: 'src',
build: {
outDir: '../dist',
emptyOutDir: true,
},
server: {
host: '0.0.0.0', // This makes the server accessible publicly
port: 3000
}
});
Now, when you restart the server you’ll get both a local URL and a network URL. Use the network URL to access the page from another device while the server is running.
Recognise the Quest as an XR supported device
The next stumbling block is that you cannot enter VR or AR mode unless the device is being served through an SSL connection. If it’s not, opening the page on the Quest headset will look just like on your laptop — It won’t report as XR capable and therefore won’t give you a button to enter immersive mode (Or if you manually created the button, it won’t work).
You can fix this during development with a temporary certificate.
First install the @vitejs/plugin-basic-ssl package.
npm i @vitejs/plugin-basic-ssl
Then update your vite.config.js file like this:
import { defineConfig } from 'vite';
import basicSsl from '@vitejs/plugin-basic-ssl'; // Don't forget the import
///////////////////
export default defineConfig({
root: 'src',
build: {
outDir: '../dist',
emptyOutDir: true,
},
server: {
host: '0.0.0.0',
port: 3000
},
// This gives you a basic SSL certificate
plugins: [
basicSsl({
name: 'test', // name of certification
domains: ['*.custom.com'], // custom trust domains
certDir: '/Users/.../.devServer/cert' // custom certification directory
})
]
});
Now when you restart he server you’ll get a public address that you can use in your Headset. The address might look the same, but it’s not, it’s https instead of http.
When you access it, it will give you a warning about an unknown certificate, but you can click advanced and proceed anyway. This is safe, since you made the page.
Side-note: AI told me to do the below, but I found it doesn’t do the job. On it’s own it will create an https link, but it won’t create a certificate. If you do the above instead, you don’t need the below because you’ll get both automatically.
Not needed:
server: {
https: true,
port: 3000
},
Thanks…
I also dissect and speculate on design and development.
Digging into subtle details and implications, and exploring broad perspectives and potential paradigm shifts.
Check out my conceptual articles on Substack or find my latest below.
You can also find me on Threads, Bluesky, Mastodon, or xTwitter for more diverse posts about ongoing projects.
Leave a Reply