Volver al blog

JPEG XL Llega al PDF: Cómo el Nuevo Formato Va a Revolucionar Performance Web y Optimización de Imágenes en 2025

Hola HaWkers, la PDF Association acaba de anunciar la adopción oficial de JPEG XL (JXL) en la especificación PDF 2.0, marcando un momento histórico para el formato de imagen más prometedor de los últimos años.

Para nosotros desarrolladores web, esto no es solo otra noticia técnica — es la señal definitiva de que JPEG XL llegó para quedarse y va a transformar cómo optimizamos imágenes, mejoramos Core Web Vitals y construimos experiencias web más rápidas.

Vamos a sumergirnos en lo que hace especial a JPEG XL, cómo usarlo hoy, y por qué deberías comenzar a implementarlo en tus proyectos.

Qué Es JPEG XL y Por Qué Importa

Historia y Contexto

JPEG XL - Timeline:

  • 2017: Google desarrolla PIK (predecesor de JXL)
  • 2018: Cloudinary desarrolla FUIF (otro predecesor)
  • 2019: PIK + FUIF se fusionan en el proyecto JPEG XL
  • 2021: JPEG XL 1.0 finalizado y estandarizado
  • 2022: Apple añade soporte nativo en Safari 17 (macOS/iOS)
  • 2023: Chrome remueve soporte (polémica)
  • 2024: Chrome reactiva soporte experimental
  • 2025: PDF Association adopta oficialmente + Chrome planea soporte nativo

Por Qué JPEG XL Es Superior

Comparación técnica:

Feature JPEG PNG WebP AVIF JPEG XL
Compresión lossy
Compresión lossless
Transparencia (alpha)
Animación
HDR support
Progressive decode
Velocidad encode ⚡⚡⚡ ⚡⚡ 🐌 ⚡⚡
Velocidad decode ⚡⚡⚡ ⚡⚡⚡ ⚡⚡ ⚡⚡⚡
Tamaño vs JPEG 100% 200%+ 75-80% 50-60% 50-60%
Browser support 100% 100% 95% 80% 40%

Ventajas únicas de JPEG XL:

  1. Compresión superior: 40-50% menor que JPEG con misma calidad visual
  2. Decode rápido: Más rápido que WebP y AVIF
  3. Progressive loading: Como JPEG, pero mejor
  4. Compatibilidad: Puede encapsular JPEG existente sin reencoding
  5. Sin royalties: Completamente open source y libre de patents

Cómo JPEG XL Mejora Performance Web

1. Reducción de Tamaño = Faster Page Load

Ejemplo real:

Sitio de e-commerce con 50 imágenes de producto:

JPEG tradicional:
- 50 imágenes × 150KB = 7.5MB total
- LCP: 2.8s (3G)
- FCP: 1.9s

JPEG XL:
- 50 imágenes × 75KB = 3.75MB total (50% menor)
- LCP: 1.4s (3G) - ✅ Mejora del 50%
- FCP: 1.0s - ✅ Mejora del 47%

Impacto en los Core Web Vitals:

  • LCP (Largest Contentful Paint): Reduce 30-50% con imágenes menores
  • CLS (Cumulative Layout Shift): Mantiene dimensiones explícitas
  • INP (Interaction to Next Paint): Decode rápido no bloquea main thread

2. Progressive Decoding Mejorado

JPEG XL ofrece progressive loading más sofisticado que JPEG:

Comparación de progressive loading:

JPEG tradicional:
- Pass 1: 12.5% calidad (blocky)
- Pass 2: 25% calidad (aún blocky)
- Pass 3: 50% calidad (aceptable)
- Pass 4: 100% calidad (final)

JPEG XL:
- Pass 1: 25% calidad (ya aceptable visualmente)
- Pass 2: 50% calidad (bueno)
- Pass 3: 75% calidad (excelente)
- Pass 4: 100% calidad (perfecto)

