FrontendClient-sideTheme Sync

Embeds de Twitter (X)

Los embeds oficiales de Twitter son pesados y bloquean el hilo principal. Nuestra implementación retrasa la carga del script de Twitter hasta que el navegador está inactivo, y sincroniza dinámicamente el tema de los tweets con el de la web.

El problema con los tweets oficiales

Bloqueo de Renderizado: Insertar un <blockquote class="twitter-tweet"> clásico junto a widgets.js en el HTML inicial bloquea el renderizado (render-blocking), retrasa el LCP y carga recursos pesados incluso si el tweet está muy abajo en el artículo.

Cómo se guarda en la base de datos

El editor Tiptap guarda los embeds de Twitter como enlaces con data-embed="true":

<!-- Ambos formatos funcionan -->
<a href="https://x.com/astrodotbuild/status/1760000000000" data-embed="true">[Tweet Astro]</a>
<a href="https://twitter.com/astrodotbuild/status/1760000000000" data-embed="true">[Tweet Astro]</a>

Soporta tanto URLs de twitter.com como de x.com.


Instalación de Dependencias

Zero Dependencies

A diferencia de YouTube, esta implementación es 100% nativa (Vanilla JS). No necesitas instalar paquetes de npm ni wrappers. Solo copiar la lógica SSR e inyectar el script oficial dinámicamente.

Ningún paquete de npm requerido

Pipeline SSR: del enlace al placeholder

1. Regex de detección

const twitterEmbedRegex = /(?:<p>)?\s*<a\s+(?:[^>]*?\s+)?href=["'](https?:\/\/(?:twitter\.com|x\.com)\/[^/]+\/status\/\d+)[^"']*["']\s+data-embed=["']true["'][^>]*>\[([^\]]+)\]<\/a>\s*(?:<\/p>)?/gi;

2. El bug de x.com

Fallo Silencioso: El script oficial de Twitter (widgets.js) falla silenciosamente si el enlace del blockquote usa x.com. Para solucionarlo, el SSR fuerza el cambio a twitter.com antes de generar el HTML.
// CRÍTICO: forzar twitter.com antes de insertar el placeholder
let embedUrl = url;
if (embedUrl.includes('x.com')) {
    embedUrl = embedUrl.replace('x.com', 'twitter.com');
}

3. El placeholder SSR

El SSR reemplaza el <a> por un contenedor vacío que Twitter hidratará después:

<div class="twitter-embed my-8 flex justify-center w-full">
    <blockquote class="twitter-tweet" data-dnt="true" data-theme="dark">
        <a href="URL_DEL_TWEET"></a>
    </blockquote>
</div>

Pipeline Client-side: Hidratación Inteligente

Sincronización inicial del tema

Para evitar un "flashazo" blanco si el usuario está en modo oscuro, leemos el tema actual antes de cargar Twitter:

// Aplicar tema ANTES de cargar widgets.js
const theme = document.documentElement.getAttribute('data-theme') === 'light' ? 'light' : 'dark';
document.querySelectorAll('.twitter-tweet').forEach(el => {
    el.setAttribute('data-theme', theme);
});

Observador de mutaciones (Live Theme Sync)

Si el usuario cambia el tema después de que el tweet haya cargado, modificamos la URL del iframe ya inyectado:

new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
        if (mutation.attributeName === 'data-theme') {
            const newTheme = document.documentElement.getAttribute('data-theme') === 'light' ? 'light' : 'dark';
            
            // Buscar iframes inyectados por Twitter
            document.querySelectorAll('.twitter-tweet-rendered iframe').forEach(iframe => {
                let src = iframe.getAttribute('src');
                if (src && src.includes('theme=')) {
                    iframe.setAttribute('src', src.replace(/theme=[^&]+/, 'theme=' + newTheme));
                }
            });
        }
    });
}).observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });

Carga diferida con requestIdleCallback

El script de Twitter (widgets.js) solo se descarga e inyecta cuando el navegador no tiene otras tareas pendientes:

if ('requestIdleCallback' in window) {
    requestIdleCallback(() => loadTwitterScript());
} else {
    setTimeout(loadTwitterScript, 1000);
}

Compatibilidad con View Transitions

Al cambiar de página sin recargar completa (View Transitions), Twitter no sabe que debe escanear el nuevo DOM. Se lo ordenamos manualmente:

document.addEventListener('astro:page-load', function() {
    if (window.twttr && window.twttr.widgets) {
        window.twttr.widgets.load();
    }
});

Diagnóstico de Errores Comunes

El blockquote se ve como texto plano

Causa: La URL usa x.com en lugar de twitter.com en el href del blockquote.

Solución: Revisar que el replace() del SSR esté funcionando (ver "bug de x.com").

Tweets no cargan al navegar entre posts

Causa: El evento astro:page-load de View Transitions no disparó twttr.widgets.load().

Solución: Asegurarse que window.twttr existe en el scope global y el observer sigue activo.