{"id":28405,"date":"2022-03-23T22:03:06","date_gmt":"2022-03-23T22:03:06","guid":{"rendered":"http:\/\/Creating-a-VR-App-with-Redwood"},"modified":"2025-05-23T11:02:05","modified_gmt":"2025-05-23T18:02:05","slug":"creating-a-vr-app-with-redwood","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-vr-app-with-redwood\/","title":{"rendered":"Creating a Virtual Reality App"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>VR has grown over the past few years as the number of compatible devices increase. There are a ton of uses for it, both practical and for entertainment. If you know JavaScript, you can even start making your own VR apps right in the browser.<\/p>\n<p>In this tutorial, we\u2019re going to make a quick search and find game. There will be a few objects hidden around the world and the player will have to find them all to win. We\u2019ll be using Redwood and A-frame to handle all of our VR and user experience needs.<\/p>\n<h2>Building the VR world<\/h2>\n<p>We\u2019ll start by making a new Redwood app. In a terminal, run the following command.<\/p>\n<p><code>yarn create redwood-app vr-in-redwood<\/code><\/p>\n<p>This bootstraps a new Redwood app with a lot of folders and files that have been auto-generated. We\u2019re going to start on the front-end so that we jump into the VR part. All of our front-end code is in the <code>web<\/code> directory.<\/p>\n<p>We\u2019re going to a new page called <code>World<\/code> and it will point to the root of the app. To create this page, we\u2019ll run this command.<\/p>\n<h3>Setting up the world<\/h3>\n<p><code>yarn rw g page world \/<\/code><\/p>\n<p>After this finishes, go to the <code>web &gt; src &gt; pages<\/code> directory and you\u2019ll see a <code>WorldPage<\/code> folder. It has the code for the home page and it has a few other files to help with testing. If you take a look at <code>Routes.js<\/code>, you\u2019ll also notice the new routes have automatically added.<\/p>\n<p>We need to add Aframe to the project because this is the library we\u2019re going to use to make our VR world. Import this library in the <code>index.html<\/code> file with the following line at the end of the <code>&lt;head&gt;<\/code> element.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/aframe.io\/releases\/1.0.4\/aframe.min.js\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">script<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>Updating the component<\/h3>\n<p>Using this import, we have access to the different Aframe components available in the library. We can start building our new world in the <code>WorldPage<\/code> component. Open that file and add the following code.<\/p>\n<p><em>You can delete the import and the current contents of the return statement inside of the <code>WorldPage<\/code> component. We won\u2019t be using any of the template code.<\/em><\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> WorldPage = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a-scene<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a-assets<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span>\n          <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"room\"<\/span>\n          <span class=\"hljs-attr\">crossorigin<\/span>=<span class=\"hljs-string\">\"anonymous\"<\/span>\n          <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/res.cloudinary.com\/milecia\/image\/upload\/room-360_nag5ns.jpg\"<\/span>\n        \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a-assets<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a-sky<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"image-360\"<\/span> <span class=\"hljs-attr\">radius<\/span>=<span class=\"hljs-string\">\"10\"<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"#room\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a-sky<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a-camera<\/span> <span class=\"hljs-attr\">look-controls-enabled<\/span>=<span class=\"hljs-string\">{true}<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a-camera<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a-scene<\/span>&gt;<\/span><\/span>\n  )\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> WorldPage\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This is what your <code>WorldPage<\/code> component should look like now. We\u2019re using a few of the Aframe components.<\/p>\n<ul>\n<li>\n<code>&lt;a-scene&gt;<\/code> creates the entire world for the VR app.<\/li>\n<li>\n<code>&lt;a-assets&gt;<\/code> is how we import external resources, like images and audio files, into the world.<\/li>\n<li>\n<code>&lt;a-sky&gt;<\/code> uses a picture to create the background for the world. This is how you can create a static environment for your world if you don\u2019t need the user to move around much.<\/li>\n<li>\n<code>&lt;a-camera&gt;<\/code> is how we add a camera to the world so that a user can look around the world.<\/li>\n<\/ul>\n<p>You can learn more about how the Aframe library and components work by <a href=\"https:\/\/aframe.io\/docs\/1.2.0\/introduction\/\">checking out their docs<\/a>.<\/p>\n<h3>Pulling views from Cloudinary<\/h3>\n<p>Right now there\u2019s a placeholder image that drops users into a nice room, but you\u2019ll probably want something different for your app. We\u2019ll use Cloudinary to host the images because that\u2019ll decrease our load time and we won\u2019t have to deal with a lot of large files.<\/p>\n<p>So you can go to the Cloudinary site and <a href=\"https:\/\/cloudinary.com\/users\/register\/free\">sign up for a free account<\/a> and upload any panoramic images you want to use. Then you can update the <code>src<\/code> for the image in the <code>&lt;a-assets&gt;<\/code> element.<\/p>\n<p>You\u2019ll need to update <code>milecia<\/code> in the asset URL to match the cloud name for your Cloudinary account so that you can use your images.<\/p>\n<h3>Adding customization<\/h3>\n<p>Since we have the option to upload as many images as we want, users might like it if they can switch between images and have their own worlds load when they come to the app.<\/p>\n<p>We can add this by creating a new variable that will come from the back-end we\u2019ll be making in a bit. We\u2019ll start by adding a few GraphQL methods. Import a method from Redwood at the top of the <code>WorldPage<\/code> component file.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { useQuery } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@redwoodjs\/web'<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Then we\u2019ll add a call to that method inside of the component.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> { loading, data } = useQuery(WORLDS)\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Now we need to add the GraphQL definition for the query. So at the bottom of the component, above the export statement, add the following code.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> WORLDS = gql<span class=\"hljs-string\">`\n  query Worlds {\n    worlds {\n      id\n      imageName\n    }\n  }\n`<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>With our GraphQL request defined, let\u2019s update the component to use our new data. First, we\u2019ll add a loading state so that we don\u2019t have issues while data is being fetched. Below the <code>useQuery<\/code> line, add the following lines.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">if<\/span> (loading) {\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>Loading...<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Below this, we\u2019ll add a new variable that will contain the URL users have recently uploaded for the world. It\u2019ll default to an image if there isn\u2019t a user-selected one to load.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> worldUrl = data?.worlds&#91;data.worlds.length - <span class=\"hljs-number\">1<\/span>].imageName || <span class=\"hljs-string\">'room-360_nag5ns.jpg'<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Then we\u2019ll make the URL dynamic by updating the URL in the assets.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span>\n  <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"room\"<\/span>\n  <span class=\"hljs-attr\">crossorigin<\/span>=<span class=\"hljs-string\">\"anonymous\"<\/span>\n  <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{<\/span>`<span class=\"hljs-attr\">https:<\/span>\/\/<span class=\"hljs-attr\">res.cloudinary.com<\/span>\/<span class=\"hljs-attr\">milecia<\/span>\/<span class=\"hljs-attr\">image<\/span>\/<span class=\"hljs-attr\">upload<\/span>\/${<span class=\"hljs-attr\">worldUrl<\/span>}`}\n\/&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>With all of this in place, you can finally run the app with this command.<\/p>\n<p><code>yarn rw dev<\/code><\/p>\n<p>You should see something similar to this.<\/p>\n<p>Now we\u2019ll add the back-end and database setup to support the front-end we just created.<\/p>\n<h2>Setting up the back-end<\/h2>\n<p>Go to the <code>api &gt; db<\/code> directory and open <code>schema.prisma<\/code>. This is where we\u2019ll add the schema to save the URL that the user wants for their world. We\u2019re going to update the provider to use a Postgres database.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">provider = <span class=\"hljs-string\">\"postgresql\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Then we\u2019ll update the existing placeholder schema with our real schema. You can replace the <code>UserExample<\/code> schema with the following.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">model World {\n  id    Int     @id @<span class=\"hljs-keyword\">default<\/span>(autoincrement())\n  imageName <span class=\"hljs-built_in\">String<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>Running the migration<\/h3>\n<p>Before we run the migration, we\u2019ll need to update the <code>.env<\/code> file to use the database instance you want. You can <a href=\"https:\/\/www.postgresql.org\/download\/\">set up Postgres locally<\/a>. Update your <code>DATABASE_URL<\/code> with your credentials. It might look similar to this.<\/p>\n<p><code>DATABASE_URL=postgres:\/\/postgres:admin@localhost:5432\/vr_worlds<\/code><\/p>\n<p>With the schema in place, we\u2019ll be able to do our first migration.<\/p>\n<p><code>yarn rw prisma migrate dev<\/code><\/p>\n<p>This will make Prisma set up our new database. You\u2019ll be prompted to name your migration and then it will run. If you check your Postgres instance now, you should see the new table there.<\/p>\n<h3>Set up the GraphQL server<\/h3>\n<p>All that\u2019s left is to create the GraphQL types and resolvers. The great thing about Redwood is that it has a command to generate these things for us.<\/p>\n<p><code>yarn rw g sdl world<\/code><\/p>\n<p>Now if you go to <code>api &gt; src &gt; graphql<\/code>, you\u2019ll see <code>worlds.sdl.js<\/code> with all of the types you need for GraphQL. Then if you go to <code>api &gt; src &gt; services<\/code>, you\u2019ll see a new <code>worlds<\/code> folder with a few files. The <code>worlds.js<\/code> file has the one resolver that we need to fetch the data on the front-end.<\/p>\n<p>That\u2019s all! Now you have a full-stack VR app that works.<\/p>\n<h2>Finished code<\/h2>\n<p>You can check out the finished code in <a href=\"https:\/\/codesandbox.io\/s\/clever-shamir-br7sh\">this Code Sandbox<\/a> or in <a href=\"https:\/\/github.com\/flippedcoder\/blog-examples\">this GitHub repo in the \u2019vr-in-redwood` folder<\/a>.<\/p>\n<\/div>\n\n\n  <div class=\"wp-block-cloudinary-code-sandbox \">\n    <iframe\n      src=\"https:\/\/codesandbox.io\/embed\/clever-shamir-br7sh?theme=dark&amp;codemirror=1&amp;highlights=&amp;editorsize=50&amp;fontsize=14&amp;expanddevtools=0&amp;hidedevtools=0&amp;eslint=0&amp;forcerefresh=0&amp;hidenavigation=0&amp;initialpath=%2F&amp;module=&amp;moduleview=0&amp;previewwindow=&amp;view=&amp;runonclick=1\"\n      height=\"500\"\n      style=\"width: 100%;\"\n      title=\"clever-shamir-br7sh\"\n      loading=\"lazy\"\n      allow=\"accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking\"\n      sandbox=\"allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts\"\n    ><\/iframe>\n  <\/div>\n\n\n<div class=\"wp-block-cloudinary-markdown \"><h2>Conclusion<\/h2>\n<p>Hopefully, you can see how quickly you can create a new VR app in the JavaScript ecosystem. One thing that could be added to this app is the actual ability for users to push their preferred world in. This is a little tricky, but not terribly hard. You can definitely add that functionality as a challenge if you want to get more into VR.<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":41,"featured_media":28406,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[134,370,177,246,371],"class_list":["post-28405","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-guest-post","tag-image","tag-javascript","tag-react","tag-under-review"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Creating a Virtual Reality App<\/title>\n<meta name=\"description\" content=\"VR is a relatively new area of tech and now&#039;s a good time to learn about how to make AR apps with JavaScript. This tutorial will take you through everything you need to get started with VR in Redwood.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-vr-app-with-redwood\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Creating a Virtual Reality App\" \/>\n<meta property=\"og:description\" content=\"VR is a relatively new area of tech and now&#039;s a good time to learn about how to make AR apps with JavaScript. This tutorial will take you through everything you need to get started with VR in Redwood.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-vr-app-with-redwood\/\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2022-03-23T22:03:06+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-05-23T18:02:05+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924593\/Web_Assets\/blog\/cf0d9a714b82b1ab77b8167ea72133ee2156bb65-7952x5304-1_2840627b67\/cf0d9a714b82b1ab77b8167ea72133ee2156bb65-7952x5304-1_2840627b67.jpg?_i=AA\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-vr-app-with-redwood\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-vr-app-with-redwood\/\"},\"author\":{\"name\":\"\",\"@id\":\"\"},\"headline\":\"Creating a Virtual Reality App\",\"datePublished\":\"2022-03-23T22:03:06+00:00\",\"dateModified\":\"2025-05-23T18:02:05+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-vr-app-with-redwood\/\"},\"wordCount\":5,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-vr-app-with-redwood\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924593\/Web_Assets\/blog\/cf0d9a714b82b1ab77b8167ea72133ee2156bb65-7952x5304-1_2840627b67\/cf0d9a714b82b1ab77b8167ea72133ee2156bb65-7952x5304-1_2840627b67.jpg?_i=AA\",\"keywords\":[\"Guest Post\",\"Image\",\"Javascript\",\"React\",\"Under Review\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2022\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-vr-app-with-redwood\/\",\"url\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-vr-app-with-redwood\/\",\"name\":\"Creating a Virtual Reality App\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-vr-app-with-redwood\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-vr-app-with-redwood\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924593\/Web_Assets\/blog\/cf0d9a714b82b1ab77b8167ea72133ee2156bb65-7952x5304-1_2840627b67\/cf0d9a714b82b1ab77b8167ea72133ee2156bb65-7952x5304-1_2840627b67.jpg?_i=AA\",\"datePublished\":\"2022-03-23T22:03:06+00:00\",\"dateModified\":\"2025-05-23T18:02:05+00:00\",\"description\":\"VR is a relatively new area of tech and now's a good time to learn about how to make AR apps with JavaScript. This tutorial will take you through everything you need to get started with VR in Redwood.\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-vr-app-with-redwood\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-vr-app-with-redwood\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-vr-app-with-redwood\/#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924593\/Web_Assets\/blog\/cf0d9a714b82b1ab77b8167ea72133ee2156bb65-7952x5304-1_2840627b67\/cf0d9a714b82b1ab77b8167ea72133ee2156bb65-7952x5304-1_2840627b67.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924593\/Web_Assets\/blog\/cf0d9a714b82b1ab77b8167ea72133ee2156bb65-7952x5304-1_2840627b67\/cf0d9a714b82b1ab77b8167ea72133ee2156bb65-7952x5304-1_2840627b67.jpg?_i=AA\",\"width\":7952,\"height\":5304},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-vr-app-with-redwood\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Creating a Virtual Reality App\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Creating a Virtual Reality App","description":"VR is a relatively new area of tech and now's a good time to learn about how to make AR apps with JavaScript. This tutorial will take you through everything you need to get started with VR in Redwood.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-vr-app-with-redwood\/","og_locale":"en_US","og_type":"article","og_title":"Creating a Virtual Reality App","og_description":"VR is a relatively new area of tech and now's a good time to learn about how to make AR apps with JavaScript. This tutorial will take you through everything you need to get started with VR in Redwood.","og_url":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-vr-app-with-redwood\/","og_site_name":"Cloudinary Blog","article_published_time":"2022-03-23T22:03:06+00:00","article_modified_time":"2025-05-23T18:02:05+00:00","twitter_card":"summary_large_image","twitter_image":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924593\/Web_Assets\/blog\/cf0d9a714b82b1ab77b8167ea72133ee2156bb65-7952x5304-1_2840627b67\/cf0d9a714b82b1ab77b8167ea72133ee2156bb65-7952x5304-1_2840627b67.jpg?_i=AA","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-vr-app-with-redwood\/#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-vr-app-with-redwood\/"},"author":{"name":"","@id":""},"headline":"Creating a Virtual Reality App","datePublished":"2022-03-23T22:03:06+00:00","dateModified":"2025-05-23T18:02:05+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-vr-app-with-redwood\/"},"wordCount":5,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-vr-app-with-redwood\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924593\/Web_Assets\/blog\/cf0d9a714b82b1ab77b8167ea72133ee2156bb65-7952x5304-1_2840627b67\/cf0d9a714b82b1ab77b8167ea72133ee2156bb65-7952x5304-1_2840627b67.jpg?_i=AA","keywords":["Guest Post","Image","Javascript","React","Under Review"],"inLanguage":"en-US","copyrightYear":"2022","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-vr-app-with-redwood\/","url":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-vr-app-with-redwood\/","name":"Creating a Virtual Reality App","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-vr-app-with-redwood\/#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-vr-app-with-redwood\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924593\/Web_Assets\/blog\/cf0d9a714b82b1ab77b8167ea72133ee2156bb65-7952x5304-1_2840627b67\/cf0d9a714b82b1ab77b8167ea72133ee2156bb65-7952x5304-1_2840627b67.jpg?_i=AA","datePublished":"2022-03-23T22:03:06+00:00","dateModified":"2025-05-23T18:02:05+00:00","description":"VR is a relatively new area of tech and now's a good time to learn about how to make AR apps with JavaScript. This tutorial will take you through everything you need to get started with VR in Redwood.","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-vr-app-with-redwood\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-vr-app-with-redwood\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-vr-app-with-redwood\/#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924593\/Web_Assets\/blog\/cf0d9a714b82b1ab77b8167ea72133ee2156bb65-7952x5304-1_2840627b67\/cf0d9a714b82b1ab77b8167ea72133ee2156bb65-7952x5304-1_2840627b67.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924593\/Web_Assets\/blog\/cf0d9a714b82b1ab77b8167ea72133ee2156bb65-7952x5304-1_2840627b67\/cf0d9a714b82b1ab77b8167ea72133ee2156bb65-7952x5304-1_2840627b67.jpg?_i=AA","width":7952,"height":5304},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-vr-app-with-redwood\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Creating a Virtual Reality App"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":""}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924593\/Web_Assets\/blog\/cf0d9a714b82b1ab77b8167ea72133ee2156bb65-7952x5304-1_2840627b67\/cf0d9a714b82b1ab77b8167ea72133ee2156bb65-7952x5304-1_2840627b67.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28405","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=28405"}],"version-history":[{"count":1,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28405\/revisions"}],"predecessor-version":[{"id":37662,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28405\/revisions\/37662"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/28406"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=28405"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=28405"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=28405"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}