Skip to content

Commit 5e6581f

Browse files
committed
fix: hydration error
1 parent 4f7017a commit 5e6581f

1 file changed

Lines changed: 40 additions & 17 deletions

File tree

vocs.config.tsx

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1428,18 +1428,30 @@ export default defineConfig({
14281428
updateActive();
14291429
}
14301430
1431-
function tryMount() {
1432-
mount();
1433-
// Retry briefly in case body isn't ready yet (very early script execution).
1434-
if (!document.getElementById('zd-pillar-bar')) {
1435-
var attempts = 0;
1436-
var interval = setInterval(function() {
1431+
// Re-insert whenever React's layout lacks the bar. mount() is a no-op when the
1432+
// bar already exists, so our own insertion can't loop the observer.
1433+
function watch() {
1434+
var observer = new MutationObserver(function() {
1435+
mount();
1436+
updateActive();
1437+
});
1438+
observer.observe(document.body, { childList: true, subtree: true });
1439+
}
1440+
1441+
// Mount only AFTER React has hydrated. Inserting our node into Vocs's
1442+
// server-rendered subtree before hydration makes the real DOM diverge from the
1443+
// server HTML, so React reports a hydration mismatch and regenerates the whole
1444+
// tree — wiping the bar (the visible flicker). Waiting for 'load' + two rAFs
1445+
// lets hydration finish first; the observer then keeps it mounted across
1446+
// React's own re-renders and SPA navigations.
1447+
function start() {
1448+
requestAnimationFrame(function() {
1449+
requestAnimationFrame(function() {
14371450
mount();
1438-
if (document.getElementById('zd-pillar-bar') || ++attempts > 20) {
1439-
clearInterval(interval);
1440-
}
1441-
}, 50);
1442-
}
1451+
updateActive();
1452+
watch();
1453+
});
1454+
});
14431455
}
14441456
14451457
// Patch history APIs so SPA navigation triggers our updater.
@@ -1460,10 +1472,10 @@ export default defineConfig({
14601472
updateActive();
14611473
});
14621474
1463-
if (document.readyState === 'loading') {
1464-
document.addEventListener('DOMContentLoaded', tryMount);
1475+
if (document.readyState === 'complete') {
1476+
start();
14651477
} else {
1466-
tryMount();
1478+
window.addEventListener('load', start);
14671479
}
14681480
})();
14691481
</script>`;
@@ -1515,10 +1527,21 @@ export default defineConfig({
15151527
15161528
window.addEventListener('zd-locationchange', markExternalLinks);
15171529
1518-
if (document.readyState === 'loading') {
1519-
document.addEventListener('DOMContentLoaded', start);
1530+
// Run only AFTER React hydrates. Marking anchors earlier writes
1531+
// data-zd-external-checked / target / rel onto the server-rendered HTML, so
1532+
// React sees attributes the server didn't emit and reports a hydration
1533+
// mismatch. 'load' + two rAFs lets hydration finish; the MutationObserver
1534+
// then handles every anchor React renders afterward.
1535+
function deferredStart() {
1536+
requestAnimationFrame(function() {
1537+
requestAnimationFrame(start);
1538+
});
1539+
}
1540+
1541+
if (document.readyState === 'complete') {
1542+
deferredStart();
15201543
} else {
1521-
start();
1544+
window.addEventListener('load', deferredStart);
15221545
}
15231546
})();
15241547
</script>`;

0 commit comments

Comments
 (0)