@builder.io/sdk-vue: non-deterministic block class names cause SSR hydration mismatches in Nuxt 4 / Vue 3.5
- Dominant language
- TypeScript
- Stars
- 8.8k
- Forks
- 1.2k
- Avg merge
- 1d 6h
- Merged PRs (30d)
- 17
Description
## Environment
- `@builder.io/sdk-vue`: **5.2.4** (also latest **5.2.5** — its only change is a security patch per the changelog, so unaffected)
- Nuxt **4.4.8**, Nitro **2.13.4**, Vue **3.5.39**, Vite **6.4.3**
- Rendering mode: **SSR** (universal), dev and production builds
## Describe the bug
When a Builder page is server-rendered and then hydrated on the client, Vue reports **hydration class/attribute mismatches on `.builder-block` elements**. The generated `builder-` class names differ between the server render and the client render for the *same* block, e.g. server emits `builder-ffd75456478d4dc7990915e418f7df4f` while the client expects `builder-76d4ab7cc2bd4a68bef3a7ebbe3871ac`.
On a single page we see **42 mismatches (39 class + 3 attribute)**, all on Builder blocks.
## Exact warnings (dev)
```
[Vue warn]: Hydration class mismatch on div.builder-ffd75456478d4dc7990915e418f7df4f.builder-block
- expected on client: class="builder-76d4ab7cc2bd4a68bef3a7ebbe3871ac builder-block"
Note: this mismatch is check-only. The DOM will not be rectified in production due to performance overhead.
[Vue warn]: Hydration attribute mismatch on div.builder-blocks.component-5vz8uirn7sc
- expected on client: hidden="true"
Hydration completed but contains mismatches.
```
## Production impact
This is **not** dev-only. In a production build the detailed `[Vue warn]` lines are stripped, but Vue 3.5 still emits `Hydration completed but contains mismatches.` as a `console.error` (confirmed present in `@vue/runtime-core`'s production build). So every SSR visitor gets a console error, and it can be picked up by error monitoring.
## The content is identical on both sides
The Builder content is fetched once on the server and hydrated from the framework payload (not re-fetched on the client), so both renders receive a byte-identical content object. The differing `builder-` class names therefore point to **non-deterministic class-name generation inside the SDK's render**, not a content difference.
## Minimal reproduction
1. Nuxt 4 app (SSR) with `@builder.io/sdk-vue@5.2.4`.
2. Fetch a page entry server-side and hydrate it (so server and client share one content object):
```ts
const { data } = await useAsyncData('builder', () =>
fetchOneEntry({ model: 'page', apiKey: KEY, apiVersion: 'v3' })
)
```
3. Render it: ``
4. Load the page in a browser and observe the console during hydration.
**Expected:** `builder-*` class names are identical between the SSR output and the client hydration render → no hydration mismatch.
**Actual:** `builder-` class names differ per render pass → many `Hydration class mismatch` warnings + `Hydration completed but contains mismatches.`
## Additional context
- Wrapping the `` in a `data-allow-mismatch` container does **not** help: Vue 3.5 only cascades `data-allow-mismatch` to descendants for `text`/`children` mismatches, never for `class`/`attribute`, so the flag would have to sit on each generated `.builder-block` (which the app doesn't control).
- Root cause appears to be that block class/id generation isn't stable across SSR and CSR. Making it deterministic (e.g. derive from the persisted block `id`/content rather than a per-render hash) would resolve it.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with the Vue SDK's Content entry point and the fetchOneEntry SSR payload described in the reproduction, then trace how builder-* block classes are generated during server and client renders. Reproduce the Nuxt 4 hydration mismatch and verify that identical block content produces identical classes with no hydration warnings or errors.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- nuxtjs, typescript
- Domain
- frontend, web-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100