Last active
February 27, 2022 15:21
-
-
Save TheRusskiy/e00ce85b63bfa22e38b043c77c124f06 to your computer and use it in GitHub Desktop.
Next.js Cloudinary Image
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import NextImage from 'next/image' | |
import { useCallback } from 'react' | |
import compact from 'lodash/compact' | |
const CLOUDINARY_HOST = 'https://some-domain.mo.cloudinary.net' | |
type ImageLoaderProps = { | |
src: string | |
width: number | |
quality?: number | |
root?: string | |
} | |
// strip any leading slashes | |
function normalizeSrc(src: string): string { | |
return src[0] === '/' ? src.slice(1) : src | |
} | |
type Props = Parameters<typeof NextImage>[0] & { | |
// https://cloudinary.com/documentation/media_optimizer_transformations#automatic_quality | |
quality?: | |
| 'auto' | |
| 'auto:best' | |
| 'auth:good' | |
| 'auto:eco' | |
| 'auto:low' | |
| number | |
gravity?: | |
| 'auto' | |
| 'center' | |
| 'north_east' | |
| 'north' | |
| 'north_west' | |
| 'west' | |
| 'south_west' | |
| 'south' | |
| 'south_east' | |
| 'east' | |
crop?: | |
| 'crop' | |
| 'fill' | |
| 'fill_pad' | |
| 'fit' | |
| 'lfill' | |
| 'limit' | |
| 'lpad' | |
| 'mfit' | |
| 'mpad' | |
| 'pad' | |
| 'scale' | |
| 'thumb' | |
} | |
// https://cloudinary.com/documentation/transformation_reference | |
const Image = ({ | |
quality = 'auto', | |
gravity = 'center', | |
crop = 'limit', | |
width: initialWidth, | |
height: initialHeight, | |
...props | |
}: Props): ReturnType<typeof NextImage> => { | |
const aspectRatio: number | null = | |
typeof initialWidth === 'number' && typeof initialHeight === 'number' | |
? initialWidth / initialHeight | |
: null | |
const cloudinaryLoader = useCallback( | |
({ src, width }: ImageLoaderProps): string => { | |
const height = | |
gravity === 'auto' && aspectRatio ? width / aspectRatio : null | |
const params = compact([ | |
'f_auto', | |
'c_' + crop, | |
'g_' + gravity, | |
'w_' + width, | |
height ? 'h_' + height : '', | |
'q_' + (quality || 'auto') | |
]) | |
if ( | |
gravity === 'auto' && | |
!['fill', 'lfill', 'fill_pad', 'thumb', 'crop'].includes(crop) | |
) { | |
console.error( | |
'Automatic cropping is supported for the fill, lfill, fill_pad, thumb and crop modes.' | |
) | |
} | |
const paramsString = `tx=${params.join(',')}&resource_type=image` | |
const normalizedSrc = normalizeSrc(src) | |
const parsedUrl = new URL(src) | |
const joiner = parsedUrl.search && parsedUrl.search.length ? '&' : '?' | |
return `${CLOUDINARY_HOST}/${normalizedSrc}${joiner}${paramsString}` | |
}, | |
[crop, quality, gravity, aspectRatio] // height | |
) | |
let imageSrc: string | null = null | |
// Next image accepts different types of imports | |
if (!props.src) { | |
imageSrc = null | |
} else if (typeof props.src === 'string') { | |
imageSrc = props.src | |
} else if (typeof props.src.default !== 'undefined') { | |
imageSrc = props.src.default.src | |
} else if (typeof props.src.src !== 'undefined') { | |
imageSrc = props.src.src | |
} | |
// disable optimization for dev environment and images present in source code | |
const isNextImage = imageSrc?.includes('_next/static') || process.env.NODE_ENV === 'development' | |
return ( | |
<NextImage | |
{...props} | |
height={initialHeight} | |
loader={isNextImage ? undefined : cloudinaryLoader} | |
width={initialWidth} | |
/> | |
) | |
} | |
export default Image |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment