SSR

How skeletonization behaves with server-side rendering and hydration.

SSR

How it works

The automatic engine measures and rewrites the DOM, which only exists on the client. So skeleton painting runs on the client, after hydration. The server and the client's initial render are identical, which means no hydration mismatch.

In practice:

  1. The server renders your real markup (or the loading branch).
  2. The client hydrates that exact markup.
  3. After mount, if the scope is enabled, the engine paints bones.
  4. When loading completes, the originals are restored in place.

Avoiding a flash

Bind :enabled to your loading state so the skeleton only activates while data is pending:

<script setup lang="ts">
// Lazy composables return immediately with `pending: true`, so the skeleton
// shows right after hydration and disappears when data arrives.
const { data, pending } = await useLazyFetch('/api/dashboard')
</script>

<template>
  <Skeletonizer :enabled="pending">
    <Dashboard :data="data" />
  </Skeletonizer>
</template>

Manual primitives during SSR

The Skeleton* primitives are plain markup, so they are server-rendered. Use them when you want the skeleton visible in the initial HTML (e.g. above-the-fold placeholders):

<template>
  <div v-if="pending">
    <SkeletonCard media avatar :lines="3" />
  </div>
  <ArticleCard v-else :article="data" />
</template>

Nitro & accessibility

The module is Nitro-compatible and the runtime plugin is SSR-safe (it no-ops DOM access on the server). Painted bones are aria-hidden and active hosts get aria-busy.