Beneficio: Usuario ve imagen de calidad "ok" 2x más rápido.

3. Responsive Images Optimizadas

JPEG XL permite servir una única imagen que se adapta:

Implementación con <picture> y art direction:

<picture>
  <!-- JPEG XL para browsers que soportan -->
  <source type="image/jxl" srcset="
    hero-400w.jxl 400w,
    hero-800w.jxl 800w,
    hero-1200w.jxl 1200w,
    hero-1600w.jxl 1600w
  " sizes="(max-width: 640px) 100vw,
          (max-width: 1024px) 80vw,
          1200px">

  <!-- WebP como fallback -->
  <source type="image/webp" srcset="
    hero-400w.webp 400w,
    hero-800w.webp 800w,
    hero-1200w.webp 1200w,
    hero-1600w.webp 1600w
  " sizes="(max-width: 640px) 100vw,
          (max-width: 1024px) 80vw,
          1200px">

  <!-- JPEG como fallback final -->
  <img src="hero-1200w.jpg"
       alt="Hero image"
       width="1200"
       height="675"
       loading="lazy"
       decoding="async">
</picture>

Ahorro de bandwidth:

Usuario mobile (viewport 375px):
- JPEG: 150KB
- WebP: 110KB
- JPEG XL: 65KB ✅ 57% menor que JPEG

Usuario desktop (viewport 1920px):
- JPEG: 450KB
- WebP: 340KB
- JPEG XL: 200KB ✅ 56% menor que JPEG

Implementación Práctica: Cómo Usar JPEG XL Hoy

1. Convirtiendo Imágenes Para JPEG XL

Usando CLI (cjxl):

# Instalar encoder JPEG XL
# macOS
brew install jpeg-xl

# Ubuntu/Debian
sudo apt install libjxl-tools

# Convertir JPEG a JXL (lossy)
cjxl input.jpg output.jxl --quality 85 --effort 7

# Convertir PNG a JXL (lossless)
cjxl input.png output.jxl --lossless_jpeg=0 --effort 9

# Convertir con progressive encoding
cjxl input.jpg output.jxl --progressive --quality 90

# Batch convert
for img in *.jpg; do
  cjxl "$img" "${img%.jpg}.jxl" --quality 85 --effort 7
done

Usando Node.js:

// Instalar: npm install @jsquash/jxl
import { encode } from '@jsquash/jxl';
import { readFile, writeFile } from 'fs/promises';
import sharp from 'sharp';

async function convertToJXL(inputPath, outputPath, quality = 85) {
  try {
    // Leer imagen con sharp
    const image = sharp(inputPath);
    const metadata = await image.metadata();

    // Obtener raw pixel data
    const { data, info } = await image
      .ensureAlpha()
      .raw()
      .toBuffer({ resolveWithObject: true });

    // Encode para JPEG XL
    const jxlBuffer = await encode(data, {
      width: info.width,
      height: info.height,
      quality: quality,
      effort: 7, // 1-9, mayor = mejor compresión
      progressive: true
    });

    // Guardar archivo
    await writeFile(outputPath, jxlBuffer);

    console.log(`✅ Convertido: ${inputPath}${outputPath}`);
    console.log(`   Tamaño original: ${(await readFile(inputPath)).length / 1024}KB`);
    console.log(`   Tamaño JXL: ${jxlBuffer.length / 1024}KB`);
    console.log(`   Ahorro: ${((1 - jxlBuffer.length / (await readFile(inputPath)).length) * 100).toFixed(1)}%`);

  } catch (error) {
    console.error(`❌ Error al convertir ${inputPath}:`, error);
  }
}

// Convertir imagen
await convertToJXL('hero.jpg', 'hero.jxl', 85);

// Convertir múltiples imágenes
const images = ['hero.jpg', 'product1.png', 'banner.jpg'];
await Promise.all(
  images.map(img => convertToJXL(
    img,
    img.replace(/\.(jpg|png)$/, '.jxl'),
    85
  ))
);

