Images
Hotel room images are served from a CDN in multiple variants. This guide covers available sizes, the WebP format, responsive image patterns, and caching best practices.
Image endpoints
Images are associated with room types. Individual rooms inherit images from their room type, but may also have room-specific photos.
/api/v1/room-types/:id/imagesRoom type images/api/v1/rooms/:number/images?property_id=Room-specific images"color:#c4b5fd">const BASE_URL = "https:">//hotel-data.k3s.ahsm-krakow.com/api/v1";
"color:#6b7280">// Images for a room type
"color:#c4b5fd">const response = "color:#c4b5fd">await fetch("color:#86efac">`${BASE_URL}/room-types/apt-penthouse/images`);
"color:#c4b5fd">const { data: images } = "color:#c4b5fd">await response.json();
"color:#6b7280">// Images for a specific room
"color:#c4b5fd">const roomImages = "color:#c4b5fd">await fetch(
"color:#86efac">`${BASE_URL}/rooms/301/images?property_id=apt`
).then((r) => r.json());Example response
{
"data": [
{
"id": "img-001",
"room_type_id": "apt-penthouse",
"filename": "penthouse-living-room.jpg",
"alt": "Penthouse suite living room with panoramic view",
"sort_order": 1,
"variants": {
"thumbnail": "https://cdn.hotel-data.example.com/images/img-001/thumbnail.webp",
"medium": "https://cdn.hotel-data.example.com/images/img-001/medium.webp",
"large": "https://cdn.hotel-data.example.com/images/img-001/large.webp",
"original": "https://cdn.hotel-data.example.com/images/img-001/original.jpg"
}
}
]
}Image variants
Every image is processed into four variants at upload time. CDN URLs are stable and immutable — the same URL will always return the same image.
| Variant | Dimensions | Format | Best used for |
|---|---|---|---|
thumbnail | ~320 × 240 px | WebP | Lazy load placeholders, grid thumbnails, blur-up technique |
medium | ~800 × 600 px | WebP | Default display size for cards and listings |
large | ~1600 × 1200 px | WebP | Lightbox / full-screen view on high-DPI displays |
original | Source resolution | JPEG | Downloads, print, or if you need the full-fidelity source |
WebP first. The thumbnail, medium, and large variants are served as WebP, which delivers 25-35% smaller file sizes compared to JPEG at equivalent quality. All modern browsers support WebP. Use the original variant only when you explicitly need JPEG.
Usage in Next.js
Use the next/image component with the thumbnail as a blur placeholder for a smooth loading experience.
"color:#c4b5fd">import Image "color:#c4b5fd">from "next/image";
interface RoomImage {
id: string;
alt: string;
variants: {
thumbnail: string;
medium: string;
large: string;
original: string;
};
}
"color:#c4b5fd">function RoomGallery({ images }: { images: RoomImage[] }) {
"color:#c4b5fd">return (
<div className="grid grid-cols-3 gap-2">
{images.map((image) => (
<Image
key={image.id}
src={image.variants.medium}
alt={image.alt}
width={400}
height={300}
"color:#6b7280">// Use thumbnail for initial load, switch to medium
placeholder="blur"
blurDataURL={image.variants.thumbnail}
className="rounded-lg object-cover w-full aspect-4/3"
loading="lazy"
/>
))}
</div>
);
}Responsive images without Next.js
Use a picture element with srcset to let the browser choose the most appropriate size.
"color:#6b7280">// Use srcset for responsive images without Next.js Image
"color:#c4b5fd">function ResponsiveRoomImage({ image }) {
"color:#c4b5fd">return (
<picture>
{/* WebP variants for modern browsers */}
<source
srcSet={"color:#86efac">`
${image.variants.thumbnail} 320w,
${image.variants.medium} 800w,
${image.variants.large} 1600w
`}
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
type="image/webp"
/>
{/* JPEG fallback */}
<img
src={image.variants.medium}
alt={image.alt}
loading="lazy"
decoding="">async"
className="w-full h-auto rounded-lg"
/>
</picture>
);
}Caching strategy
CDN image URLs are immutable — the filename includes a content hash, so you can cache them aggressively for up to one year. API responses that include image metadata should be cached for 5 minutes.
"color:#6b7280">// If you proxy images through your own server,
"color:#6b7280">// set aggressive cache headers since CDN URLs are immutable.
res.setHeader(
"Cache-Control",
"public, max-age=31536000, immutable"
);
"color:#6b7280">// For API responses (not the images themselves),
"color:#6b7280">// use shorter cache with stale-while-revalidate:
res.setHeader(
"Cache-Control",
"public, max-age=300, stale-while-revalidate=600"
);max-age=31536000, immutablemax-age=300, stale-while-revalidate=600Performance checklist
- Use the thumbnail variant as a blur placeholder during initial load
- Serve the medium variant for standard card/grid views
- Only load the large variant in lightbox or full-screen contexts
- Set loading="lazy" on images below the fold
- Use WebP variants (thumbnail, medium, large) — not original — in browsers
- Set explicit width and height to prevent layout shifts (CLS)
- Preload hero images with <link rel="preload"> for LCP optimization