@@ -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