DevOps

Optimización de GitHub: cómo migraron github.com de CSS-in-JS a CSS Modules

TL;DR — Lo que vas a tener al final

  • Tiempo de setup: 30 minutos a 2 horas dependiendo de la complejidad del entorno.
  • Requisitos: Acceso root, Linux moderno, conexión a internet para descargar dependencias.
  • Resultado: Servicio funcional y estable, documentado paso a paso.
  • Troubleshooting: Logs estándar en /var/log/, comandos de verificación al final de cada sección.

El problema: cuando el CSS se vuelve JavaScript

El equipo de GitHub publicó en su blog de ingeniería un caso de estudio detallado sobre cómo migró github.com desde un stack de CSS-in-JS (basado en styled-components + el prop sx + styled-system) hacia CSS Modules estáticas. El resultado, medido en producción: 55% menos tiempo de renderizado en servidor y 25% menos tiempo de inicialización de componentes en el sistema de diseño Primer, seguido de mejoras de entre 1% y 22% en tiempos de SSR a lo largo de github.com una vez que se completó la migración en junio de 2026.

Lo interesante no es solo la cifra, sino el contexto: el problema empezó en 2023 cuando el número de componentes en ciertas páginas creció de forma sostenida. Con un esquema CSS-in-JS, cada componente nuevo añade trabajo de procesamiento en tiempo de ejecución: el navegador tiene que interpretar el objeto de estilos, calcular las clases resultantes, generar e inyectar el CSS en el DOM y, en el servidor, recolectar todos los estilos producidos antes de enviar el HTML. Multiplicado por cientos de componentes por vista, el costo deja de ser marginal.

Cómo funciona CSS-in-JS (y por qué escala mal)

CSS-in-JS resolvió un problema real: co-localización entre componente y estilo, soporte nativo de TypeScript con los design tokens, y la posibilidad de personalizar un componente desde fuera mediante un objeto inline (el famoso prop sx en Primer). Todo eso tiene un precio:

  • El cliente inicializa estilos en tiempo de carga, alargando el time to interactive.
  • El servidor debe recolectar los estilos generados para incluirlos en el HTML antes de enviarlo, lo que retrasa el SSR.
  • Cada actualización de estilo en una página con muchos componentes dispara trabajo extra en el reconciliador.
  • El bundle de CSS crece dinámicamente según qué componentes se renderizan, no en build time.

El propio GitHub reconoce que sx era “lo mejor y lo peor de CSS-in-JS”: excelente DX y tipado fuerte, pero con un costo de runtime que escala con la cantidad de componentes en la página. Un benchmark interno con 1.000 componentes midió 242 ms de inyección de CSS en runtime frente a 96 ms sirviendo archivos CSS estáticos — más del doble.

CSS Modules: misma co-localización, cero runtime

La solución que adoptó Primer fue CSS Modules, una especificación que mantiene la organización por componente pero elimina cualquier trabajo en cliente o servidor:

  • Los estilos se escriben en un archivo .css adyacente al JavaScript del componente.
  • Los nombres de clase son locales por defecto al módulo, evitando colisiones que son el problema clásico del CSS global.
  • Un paso de build transforma esos archivos en hojas de estilo estáticas que se sirven con el HTML de la página.
  • No hay JavaScript que procese estilos, ni en el navegador ni en el servidor.

Un antes y después típico del prop sx hacia CSS Modules se ve así. Primero, la versión con styled-components donde el navegador y el servidor tienen que procesar el objeto de estilos en cada render:

// Button.tsx — versión CSS-in-JS con styled-components
import styled from 'styled-components'
const Button = styled.button<{ $variant?: 'primary' | 'secondary' }>`
  background: ${(p) => (p.$variant === 'primary' ? '#0969da' : '#f6f8fa')};
  color: ${(p) => (p.$variant === 'primary' ? '#fff' : '#1f2328')};
  border: 1px solid ${(p) => (p.$variant === 'primary' ? '#0969da' : '#d0d7de')};
  border-radius: 6px;
  padding: 5px 16px;
  font-weight: 500;
  cursor: pointer;
`
// Uso en una vista con 200+ Buttons:
//   runtime cost = 200 objetos de estilos procesados por el reconciliador

La migración a CSS Modules traslada todo ese trabajo al build time. El componente importa solo un mapeo de nombres de clase, y el navegador recibe un .css estático:

// Button.tsx — versión CSS Modules
import styles from './Button.module.css'
export function Button({ variant = 'secondary', children }: { variant?: 'primary' | 'secondary'; children: React.ReactNode }) {
  const cls = variant === 'primary' ? styles.primary : styles.secondary
  return <button className={cls}>{children}</button>
}
/* Button.module.css — generado por el build a partir de este archivo */
.primary {
  background: #0969da;
  color: #fff;
  border: 1px solid #0969da;
  border-radius: 6px;
  padding: 5px 16px;
  font-weight: 500;
  cursor: pointer;
}
.secondary {
  background: #f6f8fa;
  color: #1f2328;
  border: 1px solid #d0d7de;
  border-radius: 6px;
  padding: 5px 16px;
  font-weight: 500;
  cursor: pointer;
}

