BuilderIO / BuilderIO/builder

@builder.io/sdk-vue: non-deterministic block class names cause SSR hydration mismatches in Nuxt 4 / Vue 3.5

Open
#4,729 0 comments 0 reactions 0 assignees View on GitHub
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.