Cómo funciona FolioPress: una página, todos los idiomas
La mayoría de las webs hablan un solo idioma. En cuanto un negocio necesita un segundo, todo se complica: un plugin de traducción añadido a la fuerza, una segunda copia de la web que hay que mantener sincronizada o un plan más caro solo para poner «Hello» junto a «Hola».
FolioPress nació de la idea contraria: una página no es una página con traducciones pegadas, sino que es todos sus idiomas a la vez. El título, la dirección, el menú, las etiquetas, lo que ve Google: todo existe en cada idioma que elijas, desde el primer minuto.
Todo empezó con las ganas de sustituir un CMS hecho a medida al que se le habían añadido varios idiomas a la fuerza. Buscando alternativas, descubrí que todas fracasaban estrepitosamente a la hora de integrar contenido en varios idiomas en un creador de webs fácil de usar.
Esto es un vistazo entre bastidores: cómo encaja todo, las concesiones que hay detrás y las cosas que se rompieron por el camino.
A vista de pájaro
Una visita viaja de izquierda a derecha. La torre de control nunca está en ese camino. Solo construye, traslada y cuida los sitios.
Una visita a un sitio FolioPress da tres saltos. Cloudflare la recoge cerca del visitante y filtra la basura. Una puerta de entrada (Traefik, que además se encarga de los certificados HTTPS) mira la dirección y encuentra el sitio correcto. El propio sitio responde, ya sea desde un edificio compartido, donde muchos sitios pequeños conviven puerta con puerta, o desde una casa propia cuando tiene mucho movimiento. A un lado, una torre de control se ocupa de todo lo que un visitante nunca ve: altas, facturación, direcciones web, actualizaciones y comprobaciones de salud, con un pequeño conserje en cada servidor que arranca y detiene los sitios.
Todo es Python, en tres capas que escribí yo: un framework web (pystarter, construido sobre FastAPI), el sistema de gestión de contenidos por encima (foliopress) y la torre de control (Forestry). Los pagos pasan por Polar, que actúa como vendedor oficial (el llamado merchant of record), así que el IVA europeo es su quebradero de cabeza y no el mío.
Cada página, cada idioma
Cada idioma tiene su propia dirección legible y, si quieres, su propio dominio.
Cuando una casa de huéspedes de Lisboa escribe su página «Nosotros», la escribe una vez por idioma, y cada versión recibe una dirección que se lee con naturalidad en ese idioma. Por dentro, una traducción es un registro junto al original, no una copia de la página: títulos, direcciones, menús, etiquetas y descripciones para buscadores existen por idioma. Los visitantes llegan a su idioma automáticamente, y Google sabe exactamente qué página es cuál (gracias a las etiquetas hreflang y a un sitemap por idioma), así que la versión portuguesa aparece en las búsquedas en portugués. ¿Quieres la web alemana en su propio dominio .de? También se puede. Y si prefieres no empezar con una página en blanco, tienes a un clic un primer borrador de cada traducción hecho con IA: tú lo revisas, tú decides.
Cada sitio guarda todo esto en un pequeño archivo de base de datos propio (SQLite). No hay ningún servidor de bases de datos que mantener, la copia de seguridad nocturna fuera del servidor es una simple copia de archivo, y un sitio entero es una carpeta que se puede trasladar, restaurar o borrar de una sola vez.
Los sitios pequeños comparten, los concurridos se mudan
La mayoría de las webs están tranquilas casi todo el tiempo. Darle a cada una su propia maquinaria sería como alquilar una casa entera para alguien que viene dos veces al año. Así que los sitios pequeños comparten un edificio (un proceso, datos estrictamente separados) y la plataforma vigila el tráfico de cada sitio. Cuando uno se anima, se muda a una casa propia. Cuando se calma durante un par de semanas, vuelve.
Compartir edificio solo funciona si las paredes son sólidas. Cuando llega una petición, el proceso compartido mira la dirección y cambia a la base de datos y la configuración de ese sitio solo durante esa petición (con los contextvars de Python), de modo que un sitio nunca ve los datos de otro. El contenedor de cada sitio tiene además su propia red privada con un cortafuegos de salida. Y como los propietarios pueden subir y editar sus propios temas, esas plantillas se ejecutan en un entorno aislado (sandbox) con límites de tiempo, de salida y de memoria.
Decidir qué cuenta como «concurrido» costó varios intentos. Una sola newsletter, o un rastreador con un arrebato, no debería hacer que un sitio parezca popular. Así que la puntuación es el día típico del sitio durante la última semana (la mediana), más lo concurridas que son sus horas punta (la hora del percentil 95), lo que descarta el puñado de horas más disparatadas.
Día de mudanza, sin cerrar la tienda
Cada actualización, y cada mudanza entre el edificio compartido y una casa propia, ocurre mientras el sitio sigue atendiendo a sus visitantes. Los ingenieros lo llaman despliegue blue/green: una nueva copia del sitio arranca junto a la antigua y, solo cuando está sana, la puerta de entrada empieza a enviarle gente. Dos detalles hacen que el cambio sea realmente imperceptible. El conserje no da por hecho que la puerta de entrada ha cambiado: consulta las propias cifras de la puerta de entrada hasta que lo ha hecho. Y a la copia antigua se le pide primero que se «vacíe»: deja de aceptar visitantes nuevos, termina lo que estaba haciendo y solo entonces se apaga.
Cosas que se rompieron
Nada te enseña tanto sobre un sistema como verlo fallar. Algunas de mis favoritas:
La vez que lo tumbé todo yo solito. Para que una página lenta no retrasara a todas las demás, pasé el renderizado de páginas a un grupo de hilos de trabajo. Luego, para comprobar que las mudanzas de sitios no tenían tropiezos, lancé una avalancha de tráfico de prueba contra los sitios en producción. Se congelaron durante nueve minutos y siguieron congelados después de que el tráfico parara. Cada página retenía una conexión a la base de datos mientras pedía una segunda y, con docenas de hilos y un pool de quince conexiones, todos los hilos acabaron esperando a algún otro. Mientras tanto, la comprobación de salud, que nunca tocaba la base de datos, informaba alegremente de que todo iba bien.
La solución: pools de base de datos que no pueden bloquearse mutuamente, un limitador que convierte una sobrecarga en un rápido «inténtalo de nuevo» en lugar de un cuelgue (con un reparto justo para cada sitio, para que uno con mucho tráfico no deje sin recursos a los demás), una comprobación de salud que detecta un proceso atascado, un watchdog que lo reinicia y una norma firme sobre dónde se hacen las pruebas de carga.
El tropiezo de 45 segundos. La mayoría de las mudanzas de sitios eran impecables, pero había un tipo que no: si la mudanza cambiaba las reglas de enrutamiento del sitio, la copia antigua y la nueva describían brevemente la misma ruta de dos formas distintas. La puerta de entrada, sin saber a cuál creer, no servía ninguna durante unos 45 segundos. Todo lo demás en la mudanza era correcto, y precisamente por eso costó un tiempo detectarlo.
Un sitio diminuto al 100 % de CPU. Una tiendecita con unos cientos de visitantes al día no paraba de saturar su procesador. Los culpables no eran los clientes, sino escáneres automáticos que lanzaban más de cien peticiones en diez segundos buscando archivos como /.env o /wp-login.php que un sitio FolioPress nunca tiene. Cada una recibía una preciosa página de «no encontrado» con todo su diseño: unos 70 ms de trabajo, cien veces seguidas.
La solución: esas peticiones reciben ahora un «Not found» de nueve bytes antes de que se haga ningún trabajo de verdad, y Cloudflare rechaza la mayoría antes de que lleguen.
«No se encuentra el servidor» para un sitio que funcionaba. La dirección web de un sitio recién creado se genera durante la configuración. Si algo consultaba el nombre un instante demasiado pronto, los proveedores de internet recordaban «este nombre no existe» durante hasta treinta minutos, aunque el sitio funcionara perfectamente. La solución fue una dirección comodín, para que cualquier nombre bajo foliopress.io exista siempre; los nombres sin sitio reciben una amable página de «aquí no hay ningún sitio».
Vecinos. Alojar muchos sitios puerta con puerta plantea preguntas que una web aislada nunca tiene que responder: qué puede hacerle el código de un sitio a otro, y qué puede reclamar un sitio que no es suyo. Una revisión de seguridad antes de abrir las altas destapó unos cuantos problemas de este tipo. Todos están resueltos: cookies más estrictas, prueba de propiedad para los dominios personalizados y cada sitio montado en su propia carpeta separada, para que ningún sitio pueda llegar a los archivos de otro.
Lo que le diría a alguien que empieza
- Las piezas aburridas y probadas llegan muy lejos. SQLite, un único framework, contenedores sin florituras. La mayoría de los problemas difíciles estaban en las costuras entre ellas.
- Mide antes de creer. La puntuación de tráfico, la caída y los picos de CPU parecían otra cosa al principio.
- Diseña para la mayoría silenciosa. La mayoría de los sitios son pequeños. Que lo pequeño salga barato es lo que hace posible todo lo demás.
Lo que viene
Publicación en redes sociales, diagramas mermaid, pero sobre todo esperar los comentarios de los usuarios para dar forma al futuro.
Si tienes una web que necesita hablar más de un idioma, prueba FolioPress en foliopress.io: los primeros 30 días son gratis. Y si eres de perfil técnico, me encantaría saber cómo lo habrías construido tú.