From 157afb51739c646c599ffcadfba8f01793cc7b3a Mon Sep 17 00:00:00 2001 From: Bruno Mota Date: Wed, 5 Nov 2025 18:21:36 +0000 Subject: [PATCH 1/4] feat: image support client side rendering --- src/components/Image/index.tsx | 38 +++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/src/components/Image/index.tsx b/src/components/Image/index.tsx index 1fb138c..9a8f908 100644 --- a/src/components/Image/index.tsx +++ b/src/components/Image/index.tsx @@ -16,17 +16,28 @@ type TClutchImageProps = { type PlaceholderValue = 'blur' | 'empty' | `data:image/${string}`; -export async function Image({ +export function Image(props: TClutchImageProps) { + const { src } = props; + + if (!src) return null; + + // If not in browser (server-side), use ServerImage + if (typeof window === 'undefined') { + return ; + } + + return ; +} + +async function ServerImage({ src, className, placeholder, sizes = 'auto', ...props }: TClutchImageProps) { - if (!src) return null; - - const { width, height, format, blurDataURL } = - typeof src === 'string' ? await getImageInfo(src) : src; + const imageInfo = typeof src === 'string' ? await getImageInfo(src) : src; + const { width, height, format, blurDataURL } = imageInfo; let placeholderVal: PlaceholderValue = placeholder ? 'blur' : 'empty'; const size = width + height; @@ -48,3 +59,20 @@ export async function Image({ /> ); } + +function ClientImage({ + src, + className, + sizes = 'auto', + ...props +}: TClutchImageProps) { + return ( + + ); +} From cd42f3ba68b8fbd127059c7f4547fc1152edcd25 Mon Sep 17 00:00:00 2001 From: Bruno Mota Date: Wed, 5 Nov 2025 19:06:28 +0000 Subject: [PATCH 2/4] feat: render img tag if src is a string on client image --- .changeset/rich-hands-pull.md | 5 +++++ src/components/Image/index.tsx | 15 ++++++--------- 2 files changed, 11 insertions(+), 9 deletions(-) create mode 100644 .changeset/rich-hands-pull.md diff --git a/.changeset/rich-hands-pull.md b/.changeset/rich-hands-pull.md new file mode 100644 index 0000000..f1f3488 --- /dev/null +++ b/.changeset/rich-hands-pull.md @@ -0,0 +1,5 @@ +--- +'@clutch-creator/toolkit': patch +--- + +Image support client side rendering diff --git a/src/components/Image/index.tsx b/src/components/Image/index.tsx index 9a8f908..b15fa50 100644 --- a/src/components/Image/index.tsx +++ b/src/components/Image/index.tsx @@ -64,15 +64,12 @@ function ClientImage({ src, className, sizes = 'auto', + placeholder, ...props }: TClutchImageProps) { - return ( - - ); + if (typeof src === 'string') { + return ; + } + + return ; } From 5899cacc87317def8e62179b313cb38df3101235 Mon Sep 17 00:00:00 2001 From: Bruno Mota Date: Wed, 5 Nov 2025 19:13:35 +0000 Subject: [PATCH 3/4] fix: drop sizes on client img --- src/components/Image/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Image/index.tsx b/src/components/Image/index.tsx index b15fa50..766489a 100644 --- a/src/components/Image/index.tsx +++ b/src/components/Image/index.tsx @@ -68,7 +68,7 @@ function ClientImage({ ...props }: TClutchImageProps) { if (typeof src === 'string') { - return ; + return ; } return ; From a4db6acb0367a88e6bc1346e561055574371e8ce Mon Sep 17 00:00:00 2001 From: Bruno Mota Date: Thu, 6 Nov 2025 07:26:57 +0000 Subject: [PATCH 4/4] chore: drop unused client image component and image component better types --- src/components/ClientImage/index.tsx | 42 ---------------------------- src/components/Image/index.tsx | 12 +++++--- 2 files changed, 8 insertions(+), 46 deletions(-) delete mode 100644 src/components/ClientImage/index.tsx diff --git a/src/components/ClientImage/index.tsx b/src/components/ClientImage/index.tsx deleted file mode 100644 index 106a212..0000000 --- a/src/components/ClientImage/index.tsx +++ /dev/null @@ -1,42 +0,0 @@ -'use client'; - -import NextImage from 'next/image'; -import React, { cloneElement, use } from 'react'; - -type TClientImageProps = { - // @deprecated This component is used to wrap a lazy-loaded image component. - children?: React.LazyExoticComponent>; -} & React.ComponentProps; - -export const ClientImage = ({ children, ...props }: TClientImageProps) => { - // @deprecated: This component is used to wrap a lazy-loaded image component. - if (children) { - const child = - children?.$$typeof === Symbol.for('react.lazy') - ? // @ts-expect-error next react lazy payload - use(children._payload) - : children; - - return React.isValidElement(child) - ? cloneElement(child, { ...props }) - : child; - } - - if (!props.src) return null; - - let placeholderVal = props.placeholder; - - if (typeof placeholderVal === 'boolean') { - placeholderVal = placeholderVal ? 'blur' : 'empty'; - } - - return ( - - ); -}; diff --git a/src/components/Image/index.tsx b/src/components/Image/index.tsx index 766489a..4f73e6a 100644 --- a/src/components/Image/index.tsx +++ b/src/components/Image/index.tsx @@ -1,8 +1,8 @@ -import NextImage from 'next/image'; +import NextImage, { StaticImageData } from 'next/image'; import { getImageInfo } from './utils'; type TClutchImageProps = { - src: string; + src: string | StaticImageData; alt: string; fill?: boolean; sizes?: string; @@ -37,12 +37,16 @@ async function ServerImage({ ...props }: TClutchImageProps) { const imageInfo = typeof src === 'string' ? await getImageInfo(src) : src; - const { width, height, format, blurDataURL } = imageInfo; + const { width, height, blurDataURL } = imageInfo; let placeholderVal: PlaceholderValue = placeholder ? 'blur' : 'empty'; + const isSvg = + 'format' in imageInfo + ? imageInfo.format === 'svg' + : imageInfo.src.endsWith('.svg'); const size = width + height; - if (placeholder === undefined && format !== 'svg' && size > 80) { + if (placeholder === undefined && !isSvg && size > 80) { placeholderVal = 'blur'; }