CDN de imágenes gratuito: GitHub Pages y JS
Entregar imágenes optimizadas con rapidez es crucial para el rendimiento web. Aunque los CDN comerciales ofrecen soluciones robustas, los proyectos públicos pequeños pueden usar GitHub Pages para el alojamiento estático de imágenes y jsDelivr para la entrega en caché de los recursos del repositorio. Ninguno de los dos servicios optimiza esas imágenes automáticamente.
Entender los CDN de imágenes
Un CDN de imágenes (Content Delivery Network) almacena en caché y sirve imágenes desde servidores geográficamente más cercanos a los usuarios, lo que mejora significativamente los tiempos de carga. Optimizar las imágenes a través de un CDN reduce el ancho de banda, mejora el SEO e impulsa el rendimiento web general.
Configurar GitHub Pages
GitHub Pages ofrece alojamiento estático gratuito directamente desde tus repositorios de GitHub:
-
Crea un repositorio de GitHub llamado
image-cdn. -
Clona el repositorio localmente:
git clone https://github.com/your-username/image-cdn.git cd image-cdn -
Crea una carpeta
imagesy agrega tus imágenes. -
Confirma y sube tus cambios:
git add images git commit -m "Add initial images" git push origin main -
Habilita GitHub Pages en la configuración de tu repositorio y selecciona la rama
maincomo fuente.
Ahora puedes acceder a tus imágenes a través de
https://your-username.github.io/image-cdn/images/your-image.jpg.
Limitaciones de GitHub Pages
Ten en cuenta las limitaciones de GitHub Pages:
- Límite flexible de ancho de banda de 100 GB al mes.
- Límite de 1 GB para el tamaño del sitio publicado, con un límite recomendado de 1 GB para el repositorio fuente.
- Sin procesamiento de imágenes integrado.
- El dominio predeterminado de GitHub Pages puede estar bloqueado en algunos entornos corporativos.
Usa esta configuración para los recursos de proyectos públicos pequeños, no para subidas privadas ni para un servicio comercial de alojamiento de imágenes. GitHub Pages restringe las transacciones comerciales y el alojamiento de SaaS. Consulta los límites de uso y la política de uso de jsDelivr antes de depender de cualquiera de los dos servicios.
Integrar un CDN de JavaScript
Usa un servicio de CDN como jsDelivr para almacenar en caché los recursos de repositorios públicos. No es una biblioteca de JavaScript que instalas y tampoco cambia el tamaño de las imágenes por ti.
Haz referencia a tus imágenes a través de jsDelivr:
<img
src="https://cdn.jsdelivr.net/gh/your-username/image-cdn/images/your-image.jpg"
alt="Optimized Image"
/>
Fija una etiqueta de versión o un commit en la URL para obtener un almacenamiento en caché
predecible, por ejemplo
https://cdn.jsdelivr.net/gh/your-username/image-cdn@v1.0.0/images/your-image.jpg después de crear esa
etiqueta. Actualiza la versión cuando cambien las imágenes.
Fiabilidad y respaldo
Aunque jsDelivr ofrece un rendimiento excelente, implementa estrategias de respaldo:
function loadImage(imageElement, primarySrc, fallbackSrc) {
imageElement.onerror = function () {
imageElement.onerror = null
console.warn('Primary CDN failed, using fallback')
imageElement.src = fallbackSrc
}
imageElement.src = primarySrc
}
const img = document.getElementById('my-image')
if (!(img instanceof HTMLImageElement)) throw new Error('Missing image element: my-image')
loadImage(
img,
'https://cdn.jsdelivr.net/gh/your-username/image-cdn/images/image.jpg',
'https://your-username.github.io/image-cdn/images/image.jpg',
)
Agrega un elemento <img id="my-image" alt="..."> con un texto alternativo adecuado antes de
ejecutar este código. Si ambos orígenes fallan, el navegador conserva el texto alternativo de la
imagen en lugar de reintentar el respaldo indefinidamente.
Automatizar con GitHub Actions
Automatiza la optimización y el despliegue de imágenes con GitHub Actions:
Crea .github/workflows/compress-images.yml:
name: Compress Images
on:
pull_request:
paths:
- '**.jpg'
- '**.jpeg'
- '**.png'
- '**.webp'
jobs:
build:
if: github.event.pull_request.head.repo.full_name == github.repository
name: calibreapp/image-actions
permissions:
contents: write
pull-requests: write
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v4
- name: Compress Images
uses: calibreapp/image-actions@26d5b54006db4da7a1b29903f33404a6f77b58cf
Este flujo de trabajo optimiza las imágenes en los pull requests del mismo repositorio y puede confirmar los cambios y publicar un resumen. Los pull requests desde forks se excluyen deliberadamente. Revisa la acción antes de actualizar el commit fijado; consulta su configuración.
Implementar una optimización personalizada
Para tener más control, instala sharp en un proyecto con Node.js 22 o posterior y guarda esto como optimize.cjs.
Coloca las imágenes de origen en original-images/ y ejecuta node optimize.cjs. Las salidas van a un directorio
aparte; los archivos existentes allí se reemplazan. Los ajustes de calidad de JPEG y WebP tienen
pérdida, y establecer quality en PNG habilita la cuantización de paleta, que también puede perder colores:
const sharp = require('sharp')
const fs = require('fs').promises
const path = require('path')
async function optimizeImage(inputPath, outputPath) {
try {
const image = sharp(inputPath)
const metadata = await image.metadata()
const config = {
jpeg: { quality: 80, progressive: true },
png: { quality: 80, compressionLevel: 9 },
webp: { quality: 80 },
}
switch (metadata.format) {
case 'jpeg':
await image.jpeg(config.jpeg).toFile(outputPath)
break
case 'png':
await image.png(config.png).toFile(outputPath)
break
case 'webp':
await image.webp(config.webp).toFile(outputPath)
break
default:
throw new Error(`Unsupported format: ${metadata.format}`)
}
console.log(`Optimized: ${path.basename(inputPath)}`)
return outputPath
} catch (error) {
console.error(`Failed to optimize ${inputPath}:`, error)
throw error
}
}
async function processDirectory(inputDir, outputDir) {
try {
await fs.mkdir(outputDir, { recursive: true })
const files = await fs.readdir(inputDir)
for (const file of files) {
const inputPath = path.join(inputDir, file)
const outputPath = path.join(outputDir, file)
const stat = await fs.stat(inputPath)
if (stat.isFile() && /\.(jpe?g|png|webp)$/i.test(file)) {
await optimizeImage(inputPath, outputPath)
}
}
console.log('All images processed successfully')
} catch (error) {
console.error('Directory processing failed:', error)
throw error
}
}
processDirectory('original-images', 'optimized-images').catch(() => {
process.exitCode = 1
})
Probar tu configuración
Verifica tu CDN:
-
Herramientas de desarrollo del navegador: revisa el panel Red.
-
Herramientas de pruebas de rendimiento:
-
Monitoreo de usuarios reales: usa la API Performance:
document.addEventListener('DOMContentLoaded', () => { window.addEventListener('load', () => { const imageEntries = performance .getEntriesByType('resource') .filter((entry) => entry.initiatorType === 'img') const totalLoadTime = imageEntries.reduce((sum, entry) => sum + entry.duration, 0) if (imageEntries.length === 0) return const avgLoadTime = totalLoadTime / imageEntries.length console.log(`Average image load time: ${avgLoadTime.toFixed(2)}ms`) imageEntries.forEach((entry) => { console.log(`${entry.name}: ${entry.duration.toFixed(2)}ms`) }) }) })
Seguridad
Implementa estas medidas de seguridad:
Encabezados CORS
GitHub Pages no usa un archivo _headers para configurar los encabezados de respuesta HTTP. Comprueba los
encabezados de respuesta reales con curl -I y usa un host con encabezados configurables cuando tu
aplicación requiera una política personalizada de CORS o de caché. CORS controla el acceso de los
scripts a los bytes de la respuesta; no es autenticación y no impide la incrustación habitual de
imágenes.
Evitar el hotlinking
El JavaScript del lado del cliente no puede impedir que otro sitio incruste URL de imágenes públicas. Si te importan el control de acceso o los límites de tráfico, usa un proveedor que aplique URL firmadas u otra política del lado del servidor. No guardes imágenes privadas o sensibles en este repositorio público.
Optimización avanzada
Imágenes adaptables
<img
srcset="
https://cdn.jsdelivr.net/gh/your-username/image-cdn/images/image-small.jpg 480w,
https://cdn.jsdelivr.net/gh/your-username/image-cdn/images/image-medium.jpg 800w,
https://cdn.jsdelivr.net/gh/your-username/image-cdn/images/image-large.jpg 1200w
"
sizes="(max-width: 600px) 480px,
(max-width: 1200px) 800px,
1200px"
src="https://cdn.jsdelivr.net/gh/your-username/image-cdn/images/image-large.jpg"
alt="Responsive Image"
loading="lazy"
/>
Formatos de imagen modernos
Usa un sistema de detección de formatos y respaldo:
<picture>
<source
srcset="https://cdn.jsdelivr.net/gh/your-username/image-cdn/images/image.avif"
type="image/avif"
/>
<source
srcset="https://cdn.jsdelivr.net/gh/your-username/image-cdn/images/image.webp"
type="image/webp"
/>
<img
src="https://cdn.jsdelivr.net/gh/your-username/image-cdn/images/image.jpg"
alt="Optimized image with format fallbacks"
loading="lazy"
/>
</picture>
Beneficios y limitaciones
Beneficios:
- Sin cargos de alojamiento para proyectos públicos pequeños dentro de las políticas de uso de los proveedores.
- Configuración y automatización sencillas.
- Mejor rendimiento global.
Limitaciones:
- GitHub Pages tiene un límite flexible de ancho de banda de 100 GB/mes y un límite de 1 GB para el sitio publicado.
- Menos control en comparación con los CDN comerciales.
- Sin transformación de imágenes integrada.
Para proyectos más grandes o necesidades avanzadas, considera servicios como la API de procesamiento de imágenes de Transloadit.
Al seguir estos pasos, has creado un CDN de imágenes gratuito con GitHub Pages, un CDN de JavaScript y GitHub Actions.
