Building a 3D Product Gallery: E-commerce Thumbnails at Scale

Generate product thumbnails from 3D models on-demand without pre-rendering. Learn how to build scalable e-commerce galleries with GLB2PNG API.

The problem with pre-rendering 3D product thumbnails

If you're running an e-commerce platform with 3D product models, you've likely faced the challenge of generating thumbnails. The traditional approach is to pre-render all product images when products are uploaded or updated. This works fine for small catalogs, but quickly becomes a bottleneck as you scale.

Consider a marketplace with 10,000 products, each with multiple color variants or configurations. Pre-rendering means:

  • Storage costs: Thousands of images stored in your CDN or object storage
  • Update overhead: Re-rendering all thumbnails when you change lighting, camera angles, or product models
  • Development complexity: Building and maintaining rendering pipelines, queue systems, and error handling
  • Time to market: New products can't go live until thumbnails are generated

On-demand rendering: a better approach

Instead of pre-rendering, you can generate product thumbnails on-demand using the GLB2PNG API. This means thumbnails are created the first time they're requested and then cached by our global CDN for subsequent requests.

The benefits are immediate:

  • No storage costs: Images are generated on-demand and cached automatically
  • Instant updates: Change your product model URL and the thumbnail updates automatically
  • Simplified architecture: No rendering queues or background jobs needed
  • Faster product launches: Products go live immediately, thumbnails generate on first view

Let's build a React component that displays product thumbnails using on-demand rendering. For this example, we'll use the Glam Velvet Sofa from Khronos Group as our product model.

Setting up the API endpoint

First, we'll create a function that generates the thumbnail URL for a product:

const GLB2PNG_API_KEY = 'your-api-key-here'; // Get from your GLB2PNG dashboard
const GLB2PNG_BASE_URL = 'https://www.glb2png.com/v1';

function getProductThumbnailUrl(glbUrl, options = {}) {
  const {
    size = 512,
    rotationVertical = 0,
    rotationHorizontal = -45,
    variant = null
  } = options;

  const params = [
    `s:${size}`,
    `rv:${rotationVertical}`,
    `rh:${rotationHorizontal}`
  ];

  // Add variant parameter if specified (for glTF material variants)
  if (variant) {
    params.push(`v:${variant}`);
  }

  const paramsString = params.join(',');
  const encodedGlbUrl = encodeURIComponent(glbUrl);
  return `${GLB2PNG_BASE_URL}/${GLB2PNG_API_KEY}/r/${paramsString}/u/${encodedGlbUrl}`;
}

GLB2PNG_API_KEY is the API Key from your GLB2PNG dashboard and glbUrl is the public URL to your product's glb/glTF file.

Creating the product card component

Now let's create a product card that uses this function:

import { useState } from 'react';

function ProductCard({ product }) {
  const [imageError, setImageError] = useState(false);
  
  // Generate thumbnail URL with optimal settings for product display
  const thumbnailUrl = getProductThumbnailUrl(product.glbUrl, {
    size: 512,
    rotationHorizontal: -45, // Show the front-left angle
    rotationVertical: 15      // Slight upward angle
  });

  return (
    <div className="product-card">
      <a href={`/products/${product.id}`}>
        <img
          src={thumbnailUrl}
          alt={product.name}
          className="product-thumbnail"
          style={{ backgroundColor: '#ffffff' }}
          onError={() => setImageError(true)}
          loading="lazy"
        />
        {imageError && (
          <div className="thumbnail-fallback">
            Product Image
          </div>
        )}
      </a>
      <h3>{product.name}</h3>
      <p className="price">${product.price}</p>
    </div>
  );
}

The thumbnail is generated on-demand when the image loads. If the same product is viewed again, the image is served from our CDN cache in milliseconds.

Responsive images for better performance

For better SEO and page load performance, you can generate multiple sizes and use the srcset attribute:

function getProductThumbnailSrcSet(glbUrl, options = {}) {
  const sizes = [256, 512, 1024, 2048];
  return sizes.map(size => {
    const url = getProductThumbnailUrl(glbUrl, { ...options, size });
    return `${url} ${size}w`;
  }).join(', ');
}