El navegador ahora recibe un único Button.[hash].css con ambas clases. Las 200 instancias del componente en la página comparten ese mismo archivo. Cero JavaScript dedicado a procesar estilos.

El navegador termina descargando más bytes de CSS que con una solución runtime, pero a cambio no ejecuta nada: el costo se paga una vez en build time y luego es solo transferencia. En la mayoría de los casos reales, la transferencia es más barata que el cómputo, especialmente en conexiones donde el ancho de banda es razonablemente bueno pero el hilo principal del navegador ya está saturado.

Tabla comparativa: CSS-in-JS runtime vs CSS Modules

Aspecto CSS-in-JS runtime (styled-components / sx) CSS Modules (build time)
Cuándo se procesan los estilos En el cliente y/o servidor, en cada render Una vez, durante el build
Costo de CPU al cargar la página Proporcional al número de componentes con estilos Cero (CSS ya compilado)
Impacto en SSR El servidor debe recolectar estilos antes de enviar HTML CSS viaja como archivo estático junto al HTML
Soporte de tipado / tokens Nativo (los props están tipados) Indirecto (vía variables CSS y tooling)
Co-localización con el componente Inline (objeto JS al lado del JSX) Archivo adyacente componente.module.css
Tema dinámico / theming API JS directa (ThemeProvider, sx) CSS variables + reasignación de atributos
Coste del payload CSS Variable, depende del render Mayor en bytes, fijo y predecible
Dependencias runtime styled-components, styled-system, helpers Ninguna adicional
Curva de adopción incremental Alta fricción si convives con el legacy Feature flags + shims permiten coexistencia

Cómo se hizo la migración en GitHub, paso a paso

Lo más valioso del post de GitHub no son las métricas finales, sino la mecánica. Migrar un sistema de diseño usado por toda la empresa sin romper producción requirió un protocolo específico para cada componente:

  1. Crear un archivo nuevo que traducía los estilos existentes a CSS Modules, sin tocar el código viejo.
  2. Envolver el componente en un feature flag que permitía alternar entre los estilos antiguos y los nuevos sin redeploy.
  3. Usar visual regression tests existentes para confirmar que las capturas de pantalla con la nueva implementación eran idénticas a las del CSS-in-JS.
  4. Ampliar el flag gradualmente: primero el equipo de Primer, luego toda la organización de GitHub, finalmente todos los usuarios.

El sistema viejo seguía funcionando durante toda la migración, así que cada componente podía revertirse individualmente si aparecía un problema en producción. Para diciembre de 2024, todos los componentes de Primer estaban en CSS Modules, y el resto de github.com seguía usando styled-components + sx en el código de aplicación. Esa segunda fase fue la más dura.

Eliminando el prop sx del código de aplicación

Aunque el sistema de diseño ya estaba en CSS Modules, gran parte del código de producto seguía dependiendo de sx. Para junio de 2026 GitHub contaba con un pico de ~7.760 props sx distribuidos por su monorepo. La solución combinó tres herramientas:

  • Un plugin de VS Code creado por Ian Sanders que asistía en la migración prop por prop.
  • Un codemod interno que migraba archivos completos de forma automática.
  • El Copilot coding agent, que se sumó a la rotación en las etapas finales para acelerar la cola larga.

Una rotación de 8 ingenieros migró 6.419 props en 6 meses, con ganancias de SSR que iban del 1% al 22% dependiendo de la página. El patrón paquete por paquete era: traducir el uso de sx a un archivo CSS Modules equivalente, reemplazar los imports de @primer/styled-react por @primer/react, probar en pre-producción y desplegar. GitHub publicó el wrapper @primer/styled-react como una librería transitiva precisamente para mantener compatibilidad mientras se completaba la transición.

El último paso: desacoplar el theming

Cuando terminó la migración de sx, apareció un último obstáculo: GitHub soporta siete temas diferentes más una variante de alto contraste para cada uno, todo activado a través de las utilidades de styled-components. Las variables de tema siempre estuvieron definidas en CSS (@primer/css), pero la lógica JS seguía dependiendo del runtime de styled-components.

El equipo volvió a aplicar el mismo protocolo: refactor + feature flag + rollout gradual. Dos meses después, todo el sistema de theming estaba desacoplado de styled-components, y para junio de 2026 GitHub podía declarar github.com al 100% en CSS Modules, sin sx, sin styled-components y sin styled-system.

Lecciones que aplican a cualquier equipo frontend