2. Sirviendo JPEG XL con Fallbacks

Content negotiation en el servidor:

// Express.js middleware
import { fileExists } from './utils.js';

app.use('/images', async (req, res, next) => {
  const accept = req.headers.accept || '';
  const supportsJXL = accept.includes('image/jxl') || accept.includes('image/jxl');

  if (supportsJXL) {
    const jxlPath = req.path.replace(/\.(jpg|png|webp)$/, '.jxl');
    const fullPath = `./public/images${jxlPath}`;

    if (await fileExists(fullPath)) {
      res.setHeader('Content-Type', 'image/jxl');
      res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
      res.setHeader('Vary', 'Accept');
      return res.sendFile(fullPath);
    }
  }

  next();
});

Nginx config:

# Servir JPEG XL cuando soportado
location ~* \.(jpg|jpeg|png)$ {
  # Variar cache basado en Accept header
  add_header Vary Accept;

  # Intentar servir .jxl si existe y soportado
  set $jxl_suffix "";
  if ($http_accept ~* "image/jxl") {
    set $jxl_suffix ".jxl";
  }

  # Intentar archivo JXL primero
  try_files $uri$jxl_suffix $uri =404;

  # Cache agresivo
  expires 1y;
  add_header Cache-Control "public, immutable";
}

3. Detección de Soporte en Frontend

// Detectar soporte a JPEG XL
async function supportsJXL() {
  // Verificar via feature detection
  if (!self.createImageBitmap) return false;

  // Imagen JXL mínima (1x1 pixel transparente)
  const jxlData = 'data:image/jxl;base64,/woIAAAA';

  try {
    const img = await fetch(jxlData)
      .then(r => r.blob())
      .then(blob => createImageBitmap(blob));

    return img.width === 1 && img.height === 1;
  } catch {
    return false;
  }
}

// Usar resultado
const hasJXL = await supportsJXL();

if (hasJXL) {
  console.log('✅ Browser soporta JPEG XL');
  // Cargar imágenes JXL
  document.querySelectorAll('img[data-jxl]').forEach(img => {
    img.src = img.dataset.jxl;
  });
} else {
  console.log('❌ Browser no soporta JPEG XL, usando fallback');
  // Cargar WebP o JPEG
}

// Añadir clase al HTML para CSS condicional
document.documentElement.classList.add(
  hasJXL ? 'jxl-support' : 'no-jxl-support'
);

CSS condicional:

/* Usar JXL cuando soportado */
.jxl-support .hero {
  background-image: url('hero.jxl');
}

/* Fallback para browsers sin soporte */
.no-jxl-support .hero {
  background-image: url('hero.webp');
}

/* Fallback final (todos browsers) */
.hero {
  background-image: url('hero.jpg');
}

4. Automatización de Build con Webpack/Vite

Plugin Webpack para auto-conversión:

// webpack.config.js
import ImageMinimizerPlugin from 'image-minimizer-webpack-plugin';

export default {
  module: {
    rules: [
      {
        test: /\.(jpe?g|png)$/i,
        type: 'asset',
      },
    ],
  },
  optimization: {
    minimizer: [
      new ImageMinimizerPlugin({
        minimizer: {
          implementation: ImageMinimizerPlugin.imageminGenerate,
          options: {
            plugins: [
              // Generar JPEG XL
              ['@squoosh/lib', {
                encodeOptions: {
                  jxl: {
                    quality: 85,
                    effort: 7,
                    progressive: true
                  }
                }
              }],
              // También generar WebP como fallback
              ['@squoosh/lib', {
                encodeOptions: {
                  webp: {
                    quality: 85
                  }
                }
              }]
            ]
          }
        },
        generator: [
          {
            preset: 'jxl',
            implementation: ImageMinimizerPlugin.imageminGenerate,
            options: {
              plugins: ['@squoosh/lib']
            }
          }
        ]
      })
    ]
  }
};