function ProductCard({ product }) {
  const thumbnailUrl = getProductThumbnailUrl(product.glbUrl, {
    size: 512,
    rotationHorizontal: -45,
    rotationVertical: 15
  });

  const srcSet = getProductThumbnailSrcSet(product.glbUrl, {
    rotationHorizontal: -45,
    rotationVertical: 15
  });

  return (
    <div className="product-card">
      <a href={`/products/${product.id}`}>
        <img
          src={thumbnailUrl}
          srcSet={srcSet}
          sizes="(max-width: 640px) 256px, (max-width: 1024px) 512px, 1024px"
          alt={product.name}
          className="product-thumbnail"
          style={{ backgroundColor: '#ffffff' }}
          loading="lazy"
        />
      </a>
      <h3>{product.name}</h3>
      <p className="price">${product.price}</p>
    </div>
  );
}

This ensures users on mobile devices download smaller images, improving page load times and reducing bandwidth usage.

Handling product variants

Many e-commerce products have variants (different colors, materials, or configurations). GLB2PNG supports the glTF Material Variants extension, which allows you to render different material variants from a single GLB file without needing separate files.

Using glTF Material Variants

If your GLB file includes material variants, you can render different variants by specifying the variant name:

function ProductCard({ product, selectedVariant }) {
  // Use the variant name if available, otherwise render default
  const thumbnailUrl = getProductThumbnailUrl(product.glbUrl, {
    size: 512,
    rotationHorizontal: -45,
    rotationVertical: 15,
    variant: selectedVariant?.variantName || null
  });

  return (
    <div className="product-card">
      <a href={`/products/${product.id}?variant=${selectedVariant?.id}`}>
        <img
          src={thumbnailUrl}
          alt={`${product.name} - ${selectedVariant?.name || 'Default'}`}
          className="product-thumbnail"
          style={{ backgroundColor: '#ffffff' }}
          loading="lazy"
        />
      </a>
      <h3>{product.name}</h3>
      {selectedVariant && <p className="variant-name">{selectedVariant.name}</p>}
      <p className="price">${selectedVariant?.price || product.price}</p>
    </div>
  );
}

When a user selects a different color variant, the thumbnail updates automatically by rendering the specified material variant from the same GLB file.

Using separate GLB files for variants

Alternatively, if you have separate GLB files for each variant, you can point to different files:

function ProductCard({ product, selectedVariant }) {
  // Use the variant's GLB URL if available, otherwise fall back to base product
  const glbUrl = selectedVariant?.glbUrl || product.glbUrl;
  
  const thumbnailUrl = getProductThumbnailUrl(glbUrl, {
    size: 512,
    rotationHorizontal: -45,
    rotationVertical: 15
  });

  return (
    <div className="product-card">
      <a href={`/products/${product.id}?variant=${selectedVariant?.id}`}>
        <img
          src={thumbnailUrl}
          alt={`${product.name} - ${selectedVariant?.name || 'Default'}`}
          className="product-thumbnail"
          style={{ backgroundColor: '#ffffff' }}
          loading="lazy"
        />
      </a>
      <h3>{product.name}</h3>
      {selectedVariant && <p className="variant-name">{selectedVariant.name}</p>}
      <p className="price">${selectedVariant?.price || product.price}</p>
    </div>
  );
}

Performance optimization strategies

Lazy loading and intersection observer

For product galleries with many items, implement lazy loading to only generate thumbnails for visible products:

import { useEffect, useRef, useState } from 'react';

function ProductCard({ product }) {
  const [isVisible, setIsVisible] = useState(false);
  const cardRef = useRef(null);

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setIsVisible(true);
          observer.disconnect();
        }
      },
      { rootMargin: '50px' } // Start loading 50px before the card is visible
    );

    if (cardRef.current) {
      observer.observe(cardRef.current);
    }

    return () => observer.disconnect();
  }, []);

  const thumbnailUrl = isVisible
    ? getProductThumbnailUrl(product.glbUrl, {
        size: 512,
        rotationHorizontal: -45,
        rotationVertical: 15
      })
    : null;

  return (
    <div ref={cardRef} className="product-card">
      <a href={`/products/${product.id}`}>
        {thumbnailUrl ? (
          <img
            src={thumbnailUrl}
            alt={product.name}
            className="product-thumbnail"
            style={{ backgroundColor: '#ffffff' }}
            loading="lazy"
          />
        ) : (
          <div className="thumbnail-placeholder">Loading...</div>
        )}
      </a>
      <h3>{product.name}</h3>
      <p className="price">${product.price}</p>
    </div>
  );
}

