diff --git a/pages/[[server]]/@[account]/[status].vue b/pages/[[server]]/@[account]/[status].vue
index 2e330b24..e25df22d 100644
--- a/pages/[[server]]/@[account]/[status].vue
+++ b/pages/[[server]]/@[account]/[status].vue
@@ -1,4 +1,6 @@
 <script setup lang="ts">
+// @ts-expect-error missing types
+import { DynamicScroller, DynamicScrollerItem } from 'vue-virtual-scroller'
 import type { ComponentPublicInstance } from 'vue'
 
 definePageMeta({
@@ -17,13 +19,13 @@ const publishWidget = ref()
 const { data: status, pending, refresh: refreshStatus } = useAsyncData(
   `status:${id}`,
   () => fetchStatus(id),
-  { watch: [isHydrated], immediate: isHydrated.value },
+  { watch: [isHydrated], immediate: isHydrated.value, default: () => shallowRef() },
 )
 const { client } = $(useMasto())
 const { data: context, pending: pendingContext, refresh: refreshContext } = useAsyncData(
   `context:${id}`,
   async () => client.v1.statuses.fetchContext(id),
-  { watch: [isHydrated], immediate: isHydrated.value },
+  { watch: [isHydrated], immediate: isHydrated.value, lazy: true, default: () => shallowRef() },
 )
 
 const replyDraft = $computed(() => status.value ? getReplyDraft(status.value) : null)
@@ -90,16 +92,25 @@ onReactivated(() => {
           @published="refreshContext()"
         />
 
-        <template v-for="(comment, di) of context?.descendants" :key="comment.id">
-          <StatusCard
-            :status="comment"
-            context="account"
-            :older="context?.descendants[di + 1]"
-            :newer="context?.descendants[di - 1]"
-            :has-newer="di === 0"
-            :main="status"
-          />
-        </template>
+        <TimelineSkeleton v-if="pendingContext" />
+        <DynamicScroller
+          v-slot="{ item, index, active }"
+          :items="context?.descendants || []"
+          :min-item-size="200"
+          key-field="id"
+          page-mode
+        >
+          <DynamicScrollerItem :item="item" :active="active">
+            <StatusCard
+              :status="item"
+              context="account"
+              :older="context?.descendants[index + 1]"
+              :newer="context?.descendants[index - 1]"
+              :has-newer="index === 0"
+              :main="status"
+            />
+          </DynamicScrollerItem>
+        </DynamicScroller>
       </div>
 
       <StatusNotFound v-else :account="route.params.account as string" :status="id" />
diff --git a/pages/[[server]]/@[account]/index.vue b/pages/[[server]]/@[account]/index.vue
index 34a0f9c8..027ac8f5 100644
--- a/pages/[[server]]/@[account]/index.vue
+++ b/pages/[[server]]/@[account]/index.vue
@@ -8,7 +8,7 @@ const accountName = $(computedEager(() => toShortHandle(params.account as string
 
 const { t } = useI18n()
 
-const { data: account, pending, refresh } = $(await useAsyncData(() => fetchAccountByHandle(accountName).catch(() => null), { immediate: process.client }))
+const { data: account, pending, refresh } = $(await useAsyncData(() => fetchAccountByHandle(accountName).catch(() => null), { immediate: process.client, default: () => shallowRef() }))
 const relationship = $computed(() => account ? useRelationship(account).value : undefined)
 
 onReactivated(() => {
diff --git a/pages/[[server]]/tags/[tag].vue b/pages/[[server]]/tags/[tag].vue
index e673005f..3df97c80 100644
--- a/pages/[[server]]/tags/[tag].vue
+++ b/pages/[[server]]/tags/[tag].vue
@@ -7,7 +7,7 @@ const params = useRoute().params
 const tagName = $(computedEager(() => params.tag as string))
 
 const { client } = $(useMasto())
-const { data: tag, refresh } = $(await useAsyncData(() => client.v1.tags.fetch(tagName)))
+const { data: tag, refresh } = $(await useAsyncData(() => client.v1.tags.fetch(tagName), { default: () => shallowRef() }))
 
 const paginator = client.v1.timelines.listHashtag(tagName)
 const stream = useStreaming(client => client.v1.stream.streamTagTimeline(tagName))