Vite plugin:

// vite.config.js
import { defineConfig } from 'vite';
import { imagetools } from 'vite-imagetools';

export default defineConfig({
  plugins: [
    imagetools({
      defaultDirectives: (url) => {
        // Generar múltiples formatos automáticamente
        return new URLSearchParams({
          format: 'jxl;webp;jpg',
          quality: '85',
          w: '400;800;1200;1600'
        });
      }
    })
  ]
});

Uso en componente:

// React/Vue component
import heroJXL from './hero.jpg?format=jxl&w=1200';
import heroWebP from './hero.jpg?format=webp&w=1200';
import heroJPEG from './hero.jpg?format=jpg&w=1200';

export function Hero() {
  return (
    <picture>
      <source type="image/jxl" srcSet={heroJXL} />
      <source type="image/webp" srcSet={heroWebP} />
      <img src={heroJPEG} alt="Hero" />
    </picture>
  );
}

Comparación: JPEG XL vs AVIF vs WebP

Benchmarks Reales

Prueba con 100 imágenes de producto (e-commerce):

Formato Tamaño Total Tiempo Encode Tiempo Decode Calidad Visual
JPEG (baseline) 15.0 MB 2.3s 0.8s 100%
WebP 11.2 MB (-25%) 8.1s 1.2s 98%
AVIF 7.5 MB (-50%) 45.2s ⚠️ 3.8s ⚠️ 99%
JPEG XL 7.8 MB (-48%) 6.4s 0.9s ✅ 99.5%

Ganador: JPEG XL ofrece mejor equilibrio entre compresión, velocidad y calidad.

Cuándo Usar Cada Formato

JPEG XL:

  • ✅ Fotos e imágenes complejas
  • ✅ Progressive loading importante
  • ✅ HDR content
  • ✅ Cuando velocidad de decode importa
  • ❌ Browser support aún limitado (necesita fallback)

AVIF:

  • ✅ Máxima compresión necesaria (mobile 3G)
  • ✅ Cuando tiempo de encode no importa (build time)
  • ❌ Decode lento (evitar en devices débiles)
  • ❌ Encode MUY lento (impracticable en runtime)

WebP:

  • ✅ Soporte amplio (95%+ browsers)
  • ✅ Buen equilibrio compresión/velocidad
  • ❌ Compresión inferior a AVIF/JXL
  • ❌ No soporta progressive loading

JPEG tradicional:

  • ✅ Soporte universal (100%)
  • ✅ Decode extremadamente rápido
  • ✅ Herramientas maduras
  • ❌ Compresión inferior
  • ❌ Sin transparencia

Impacto en Core Web Vitals y SEO

Mejoras Mensurables

Caso de estudio: Blog con muchas imágenes

Antes (JPEG):
- LCP: 3.2s (Poor)
- Total page weight: 4.8MB
- Bounce rate: 42%

Después (JPEG XL + fallbacks):
- LCP: 1.6s (Good) ✅ 50% faster
- Total page weight: 2.4MB ✅ 50% menor
- Bounce rate: 28% ✅ 33% reduction

Impacto en Google ranking:

// Simulación de score
const calculatePageScore = (lcp, cls, inp) => {
  const lcpScore = lcp < 2.5 ? 100 : lcp < 4.0 ? 50 : 0;
  const clsScore = cls < 0.1 ? 100 : cls < 0.25 ? 50 : 0;
  const inpScore = inp < 200 ? 100 : inp < 500 ? 50 : 0;

  return (lcpScore + clsScore + inpScore) / 3;
};

// Antes
const scoreBefore = calculatePageScore(3.2, 0.08, 180);
console.log('Score antes:', scoreBefore); // 50

// Después
const scoreAfter = calculatePageScore(1.6, 0.08, 180);
console.log('Score después:', scoreAfter); // 100 ✅

