MyWebsite
Llevo toda la vida haciendo páginas web, pero nunca una para mí mismo. Aquí está MyWebsite, un espacio profesional para compartir mis propios proyectos con el mundo. Como puedes ver, no es el diseño más complicado ni el más extravagante, pero es minimalista, sencillo y limpio. El atributo principal que quería conservar en mi web es su velocidad. Por eso no uso frameworks externos ni plataformas como WordPress: solo HTML, CSS y JavaScript. En este post te cuento las claves de mi diseño y cómo funciona.
Estructura de la web
La práctica más importante al crear un proyecto es estructurar bien la carpeta y todos los documentos siguiendo buenas prácticas. Para ayudar a otros, he creado una plantilla de estructura de web que se puede descargar y usar para empezar a construir tu sitio.
HTML
Para complacer al Sr. Google, todas las meta tags están escritas con cuidado, con títulos y descripciones que buscan el mejor SEO posible para mi marca personal y mis proyectos. Para darle al usuario una experiencia visual completa, es fundamental tener todos los favicons posibles, banners para compartir en redes sociales o el color de tema de Chrome en móviles. El body se divide en tres grandes bloques usando las etiquetas descriptivas que incorpora HTML5.
<body>
<header> ... </header>
<main> ... </main>
<footer> ... </footer>
</body>
CSS
a.css es la hoja de estilos principal de toda la web y siempre debe cargarse primero para que todo se vea bien. Para elementos o estilos específicos que solo hacen falta en ciertas páginas hay otros ficheros .css, como el del formulario de contacto o el del menú hamburguesa móvil. La técnica que siempre uso al empezar un CSS general nuevo es fijar estos estilos.
* {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
}
Algunos lo llaman «CSS Reset» porque elimina todas las diferencias entre navegadores y nunca más te vas a encontrar cosas raras que no esperabas. En una web grande esto podría ser un problema, porque es pesado para el motor de renderizado aplicar reglas a todos y cada uno de los elementos del documento. Sin embargo, para una web como la mía funciona bien.
JS
a.js es el motor de la web: controla todos los elementos dinámicos, como los colores, los eventos de clic o el copiado al portapapeles, y gestiona todo el CSS para la representación correcta de la web. Un patrón que suelo seguir en mi objeto JS es hacer un juego de palabras con el nombre de la variable y la función de arranque, por ejemplo este usando mi apellido (Elso).
const el = {
events: () => { ... },
load: () => { ... },
so: () => {
el.load();
el.events();
}
};
el.so();
Otra buena práctica es colocar las funciones del objeto en orden ascendente, de forma que una función siempre llame a funciones que están por encima. La función load controla todos los elementos en el primer momento en que se carga la página, y la función events controla todo lo que necesita cambiar en momentos concretos.
Diseño
¿Por qué una interfaz clara? Para mí, una interfaz clara representa limpieza, y sigue siendo el aspecto por defecto de la web. ¿Por qué el cambio de colores? El color de acento más usado en mis diseños anteriores era el azul. Es hora de innovar, y sin ningún otro color preferido, ¿por qué no usarlos todos? La idea es captar la atención visual del usuario, ya que no hay imágenes de colores.
Distribución y espacios
Esto es algo que aprendí de mi mentor Fernan García de Zúñiga: «todos los números tienen que ser múltiplos de ocho», y siempre lo tengo presente al programar una interfaz. El sistema de cuadrícula de 8 puntos respeta la variedad de tamaños de pantalla y densidades de píxeles de todos los dispositivos. Así, la altura o el ancho, el margin o el padding, siempre serán un incremento de 8, lo que le da al usuario una sensación estética consistente.
Cambio de colores
Si te has fijado, cada vez que cargas una página, el color de acento que se usa en toda la web empieza siendo uno distinto cada vez y luego recorre el resto mediante @keyframes animados. La implementación original barajaba un array de 8 colores con el algoritmo Fisher-Yates en cada carga e inyectaba un bloque <style> nuevo con los keyframes calculados — tuve que rediseñarlo cuando endurecí la Content-Security-Policy de la web para quitar 'unsafe-inline' de style-src, ya que eso bloquea tanto un atributo style="" como cualquier elemento <style> generado por JavaScript en tiempo de ejecución.
La solución mantiene el mismo efecto visual con un CSS totalmente estático: 8 clases de «paleta» precalculadas, cada una reasignando las mismas 8 custom properties (--c0 a --c7) que los @keyframes ya referencian, así que la animación en sí nunca cambia — solo cambia qué color suena en cada paso. Al cargar, la web elige al azar una de las 8 rotaciones y añade la clase correspondiente a <html>; no se genera nada, solo se alterna una clase, algo que la CSP sí permite.
.palette-1 { --c0:#086D72; --c1:#197209; --c2:#6B6407; --c3:#8E550C; --c4:#C50D0D; --c5:#BF0D5E; --c6:#A80DBA; --c7:#0D61B9; }
/* ...palette-2 through palette-7, each one rotation of the same 8 colors */
@keyframes color-animation {
0% { color: var(--c0); }
12.5% { color: var(--c1); }
/* ...through 100% { color: var(--c0); } */
}
load: () => {
const n = Math.floor(Math.random() * 8);
if (n > 0) {
document.documentElement.classList.add(`palette-${n}`);
}
}
Tipografía
Roboto es una tipografía sans-serif de Google, quizá la aproximación más cercana a Helvetica. La describen como «moderna pero accesible», y es cierto: es una tipografía limpia, de diseño geométrico y directo, con líneas limpias, algo redondeadas y con cierta emoción. Para el módulo de código uso otra tipografía de la misma familia, Roboto Mono. La tipografía monoespaciada siempre se ha usado en lenguajes de programación (editores de texto y terminales), así que esta tipografía trae enseguida a la mente la idea de que estás leyendo código fuente.
Modo oscuro
La interfaz clara descrita arriba sigue siendo la opción por defecto, pero ya no es la única: la web ahora respeta la preferencia del sistema del visitante (prefers-color-scheme) y ofrece un interruptor manual de sol/luna en la cabecera, persistido en localStorage. Casi todos los colores de a.css ya son una custom property (--color-black/--color-white/--color-bg hacen de «tinta» y «papel»), así que la mayor parte del tema oscuro consiste en cambiar esos tokens dentro de una media query y un bloque [data-theme="dark"] — un puñado de superficies que son deliberadamente oscuras en ambos temas (el menú móvil, el footer) mantienen colores literales en su lugar, a propósito.
La única parte delicada es evitar un parpadeo del tema equivocado al cargar: la elección del interruptor tiene que aplicarse antes del primer pintado, pero no puede ser inline (otra vez la CSP). La solución es un script externo diminuto, theme-init.js, cargado de forma bloqueante como lo primero de todo en <head>, antes de cualquier hoja de estilos, que lee la preferencia guardada en localStorage y aplica [data-theme] en <html> de inmediato.
Formulario de contacto
Actualmente, MyWebsite está alojada en el servicio Pages de Cloudflare, que facilita crear y publicar webs. Sin embargo, este servicio no permite ejecutar código en el backend, así que solo se pueden publicar webs estáticas. El formulario en sí se puede crear con HTML y CSS, pero para implementar la lógica de procesado de mensajes necesito ejecutar código en el backend. En vez de mover la web a otro hosting, aprovecho las herramientas que ofrece AWS para implementar un backend serverless que se encargue de esa lógica.
Consulta el post completo donde detallo cómo implementé el formulario de contacto con un backend serverless en AWS.
Calidad de la página
Lighthouse es una herramienta de código abierto y automatizada para mejorar la calidad de las páginas web. Si generas un informe sobre mi web, obtendrás ese resultado. ¡Genial! El Sr. Google está contento conmigo.
Rendimiento
Hay que incluir un preconnect en el head para los recursos externos con los que voy a trabajar, por ejemplo el uso de servicios de terceros como Google Analytics. También tengo que precargar (preload) las tipografías de la página.
<link rel="preconnect" href="https://www.google-analytics.com">
<link rel="preload" href="/font/RobotoMono-Regular.woff2" as="font" crossorigin="anonymous">
Es importante tener imágenes con el tamaño adecuado, algo que consigo con herramientas de compresión online u otros programas que no reducen la calidad de la foto a ojos humanos. Minificar los ficheros CSS y JS, activar la compresión Gzip en las peticiones de tus recursos, usar HTTP/2 y desarrollar una política de caché eficiente mejorará el rendimiento de la web.
Esta web implementa lazy loading, una técnica que mejora el rendimiento retrasando la carga de las imágenes hasta que hacen falta. Cuando un usuario visita la página, solo se cargan las imágenes visibles en pantalla. El resto de imágenes se quedan sin cargar hasta que el usuario baja por la página. Esto puede reducir mucho la cantidad de datos que hay que transferir, haciendo que la página cargue más rápido, sobre todo en móviles o con una conexión a internet lenta.
<img src="/img/MyWebsite/distancias.png" width="1248" height="571" alt="Distancias" loading="lazy">
Antes lo implementaba a mano con la Intersection Observer API: vigilaba cuándo cada imagen entraba en el viewport y entonces intercambiaba un atributo data-src por src. Los navegadores lo alcanzaron: el atributo nativo [loading="lazy"] hace ahora exactamente eso, sin JavaScript y sin riesgo de imagen rota si falla la carga de un script. Todas las imágenes lo llevan excepto la primera de cada página, la que está por encima del pliegue, que debe cargar de inmediato en vez de esperar a ser observada.
Accesibilidad
Los colores de fondo y de primer plano tienen un ratio de contraste suficiente (al menos 4.5:1), que se puede comprobar con la herramienta de inspección o con webs específicas. El elemento html tiene «es» como atributo [lang], los atributos [id] de la página son únicos, todas las páginas tienen un elemento «title» de menos de 70 caracteres, y todos los elementos de imagen tienen atributos [alt].
El diseño responsive es una forma de construir una web para que escale automáticamente su contenido y sus elementos según el tamaño de pantalla en el que se vea. Hay distintas formas de conseguirlo, como usar media queries en CSS, que pueden ser útiles pero requieren mucho código para los distintos tamaños de pantalla. Una práctica mejor es usar contenedores redimensionables sin un tamaño fijo. Solo hay dos cambios significativos entre móvil y escritorio: cómo se muestran los elementos de la cabecera, y ocultar el mensaje de copyright del footer cuando el ancho de pantalla es menor de 800px. Este es un ejemplo del contenedor div que siempre uso para fijar un ancho máximo (para las pantallas más grandes) y un ancho calculado cuando el tamaño de pantalla es menor que el máximo.
div.container {
max-width: 1248px;
padding: 0 16px;
width: calc(100% - 32px);
margin: 0 auto;
}
Buenas prácticas
Usa HTTPS en vez de HTTP, usa HTTP/2 para tus propios recursos, asegúrate de que los enlaces a destinos de otro origen son seguros (usando rel="noopener"), incluye el doctype de HTML, comprueba que no haya errores de navegador en la consola, y muestra las imágenes con la relación de aspecto correcta.
SEO
El documento tiene un rel=canonical válido, y esto es importante porque hay que fijar el canonical a la URL de la propia página en todas las páginas. Si no lo haces, dejas el SEO de esta página en manos de otra página que hayas indicado. Si el contenido de la página existe en otros idiomas, hay que indicarlo en el hreflang, y también hay que incluir la propia página con su idioma.
<link href="https://jaimeelso.com/es/post/mywebsite" rel="canonical" />
<link rel="alternate" hreflang="es" href="https://jaimeelso.com/es/post/mywebsite" />
<link rel="alternate" hreflang="en" href="https://jaimeelso.com/post/mywebsite" />
En el robots.txt se especifica cómo quieres que los rastreadores indexen tu web, y también se indica dónde está el fichero sitemap.xml.
Progressive Web App
Las PWA (Progressive Web Apps) son una forma sencilla de transformar tu web en aplicaciones de escritorio, Android o iOS. La web ya incluye un manifest.json con iconos y metadatos, así que es instalable. Lo que deliberadamente no tiene es un service-worker.js: el soporte offline y la lógica de caché son una complejidad real para una web estática sin funcionalidades interactivas que preservar offline, así que lo evalué y decidí no implementarlo — prefiero añadir esa complejidad cuando haya una razón concreta para ello, en vez de construirla de forma especulativa.
¡Gracias por leer! Nos vemos en el próximo.