El caso de GitHub no es directamente extrapolable a una aplicación React pequeña: las mejoras del 55% se midieron en un sistema con miles de componentes donde el costo de runtime era realmente enorme. En una landing page con 30 componentes, la diferencia probablemente no se note. Pero hay patrones transferibles:

  • El byte descargado no es el único costo: el JS que se ejecuta también cuesta, y a veces cuesta más. Una página que sirve 2.1 MB de CSS estático puede ser más rápida que una que sirve 800 KB de CSS generado en runtime.
  • Las migraciones grandes requieren feature flags por componente, no migraciones big-bang. Sin esa granularidad, revertir un problema en producción es inviable.
  • Visual regression tests son la red de seguridad: sin ellos, una migración visual es indistinguible de un bug introducido.
  • Los codemods automatizan la cola larga: la primera mitad de los archivos se migra a mano, pero cuando la curva se aplana, un codemod bien afinado saca el trabajo adelante sin supervisión constante.
  • El timing importa: GitHub actuó cuando styled-components entró en modo mantenimiento, lo que confirmó que seguir invirtiendo en ese stack era un callejón sin salida.

Cuándo NO vale la pena migrar

No todo proyecto debería correr a hacer lo mismo. Si tu aplicación tiene:

  • Menos de 50 componentes en la vista más pesada.
  • Tiempos de SSR ya dentro de budgets razonables (sub-200 ms en el percentil 95).
  • Un sistema de theming que cambia a menudo y depende de props dinámicas.
  • Un equipo pequeño sin horas para invertir en un codemod + feature flags por componente.

…entonces el costo de la migración probablemente supere al beneficio. CSS-in-JS sigue siendo una opción perfectamente válida para aplicaciones pequeñas y medianas, donde el DX y el tipado compensan el costo de runtime. El caso de GitHub es interesante porque expone cuándo la escala rompe el modelo, no porque el modelo sea inherentemente malo.

Resumen

GitHub migró github.com de styled-components + sx + styled-system a CSS Modules puras entre 2023 y junio de 2026, en tres fases: migración del sistema de diseño Primer (completada en diciembre de 2024, con -55% SSR y -25% init de componentes), eliminación del prop sx del código de aplicación (6.419 props migradas en 6 meses con un equipo rotativo de 8 ingenieros y ayuda de Copilot), y desacoplamiento del sistema de theming (2 meses). El resultado: github.com corre 100% en CSS Modules desde junio de 2026, con mejoras de SSR del 1% al 22% según la página. La lección operativa más replicable es la metodología: feature flag por componente, visual regression tests como red de seguridad, codemods para la cola larga, y rotación de pequeños grupos de ingenieros en lugar de un equipo dedicado durante años.

Preguntas frecuentes

¿Por qué Optimización de GitHub: cómo migraron github.com de CSS-in-JS a CSS Modules es relevante para sysadmins Linux?

Forma parte del flujo diario de administración. Los conceptos explicados se aplican directamente a servidores en producción y resuelven problemas reales que enfrentan sysadmins hispanohablantes.

¿Qué necesito para empezar con Optimización de GitHub: cómo migraron?

Distribución Linux actualizada (Debian, Ubuntu, RHEL, Fedora o Arch), acceso root, y conexión a internet. Para producción: entorno de staging separado antes de aplicar cambios.

¿Cuáles son los errores más comunes con Optimización de GitHub: cómo migraron?

Los tres más frecuentes: (1) saltar la lectura de logs, (2) aplicar cambios directos en producción sin probar, (3) olvidar permisos y ownership. La documentación y un plan de rollback siempre.

¿Cuándo usar Optimización de GitHub: cómo migraron y cuándo una alternativa?

Esta solución es ideal para el caso descrito. Alternativas como Docker, Ansible o configuración manual pueden ser mejores según contexto: portabilidad, escala y reproducibilidad son los factores clave.

¿Cómo migro desde una configuración previa a Optimización de GitHub: cómo migraron?

Documentar el estado actual, probar la nueva configuración en staging, validar funcionalmente, y aplicar en producción con rollback plan listo. Los posts de SoyLinux incluyen troubleshooting para los problemas más comunes.

Lee también

Alfonso Moreno | Sysadmin Senior

Soy Alfonso, sysadmin senior con RHCE y más de 10 años administrando sistemas Linux para empresas y proyectos personales. Me especializo en infraestructura, redes, virtualización, monitoring, automation y self-hosting. Sobre SoyLinux.com SoyLinux.com es un blog técnico en español sobre Linux, software libre y self-hosting. Lo mantengo en mi tiempo libre. Áreas de expertise - Linux server administration: RHEL, CentOS, Rocky, Alma, Fedora, Debian, Ubuntu - Network services: Postfix, BIND, Samba, NFS, Squid - Virtualización y containers: KVM, Proxmox, Docker, Podman - Monitoring: Prometheus, Grafana, Zabbix - Automation: Ansible, Bash scripting - Self-hosting: Vaultwarden, Nextcloud, Jellyfin, Immich, Homelab Contacto - Email: [email protected]

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *