<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Efren Muñoz Álvarez</title>
    <description>The latest articles on DEV Community by Efren Muñoz Álvarez (@efrenmunoz).</description>
    <link>https://dev.to/efrenmunoz</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4027562%2F00639035-443c-411d-a76c-ccebda793277.png</url>
      <title>DEV Community: Efren Muñoz Álvarez</title>
      <link>https://dev.to/efrenmunoz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/efrenmunoz"/>
    <language>en</language>
    <item>
      <title>3 bugs que encontré auditando mi propio portafolio en React (y que Lighthouse no te explica bien)</title>
      <dc:creator>Efren Muñoz Álvarez</dc:creator>
      <pubDate>Mon, 13 Jul 2026 16:45:28 +0000</pubDate>
      <link>https://dev.to/efrenmunoz/3-bugs-que-encontre-auditando-mi-propio-portafolio-en-react-y-que-lighthouse-no-te-explica-bien-3fmo</link>
      <guid>https://dev.to/efrenmunoz/3-bugs-que-encontre-auditando-mi-propio-portafolio-en-react-y-que-lighthouse-no-te-explica-bien-3fmo</guid>
      <description>&lt;p&gt;Hace poco me puse a auditar mi portafolio (&lt;a href="https://efrenmunoz.cl" rel="noopener noreferrer"&gt;efrenmunoz.cl&lt;/a&gt;) con Lighthouse en serio, no solo para sacar un número bonito sino para entender qué estaba pasando de verdad. Performance venía en 83. Terminé en 100. Pero lo interesante no fue subir el número — fue lo que encontré en el camino. Tres bugs concretos que no son obvios a simple vista, y que probablemente tengas en tu propio proyecto si usas React con code-splitting o Helmet.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. El CLS estaba escondido en un &lt;code&gt;Suspense&lt;/code&gt; vacío
&lt;/h2&gt;

&lt;p&gt;Cumulative Layout Shift (CLS) mide cuánto "salta" tu página mientras carga. El mío estaba en 0.343 — bastante mal (Google considera "bueno" cualquier valor bajo 0.1).&lt;/p&gt;

&lt;p&gt;Lo raro es que Lighthouse me apuntaba al &lt;code&gt;&amp;lt;footer&amp;gt;&lt;/code&gt; como el elemento que más se movía. Pero el footer no tenía animaciones ni nada raro. El problema estaba más arriba, en cómo cargaba cada ruta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Suspense&lt;/span&gt; &lt;span class="na"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Loader&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Home&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Suspense&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ese &lt;code&gt;fallback={&amp;lt;Loader /&amp;gt;}&lt;/code&gt; renderiza &lt;strong&gt;literalmente nada&lt;/strong&gt; mientras el chunk de la página se descarga. Como el layout usa &lt;code&gt;flex-col&lt;/code&gt; con el &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt; en &lt;code&gt;flex-1&lt;/code&gt;, en teoría debería estirarse para llenar la pantalla igual... pero el contenido real de una página como Home es mucho más alto que un viewport. Entonces pasaba esto:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Primer render: casi no hay contenido → el footer queda pegado casi arriba.&lt;/li&gt;
&lt;li&gt;El chunk de la página carga (rápido, ~100-200ms, pero después del primer paint).&lt;/li&gt;
&lt;li&gt;El contenido real se monta → la página crece varios largos de pantalla de golpe → el footer salta hacia abajo bruscamente.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ese salto grande, aunque dure milisegundos, cuenta completo para el CLS. La solución fue ridículamente simple una vez que entendí la causa:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"min-h-screen"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Con eso, el placeholder ya reserva una pantalla completa de alto desde el primer render. CLS bajó de 0.343 a 0.082, y después a 0 puro en la mayoría de las páginas.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. React Helmet no limpia las meta tags que no puso él
&lt;/h2&gt;

&lt;p&gt;Para que WhatsApp y LinkedIn generaran una vista previa decente al compartir el link (imagen, título, descripción), agregué meta tags de Open Graph directo en el &lt;code&gt;index.html&lt;/code&gt; estático — porque esos bots no ejecutan JavaScript, así que nunca ven lo que React Helmet inyecta dinámicamente.&lt;/p&gt;

&lt;p&gt;Lo que no consideré: cuando la app carga y Helmet mete sus propias tags (&lt;code&gt;og:title&lt;/code&gt;, &lt;code&gt;og:description&lt;/code&gt;, etc.), &lt;strong&gt;no borra las que ya estaban ahí&lt;/strong&gt;. Terminé con dos &lt;code&gt;&amp;lt;meta name="description"&amp;gt;&lt;/code&gt; en cada página — una estática, una dinámica — y encima con valores distintos. Un crawler que sí ejecuta JS (Google, Ahrefs) veía ambas, sin saber cuál priorizar.&lt;/p&gt;

&lt;p&gt;La solución: un script inline en el &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;, antes de que React cargue, que borra las tags estáticas apenas hay JavaScript disponible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;data-fallback=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"description"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- ...resto de tags og:*/twitter:* con data-fallback="true" --&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;meta[data-fallback="true"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bots sin JS (WhatsApp, LinkedIn) siguen viendo las tags estáticas normal. Cualquiera que sí ejecute JS las ve desaparecer antes de que Helmet monte las suyas — cero duplicados, sin tener que elegir entre "buena vista previa" o "meta tags limpias".&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Un mismo archivo de imagen sirviendo dos propósitos distintos
&lt;/h2&gt;

&lt;p&gt;Tenía imágenes de proyecto que se usaban como thumbnail en una grilla (mostradas a ~400px de ancho) y como imagen completa en una galería/lightbox (potencialmente pantalla completa). Estaba usando el mismo archivo para ambas cosas — lo que significaba servir una imagen de 2500px de ancho para mostrarla a 400px en la grilla. Un desperdicio de casi 90% del peso descargado, solo en esa página.&lt;/p&gt;

&lt;p&gt;La solución no fue "achicar la imagen" (eso arruina la galería), sino generar una segunda versión liviana específica para el thumbnail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;magick original.webp &lt;span class="nt"&gt;-resize&lt;/span&gt; 1600x1600&lt;span class="se"&gt;\&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;-quality&lt;/span&gt; 82 original-thumb.webp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y en los datos del proyecto, agregar un campo aparte:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/images/proyecto.webp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;// galería, tamaño completo&lt;/span&gt;
  &lt;span class="nx"&gt;thumb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/images/proyecto-thumb.webp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// solo para la grilla&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cada imagen se sirve en su contexto correcto. La grilla bajó de ~935 KB a menos de 100 KB en total.&lt;/p&gt;




&lt;p&gt;Ninguno de estos tres bugs sale bonito en un dashboard — Lighthouse te dice "CLS alto" o "imagen muy grande", pero no siempre te dice &lt;em&gt;por qué&lt;/em&gt;. La única forma de encontrarlos fue meterse al DOM real y cuestionar cada suposición sobre cómo React estaba renderizando las cosas.&lt;/p&gt;

&lt;p&gt;Si te está pasando algo parecido en tu proyecto, o quieres que revise el tuyo, puedes escribirme desde &lt;a href="https://efrenmunoz.cl/contacto" rel="noopener noreferrer"&gt;efrenmunoz.cl/contacto&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>spanish</category>
      <category>webdev</category>
      <category>react</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
