diff --git a/app/components/ReplEditor.vue b/app/components/ReplEditor.vue
new file mode 100644
index 0000000..3b05c50
--- /dev/null
+++ b/app/components/ReplEditor.vue
@@ -0,0 +1,45 @@
+<script setup lang="ts">
+import type { Store } from '@vue/repl'
+import { Repl } from '@vue/repl'
+import MonacoEditor from '@vue/repl/monaco-editor'
+
+const props = defineProps({
+  ssr: {
+    type: Boolean,
+    default: false,
+  },
+  store: {
+    type: Object as PropType<Store>,
+    required: true,
+  },
+})
+
+const colorMode = useColorMode()
+
+const theme = computed(() => {
+  if (colorMode.value === 'dark')
+    return 'dark'
+  else
+    return 'light'
+})
+
+const previewOptions = {
+  headHTML: `
+    <script src="https://cdn.jsdelivr.net/npm/@unocss/runtime"><\/script>
+    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@unocss/reset/tailwind.min.css" />
+    <script>
+      window.__unocss = {
+        rules: [],
+        presets: [],
+      }
+    <\/script>
+  `,
+}
+</script>
+
+<template>
+  <Repl
+    :store="props.store" :editor="MonacoEditor" :show-compile-output="true" :theme="theme" :preview-theme="true"
+    :preview-options="previewOptions" :ssr="props.ssr"
+  />
+</template>
diff --git a/app/layouts/default.vue b/app/layouts/default.vue
index f5236a5..f5a446b 100644
--- a/app/layouts/default.vue
+++ b/app/layouts/default.vue
@@ -2,26 +2,21 @@
 import { getVersionsBatch } from 'fast-npm-meta'
 import semver from 'semver'
 
-// todo: type
-const versions = useSessionStorage<any>('versions', [])
-
-const loadingVersions = shallowRef(false)
-
-async function fetchVersions() {
-  loadingVersions.value = true
-  versions.value = await getVersionsBatch(['@vueuse/core', 'vue'])
-  loadingVersions.value = false
-}
-
-if (!versions.value.length) {
-  fetchVersions()
-}
+const { data: versions, status, error, refresh: fetchVersions } = await useAsyncData(
+  'versions',
+  async () => getVersionsBatch(['@vueuse/core', 'vue']),
+  { default: () => ([]) },
+)
+
+const loadingVersions = computed(() => {
+  return status.value === 'pending'
+})
 
 const vueVersion = shallowRef()
 
 const vueVersions = computed(() => {
   const vue = versions.value.find(p => p.name === 'vue')
-  if (vue?.error)
+  if (vue?.error || error.value)
     return []
   return vue?.versions ?? []
 })
@@ -32,7 +27,7 @@ const vueUseVersion = useRouteQuery('vueuse', 'latest')
 
 const vueUseVersions = computed(() => {
   const vueuse = versions.value.find(p => p.name === '@vueuse/core')
-  if (vueuse?.error)
+  if (vueuse?.error || error.value)
     return []
   return vueuse?.versions ?? []
 })
@@ -68,7 +63,7 @@ const prod = useRouteQuery<string, boolean>('prod', 'false', {
         <USwitch v-model="prod" label="Prod" />
         <USelectMenu v-model="vueUseVersion" :items="vueUseVersionsSorted" class="w-32" icon="i-logos-vueuse" :loading="loadingVersions" />
         <USelectMenu v-model="vueVersion" :items="vueVersionsSorted" class="w-32" icon="i-logos-vue" :loading="loadingVersions" />
-        <UButton icon="i-lucide-refresh-ccw" size="md" color="primary" variant="soft" @click="fetchVersions" />
+        <UButton icon="i-lucide-refresh-ccw" size="md" color="primary" variant="soft" @click="() => fetchVersions()" />
         <UButton
           color="neutral" variant="ghost"
           :icon="colorMode.preference === 'dark' ? 'i-heroicons-moon' : 'i-heroicons-sun'"
diff --git a/app/pages/index.vue b/app/pages/index.vue
index 340c760..89125a0 100644
--- a/app/pages/index.vue
+++ b/app/pages/index.vue
@@ -1,8 +1,7 @@
 <script setup lang="ts">
 import type { OutputModes } from '@vue/repl'
 import type { ShallowRef } from 'vue'
-import { mergeImportMap, Repl, useStore, useVueImportMap } from '@vue/repl'
-import MonacoEditor from '@vue/repl/monaco-editor'
+import { mergeImportMap, useStore, useVueImportMap } from '@vue/repl'
 
 const showOutput = useRouteQuery<string, boolean>('showOutput', 'false', {
   transform: stringToBooleanTransformer,
@@ -81,35 +80,15 @@ watch(() => injectedVueVersion.value, (newVersion) => {
 watch(() => prod.value, (newProd) => {
   productionMode.value = newProd
 }, { immediate: true })
-
-const colorMode = useColorMode()
-
-const theme = computed(() => {
-  if (colorMode.value === 'dark')
-    return 'dark'
-  else
-    return 'light'
-})
-
-const previewOptions = {
-  headHTML: `
-    <script src="https://cdn.jsdelivr.net/npm/@unocss/runtime"><\/script>
-    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@unocss/reset/tailwind.min.css" />
-    <script>
-      window.__unocss = {
-        rules: [],
-        presets: [],
-      }
-    <\/script>
-  `,
-}
 </script>
 
 <template>
-  <client-only>
-    <Repl
-      :store="store" :editor="MonacoEditor" :show-compile-output="true" :theme="theme" :preview-theme="true"
-      :preview-options="previewOptions" :ssr="ssr"
-    />
-  </client-only>
+  <ClientOnly>
+    <ReplEditor :ssr="ssr" :store="store" />
+    <template #fallback>
+      <div class="flex w-full h-full justify-center items-center px-12">
+        <UProgress animation="swing" />
+      </div>
+    </template>
+  </ClientOnly>
 </template>
diff --git a/nuxt.config.ts b/nuxt.config.ts
index b0befb6..b81e6ec 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -1,8 +1,8 @@
 // https://nuxt.com/docs/api/configuration/nuxt-config
 export default defineNuxtConfig({
-  ssr: false,
   compatibilityDate: '2024-11-01',
   devtools: { enabled: true },
+  sourcemap: false,
   future: {
     compatibilityVersion: 4,
   },
@@ -18,9 +18,12 @@ export default defineNuxtConfig({
     },
     build: {
       rollupOptions: {
-        external: ['typescript'],
+        external: ['typescript', '@vue/compiler-sfc'],
       },
     },
+    ssr: {
+      noExternal: ['@vue/repl'],
+    },
   },
   css: ['~/assets/css/main.css'],
 })