import { getImgproxyUrl } from './imgproxy'; /** * Next.js Image Loader for imgproxy * * @param {Object} props - properties from Next.js Image component * @param {string} props.src - The source image URL * @param {number} props.width - The desired image width * @param {number} props.quality - The desired image quality (ignored for now as imgproxy handles it) */ export default function imgproxyLoader({ src, width, _quality, }: { src: string; width: number; _quality?: number; }) { // Skip imgproxy for SVGs as they are vectors and don't benefit from resizing, // and often cause 404s if the source is not correctly resolvable by imgproxy. if (src.toLowerCase().endsWith('.svg')) { return src; } // Check if src contains custom gravity or aspect ratio query parameters let gravity = 'sm'; // Use smart gravity (content-aware) by default let cleanSrc = src; let calculatedHeight = 0; let resizingType: 'fit' | 'fill' = 'fit'; try { // Dummy base needed for relative URLs const url = new URL(src, 'http://localhost'); const customGravity = url.searchParams.get('gravity'); const aspectRatio = url.searchParams.get('ar'); // e.g. "16:9" if (customGravity) { gravity = customGravity; url.searchParams.delete('gravity'); } if (aspectRatio) { const parts = aspectRatio.split(':'); if (parts.length === 2) { const arW = parseFloat(parts[0]); const arH = parseFloat(parts[1]); if (!isNaN(arW) && !isNaN(arH) && arW > 0) { calculatedHeight = Math.round(width * (arH / arW)); resizingType = 'fill'; // Must use fill to allow imgproxy to crop } } url.searchParams.delete('ar'); } if (customGravity || aspectRatio) { cleanSrc = src.startsWith('http') ? url.href : url.pathname + url.search; } } catch (e) { // Fallback if parsing fails } // We use the width provided by Next.js for responsive images // Height is calculated from aspect ratio if provided, otherwise 0 to maintain aspect ratio return getImgproxyUrl(cleanSrc, { width, height: calculatedHeight, resizing_type: resizingType, gravity, }); }