Google confirma: Sitios con mejores Core Web Vitals tienen boost de ranking.

Optimizaciones Complementarias

Lazy loading con JPEG XL:

// Intersection Observer para lazy load progresivo
const imageObserver = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;

      // Cargar versión JXL si soportado
      const jxlSrc = img.dataset.jxl;
      const webpSrc = img.dataset.webp;
      const jpegSrc = img.dataset.jpeg;

      // Feature detection
      if (supportsJXL && jxlSrc) {
        img.src = jxlSrc;
      } else if (supportsWebP && webpSrc) {
        img.src = webpSrc;
      } else {
        img.src = jpegSrc;
      }

      // Dejar de observar después de cargar
      imageObserver.unobserve(img);
    }
  });
}, {
  // Empezar a cargar 200px antes de aparecer
  rootMargin: '200px'
});

// Observar todas las imágenes lazy
document.querySelectorAll('img[data-lazy]').forEach(img => {
  imageObserver.observe(img);
});

Browser Support y Futuro

Status Actual (Noviembre 2025)

Browser support:

Browser Status Versión
Safari ✅ Nativo 17+ (macOS Sonoma, iOS 17)
Chrome/Edge 🟡 Flag 116+ (--enable-features=JXL)
Firefox 🟡 Flag 119+ (image.jxl.enabled=true)
Opera 🟡 Flag 102+ (Chromium-based)
Samsung Internet ❌ No -

Polyfill disponible: jxl-wasm-decoder (84KB gzipped)

Roadmap

2025 Q4:

  • Chrome planea activar soporte nativo
  • Firefox considerando activar por defecto
  • Edge sigue Chrome (Chromium)

2026:

  • Expectativa: 60-70% browser support global
  • Mobile browsers comienzan a adoptar
  • CDNs (Cloudflare, Fastly) optimizan JXL delivery

2027:

  • Expectativa: 85%+ browser support
  • JPEG XL se vuelve estándar de facto para web
  • Herramientas de diseño (Figma, Photoshop) soporte nativo

Conclusión: JPEG XL Es el Futuro de la Web

La adopción del JPEG XL por la PDF Association no es solo simbólica — es validación de que el formato está listo para producción y va a volverse ubicuo.

Para desarrolladores web, los beneficios son claros:

50% menor tamaño vs JPEG tradicional
Decode más rápido que AVIF y WebP
Progressive loading superior mejora UX
Mejora LCP y otros Core Web Vitals
Open source y royalty-free

Acción inmediata:

  1. Experimenta JPEG XL en proyectos nuevos (con fallbacks)
  2. Mide impacto en performance (Lighthouse, WebPageTest)
  3. Implementa progressive enhancement (JXL → WebP → JPEG)
  4. Monitorea browser support y ajusta según necesario

El formato está maduro, las herramientas están listas, y el soporte está creciendo. Desarrolladores early adopters tendrán ventaja competitiva en performance en los próximos años.

Si quieres profundizar más en optimización web, te recomiendo: WebAssembly en 2025: Cómo Wasm Está Redefiniendo los Límites de Performance en la Web donde exploramos otra tecnología transformando performance.

¡Vamos a por ello! 🦅

📚 ¿Quieres Profundizar Tus Conocimientos en JavaScript?

Este artículo cubrió optimización de imágenes y performance web, pero hay mucho más para explorar en el mundo del desarrollo moderno.

Desarrolladores que invierten en conocimiento sólido y estructurado tienden a tener más oportunidades en el mercado.

Material de Estudio Completo

Si quieres dominar JavaScript del básico al avanzado, preparé una guía completa:

Opciones de inversión:

  • $9.90 USD (pago único)

👉 Conocer la Guía JavaScript

💡 Material actualizado con las mejores prácticas del mercado

Comentarios (0)

Este artículo aún no tiene comentarios 😢. ¡Sé el primero! 🚀🦅

Añadir comentarios