diff --git a/build/copy_plugin.js b/build/copy_plugin.js
new file mode 100644
index 0000000000000000000000000000000000000000..a783fe7ff97f59d9800f5dddf4cc0bd648c91aa6
--- /dev/null
+++ b/build/copy_plugin.js
@@ -0,0 +1,40 @@
+import serveStatic from 'serve-static'
+import { resolve } from 'node:path'
+import { cp } from 'node:fs/promises'
+
+const getPrefix = s => {
+  const padEnd = s.endsWith('/') ? s : s + '/'
+  return padEnd.startsWith('/') ? padEnd : '/' + padEnd
+}
+
+const copyPlugin = ({ inUrl, inFs }) => {
+  const prefix = getPrefix(inUrl)
+  const subdir = prefix.slice(1)
+  let copyTarget
+  const handler = serveStatic(inFs)
+
+  return [{
+    name: 'copy-plugin-serve',
+    apply: 'serve',
+    configureServer (server) {
+      server.middlewares.use(prefix, handler)
+    }
+  }, {
+    name: 'copy-plugin-build',
+    apply: 'build',
+    configResolved (config) {
+      copyTarget = resolve(config.root, config.build.outDir, subdir)
+    },
+    closeBundle: {
+      order: 'post',
+      sequential: true,
+      async handler () {
+        console.log(`Copying '${inFs}' to ${copyTarget}...`)
+        await cp(inFs, copyTarget, { recursive: true })
+        console.log('Done.')
+      }
+    }
+  }]
+}
+
+export default copyPlugin
diff --git a/package.json b/package.json
index 2d3164585c0ec949ee17a78041d0458082abeb88..f94ad973b567b7520c80f1fa0ab33d2bd23cf377 100644
--- a/package.json
+++ b/package.json
@@ -115,6 +115,7 @@
     "sass-loader": "13.3.3",
     "selenium-server": "3.141.59",
     "semver": "7.7.1",
+    "serve-static": "1.16.2",
     "serviceworker-webpack5-plugin": "2.0.0",
     "shelljs": "0.8.5",
     "sinon": "15.2.0",
diff --git a/src/services/ruffle_service/ruffle_service.js b/src/services/ruffle_service/ruffle_service.js
index 7411dd962d22c90406d3f9dd091d3ee9a574924b..b317f08eb2e164331fc246142711ce987bcb7155 100644
--- a/src/services/ruffle_service/ruffle_service.js
+++ b/src/services/ruffle_service/ruffle_service.js
@@ -1,11 +1,12 @@
 const createRuffleService = () => {
   let ruffleInstance = null
 
-  const getRuffle = () => new Promise((resolve, reject) => {
+  const getRuffle = async () => new Promise((resolve, reject) => {
     if (ruffleInstance) {
       resolve(ruffleInstance)
       return
     }
+
     // Ruffle needs these to be set before it's loaded
     // https://github.com/ruffle-rs/ruffle/issues/3952
     window.RufflePlayer = {}
diff --git a/vite.config.js b/vite.config.js
index d298e0c076e3d243239b63e57fcc8c26d25ef78c..5f67609670415d4ae62240766b39ae71efa47947 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -1,10 +1,12 @@
 import { fileURLToPath } from 'node:url'
 import { dirname, resolve } from 'node:path'
+import { readFile } from 'node:fs/promises'
 import { defineConfig } from 'vite'
 import vue from '@vitejs/plugin-vue'
 import vueJsx from '@vitejs/plugin-vue-jsx'
 import { VitePWA } from 'vite-plugin-pwa'
 import { devSwPlugin, buildSwPlugin, swMessagesPlugin } from './build/sw_plugin.js'
+import copyPlugin from './build/copy_plugin.js'
 
 const getLocalDevSettings = async () => {
   try {
@@ -76,7 +78,11 @@ export default defineConfig(async ({ command }) => {
       vueJsx(),
       devSwPlugin({ swSrc, swDest }),
       buildSwPlugin({ swSrc, swDest }),
-      swMessagesPlugin()
+      swMessagesPlugin(),
+      copyPlugin({
+        inUrl: '/static/ruffle',
+        inFs: resolve(projectRoot, 'node_modules/@ruffle-rs/ruffle')
+      })
     ],
     resolve: {
       alias: {