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
Building the product gallery component
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:
- Always up-to-date images: Product thumbnails automatically reflect the latest model files
- Proper alt tags: You can dynamically set alt text based on product data
- Structured data: Generate product schema with accurate image URLs
- 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.