El mismo problema, resuelto tres veces distinto
Operamos tres productos web bajo el mismo paraguas legal — la landing de Lemus Digital, este blog, y Assilek — y los tres necesitan servir contenido en español e inglés. Si uno llega desde afuera esperaría que la solución fuera una sola: elegir un enfoque de internacionalización, documentarlo, y aplicarlo en todos lados. Es lo que hace la mayoría de las guías de i18n que circulan: tratan el bilingüismo como un problema técnico único con una respuesta única (rutas por locale, subdominios, o negociación de idioma por cabecera HTTP).
No es lo que hicimos. La landing sirve los dos idiomas en la misma URL con un toggle de CSS. Este blog le da una URL distinta a cada idioma de cada post. Assilek tiene hreflang completo, sitemap dinámico y traducciones enlazadas por frontmatter. Tres mecánicas distintas para lo que en la superficie parece el mismo problema.
No es indecisión ni falta de un estándar interno. Es que "servir contenido bilingüe" no es un problema — es tres problemas distintos disfrazados del mismo enunciado, y cada uno tiene un objetivo distinto detrás: conversión inmediata, indexación de contenido long-form, y adquisición orgánica sostenida como canal principal de un producto. Cuando el objetivo cambia, la arquitectura correcta cambia con él, aunque el resultado visible —"esta página también existe en inglés"— se vea igual desde afuera.
Landing: una sola URL, el idioma se decide antes del primer pixel
La landing de lemusdigital.com es un solo archivo HTML con ambos idiomas embebidos en el mismo DOM. Cada elemento traducible está duplicado con data-lang="es" o data-lang="en", y una regla CSS (html[lang="es"] [data-lang="en"] { display: none }, y su inversa) oculta el idioma que no está activo. Un script sincrónico en el <head> —antes del primer render— lee localStorage.getItem('ld_lang') si el visitante ya eligió idioma antes, o cae a navigator.language si es su primera visita. Si el navegador reporta inglés, el script pone <html lang="en"> antes de que el usuario vea nada. Sin flash de contenido en el idioma equivocado, sin JavaScript que reescriba el DOM después del render, sin redirect.
Esto significa, en términos de SEO, que Google indexa lemusdigital.com como una página en español. No hay una URL /en separada que Google pueda rankear de forma independiente para búsquedas en inglés. Es una decisión consciente, no una limitación que no vimos: la landing existe para convertir a alguien que ya llegó —por una búsqueda de marca, un enlace directo, una recomendación— no para competir por posiciones orgánicas en dos idiomas. El visitante que llega ve su idioma sin fricción ni URL rara; a cambio, renunciamos a la mitad del SEO potencial de esa página. Para una landing de una sola pantalla con un objetivo de conversión, ese cambio es correcto: UX inmediata contra alcance orgánico que esa página en particular no necesita ganar por sí sola.
El blog: URL por idioma, pero solo cuando el contenido lo amerita
Este blog invierte la lógica. Cada post vive en su propia URL por idioma —/blog/{slug-en-español}/ para el original en español, /blog/en/{slug-in-english}/ cuando existe traducción— enlazadas entre sí con <link rel="alternate" hreflang="es|en">. La razón es la opuesta a la de la landing: contenido long-form vive de la búsqueda orgánica, y Google necesita una URL indexable por idioma para poder rankear cada versión de forma independiente en su mercado. Meter dos idiomas en la misma URL con un toggle de CSS —como hace la landing— sería sabotear la única ventaja competitiva real de un post técnico: aparecer cuando alguien busca, en su idioma, el término exacto que ese post resuelve.
La parte que sí se mantiene igual que en la landing es la interfaz alrededor del contenido: nav, footer, switcher de idioma y CTAs siguen el mismo patrón data-lang bilingüe. Lo que cambia de mecánica es solo el cuerpo del post —el contenido que de verdad compite por posiciones en buscadores— no el envoltorio de navegación, que no tiene ningún valor SEO propio que proteger.
Y una decisión más, menos técnica y más editorial: no todos los posts se traducen. Los evergreen de alto valor —los que van a seguir siendo relevantes en un año y tienen volumen de búsqueda real en ambos idiomas— se traducen. Los posts locales o contextuales se quedan en un solo idioma. Traducir por sistema, sin ese filtro, generaría dos veces el volumen de contenido con la mitad del criterio editorial detrás de cada pieza — el tipo de "cobertura completa" que en la práctica diluye la calidad promedio del blog entero.
Assilek: cuando el producto es el que vende, la inversión se paga sola
Assilek es el caso donde vale la pena ir más lejos que "URL por idioma con hreflang manual". Es un producto SaaS construido en Next.js 14 con App Router, con interfaz completa en inglés y español —no solo el contenido de marketing, la aplicación entera— y un blog propio con posts nativos en ambos idiomas, enlazados entre sí por un campo translations en el frontmatter de cada post. El sitemap no es un archivo estático que alguien edita a mano cada vez que sale contenido nuevo: se genera dinámicamente a partir del contenido que existe, con las etiquetas hreflang correctas ya resueltas para cada URL.
La diferencia frente al blog de Lemus Digital no es de sofisticación técnica por sí misma —sería fácil justificar cualquier inversión de ingeniería con "es más robusto"— sino de qué tan seguido cambia el contenido y qué tan crítico es que la relación entre idiomas nunca se rompa. Un producto que capta clientes activamente por SEO no puede permitirse que un sitemap manual se desactualice, o que alguien olvide agregar el hreflang recíproco cuando publica una traducción nueva. Automatizar esa relación —generar el sitemap y las etiquetas de idioma a partir del estado real del contenido, en vez de mantenerlas a mano en paralelo— es la diferencia entre un canal de adquisición que escala sin supervisión constante y uno que necesita una checklist manual cada vez que se toca.
Ese nivel de inversión no tendría sentido en la landing de Lemus Digital ni en este blog, al menos no todavía. Construir un sitemap dinámico y un sistema de translations vinculadas para nueve posts estáticos sería resolver un problema de escala que no existe: el archivo sitemap.xml con nueve entradas se mantiene a mano en segundos, y el riesgo de que se desincronice es bajo porque el volumen es bajo. La ingeniería de i18n de Assilek existe porque el volumen de contenido y la dependencia del canal orgánico la justifican; replicarla en todos lados por consistencia sería sobre-construir donde no hace falta.
El criterio que usamos para decidir
Simplificado, el criterio detrás de las tres decisiones es este: cuanto más depende una página de que Google la encuentre en cada idioma por separado, más vale la pena pagar el costo de URLs separadas, hreflang, y automatización alrededor de esa relación. Cuanto más depende una página de que un visitante que ya llegó no tenga fricción para ver su idioma, más vale una sola URL con detección automática.
- Landing de conversión, una sola pantalla, tráfico mayormente directo o de marca → una URL, toggle instantáneo, sin hreflang. El costo de SEO perdido es bajo porque esa página no es el canal de descubrimiento.
- Contenido long-form editorial, tráfico mayormente orgánico, publicación regular pero de volumen manejable → URL por idioma, hreflang manual, traducción selectiva solo donde el contenido lo justifica. El costo de mantenimiento manual es aceptable porque el volumen es bajo.
- Producto SaaS donde la adquisición orgánica es un canal core, contenido y superficie de producto creciendo de forma continua → hreflang y sitemap generados a partir del estado real del contenido, no mantenidos a mano. El costo de automatizarlo se paga solo apenas el volumen supera lo que una persona puede mantener sincronizado sin error.
Ese criterio no es exclusivo de i18n. Es el mismo que aplicamos en cualquier decisión de arquitectura entre estos tres productos: la pregunta nunca es "cuál es la forma más robusta de hacer esto" en abstracto, sino "qué tan seguido va a cambiar esto, y qué pasa si se desincroniza". Un sitemap estático que se desactualiza en un blog de nueve posts es un error menor que se corrige en el próximo commit. Un sitemap desactualizado en un producto que vive de SEO es un problema de negocio.
Lo que no hicimos, a propósito
No instalamos una librería de i18n de propósito general (react-i18next, next-intl con toda su superficie, o similar) en la landing ni en este blog. Ninguno de los dos es una aplicación con estado —son HTML estático sin build step— así que la complejidad de una librería pensada para gestionar cientos de claves de traducción en una SPA no compra nada; el patrón data-lang con CSS resuelve el mismo problema con cero dependencias y cero build. Tampoco intentamos retrofitear el sistema de translations de Assilek al blog de Lemus Digital solo por prolijidad arquitectónica entre repos hermanos: son proyectos con volumen y objetivos de negocio distintos, y forzar la misma solución en los dos habría sido optimizar por consistencia de código en vez de por lo que cada producto necesita.
La lección que nos llevamos, más allá de este caso puntual, es que "bilingüe" no es una especificación completa. Es un requisito que hay que aterrizar preguntando primero qué tiene que lograr esa página específica —conversión, descubrimiento orgánico, o adquisición sostenida a escala— antes de elegir la mecánica. La arquitectura correcta sale de esa pregunta, no de copiar la solución que ya funcionó en otro producto del mismo equipo.