Preloading critical thumbnails

For above-the-fold products or featured items, you can preload thumbnails:

function preloadProductThumbnail(glbUrl, options = {}) {
  const thumbnailUrl = getProductThumbnailUrl(glbUrl, options);
  const link = document.createElement('link');
  link.rel = 'preload';
  link.as = 'image';
  link.href = thumbnailUrl;
  document.head.appendChild(link);
}

// Preload featured products on page load
useEffect(() => {
  featuredProducts.forEach(product => {
    preloadProductThumbnail(product.glbUrl, {
      size: 512,
      rotationHorizontal: -45,
      rotationVertical: 15
    });
  });
}, []);

Cost comparison

Let's compare the costs of pre-rendering versus on-demand rendering for a typical e-commerce scenario.

Pre-rendering approach

For a marketplace with 10,000 products:

  • Storage: 10,000 images × 200KB average = 2GB storage
  • CDN bandwidth: Varies, but typically $0.085/GB for first 10TB
  • Rendering infrastructure: Server costs, queue systems, monitoring
  • Development time: Building and maintaining the rendering pipeline

On-demand rendering with GLB2PNG

With on-demand rendering:

  • CDN included: Repeat requests served from global CDN at no extra cost
  • No storage costs: Images cached automatically
  • No infrastructure: No servers or queues to maintain

For most e-commerce sites, the majority of product views are repeat requests (returning customers, search results, category pages), which are served from cache. Only new products or cache misses require a new rendering.

Check our pricing page to see plans and rendering limits that fit your needs.

SEO benefits

On-demand rendering also provides SEO advantages:

  1. Always up-to-date images: Product thumbnails automatically reflect the latest model files
  2. Proper alt tags: You can dynamically set alt text based on product data
  3. Structured data: Generate product schema with accurate image URLs
  4. Page speed: Responsive images and lazy loading improve Core Web Vitals scores
function ProductCard({ product }) {
  const thumbnailUrl = getProductThumbnailUrl(product.glbUrl, {
    size: 1024, // Higher resolution for SEO
    rotationHorizontal: -45,
    rotationVertical: 15
  });

  return (
    <article itemScope itemType="https://schema.org/Product">
      <a href={`/products/${product.id}`}>
        <img
          src={thumbnailUrl}
          alt={`${product.name} - ${product.category}`}
          itemProp="image"
          style={{ backgroundColor: '#ffffff' }}
          loading="lazy"
        />
      </a>
      <h3 itemProp="name">{product.name}</h3>
      <p itemProp="offers" itemScope itemType="https://schema.org/Offer">
        <span itemProp="price">${product.price}</span>
        <meta itemProp="priceCurrency" content="USD" />
      </p>
    </article>
  );
}

Use cases

  • E-commerce marketplaces: Generate thumbnails for thousands of products without pre-rendering
  • Product configurators: Show real-time previews as customers customize products
  • Inventory management: Automatically update thumbnails when product models change
  • Multi-vendor platforms: Each vendor can upload GLB files, thumbnails generate automatically
  • Seasonal catalogs: Update product displays without re-rendering entire catalogs

Good to know

  • CDN caching: All generated images are cached globally. The first request generates the image, subsequent requests are served from cache
  • Customizable rendering: Adjust camera angles, image sizes, and material variants via URL parameters
  • Format support: Works with both glb and glTF files, including mesh and texture compression
  • Material variants: Supports the glTF Material Variants extension, allowing you to render different material configurations from a single file
  • Pricing and limits: Check our pricing page for plan details and rendering limits

Try it yourself

Start building your 3D product gallery today. Sign up for a free GLB2PNG account to get started. No credit card required.

Check our pricing page to see which plan fits your needs, and visit our documentation for more API details and examples.