์ค๋๋ง์ ํฌ์คํ !
์ค๋์ nextjs ์ด๋ฏธ์ง ์ปดํฌ๋ํธ์์ blurDataUrl์ ํ์ฉํด์ ์ด๋ฏธ์ง๊ฐ ์์ ํ ๋ก๋๋๊ธฐ ์ ,
ํ๋ฆฌ๊ฒ ๋ณด์ด๋๋ก ํ๋ ๋ก๋๋ฅผ ํ์ฉํด ๋ณด๋ ๋ฐฉ๋ฒ์ ๋ค๋ฃจ๊ณ ์ ํ๋ค.
์ง๊ธ๊น์ง๋ ์ผ๋ฐ img ํ๊ทธ์ ์ค์ผ๋ ํค์ ํ์ฉํด์ placeholder๋ฅผ ๊ตฌํํ์๋๋ฐ,
tripie๋ ์ฌํ ๊ด๋ จ ์ฑ์ด๋ฏ๋ก ์ฌ์ง ์ฝํ ์ธ ๊ฐ ์์ฒญ ๋ง์ ๋ชจ๋ ์ค์ผ๋ ํค์ผ๋ก ๋ก๋ํ๊ธฐ์๋ ๋ณด๊ธฐ์ ์ข์ง ์์๋ค.
๋ฐ๋ผ์ ์์ผ๋ก ๋ก๋๋ ์ด๋ฏธ์ง์ blur ์ฒ๋ฆฌ๋ ๋ฒ์ ์ผ๋ก UX๋ฅผ ๋์ด๊ณ ์ ํ๋ฒ ๋ค๋ค๋ณด๊ณ ์ ํ๋ค.
Blur Data Url ์ด๋ ์ค์ผ๋ ํค UI๋
๋จผ์ blur data url์ด ์ค์ผ๋ ํค ui ๋ณด๋ค ๋ ์ ํฉํ๊ฐ ์ฑ์งํผํฐํํ ๋ฌผ์ด๋ณด์๋ค.
โ Pros of Blur Data URLs
- Better User Experience (UX) ๐
- Blur data URLs create a smooth loading effect, making the transition to the full image feel natural.
- Skeleton loaders can feel jarring or disconnected, whereas blurred images give a preview of the actual content.
- Less Layout Shift (CLS) ๐
- Using a blurred placeholder prevents Cumulative Layout Shift (CLS), improving Core Web Vitals.
- Skeleton loaders often take up space but can still cause minor shifts when the actual image loads.
- Lower Bandwidth Usage ๐ก
- Blur data URLs are usually very small base64-encoded images (~100-200 bytes).
- Skeleton images require a separate request, increasing network requests and data usage.
- Prevents Flash of Empty Content ๐ซโฌ
- Instead of a gray box, users see a blurry preview of the real image, maintaining context.
- Faster Rendering โก
- Since blur data URLs are inlined in the HTML/CSS, the browser does not need a separate request.
- Skeleton images require fetching an external file before being displayed.
- Improves Perceived Performance ๐
- Even if the full image takes longer to load, users feel like the page is responsive because they see something instantly.
โ When Skeleton Images Might Be Better
- For non-image elements (e.g., text, buttons, cards, etc.).
- When content size is unknown, and a blurry preview wouldn’t make sense.
- For very large images where base64 blur placeholders can slightly impact initial page load.
๐ฏ When to Use Each?
ScenarioUse Blur Data URL? Use Skeleton Loader?
Image-heavy pages (news, travel, e-commerce) | โ Yes | โ No |
Content with user-generated images | โ Yes | โ No |
Large text-based content (e.g., blog posts) | โ No | โ Yes |
UI components (buttons, cards, forms) | โ No | โ Yes |
Placeholder for API-fetched images | โ Yes | โ No |
์ฆ, ์์ ์ฒดํฌ๋ฆฌ์คํธ ๊ณผ๋ฐ ์ด์์ ํด๋น๋์๋ค.
โ๏ธ ์ด๋ฏธ์ง๊ฐ ๋ง์ด ํ์ํ ํ์ด์ง
โ๏ธ ์ ์ ๊ฐ ์ ๊ณตํ๋ ์ด๋ฏธ์ง (static import๊ฐ ์๋ ์ด๋ฏธ์ง๋ค)
โ๏ธ ํ ์คํธ๊ฐ ๋ง์ง ์๋ ํ์ด์ง
โ๏ธ API fetch๋ ์ด๋ฏธ์ง์ placeholder๋ก ์ฌ์ฉํ๊ธฐ ์ํด
๊ฑธ๋ฆฌ๋ ์ ์ ์ด๋ฏธ์ง๋ฅผ ๊ฐ์ ธ์์ db์ ์ธ์ฝ๋ฉํ ๊ฐ์ ์ ์ฅํ๋ฉด ์ถ๊ฐ์ ์ธ ์์ฒญ์ด ๋ถํ์ํ์ง ์์๊น?
Blur Data Url ์ ์ฉํด ๋ณด๊ธฐ
๋จผ์ ๊ณต์๋ฌธ์๋ฅผ ์ดํด๋ณด๋ฉด Next ์ด๋ฏธ์ง ์ปดํฌ๋ํธ๋ ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉ ๊ฐ๋ฅํ๋ค.
import Image from 'next/image'
export default function Page() {
return (
<Image
src="/profile.png"
width={500}
height={500}
alt="Picture of the author"
placeholder={'empty'}
/>
)
}
placeholder ์๋'empty' // "empty" | "blur" | "data:image/..." ๊ฐ ๋ฑ์ด ๋ค์ด๊ฐ ์ ์๋๋ฐ,
blur๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋ blurDataURL ์์ฑ์ ์ถ๊ฐํด์ค์ผ ํ๋๋ฐ, data:image/...์ ๊ฐ์ด base64๋ก ์ธ์ฝ๋ฉ ๋ ๊ฐ์ด ํ์๋ค.
๋ง์ฝ src๊ฐ static Import์ธ. jpg,. png,. webp ๋๋ .avif๋ก ๋ ๊ฐ์ฒด๋ผ๋ฉด ์๋์ ์ผ๋ก ๊ฐ์ ธ์์ง์ง๋ง,
dynamic ์ด๋ฏธ์ง์ธ ๊ฒฝ์ฐ, base24 ์ธ์ฝ๋ฉ์ด ํ์๋ค.
ํด๋น ํฌ์คํ ์ nextjs(^15.0.3) app router์ ๊ธฐ์ค์ผ๋ก ์์ฑ๋์๋ค.
src/api/blur-image.ts์ ๋ค์๊ณผ ๊ฐ์ด ์ธ์ฝ๋ฉ ์์ ์ ๋ค๋ฃฐ ํจ์๋ฅผ ์์ฑํด ์ฃผ์๋ค.
import API from 'constants/api-routes';
import { NextResponse } from 'next/server';
// https://medium.com/@kavindumadushanka972/learn-how-to-create-dynamic-blur-data-urls-for-images-in-next-js-bc4eb5d04ec6 ์ฐธ๊ณ
export async function GET(request: NextResponse) {
const { searchParams } = new URL(request.url);
const url = searchParams.get('url') as string;
const base64str = await fetch(`${API.BASE_URL}/_next/image?url=${url}&w=16&q=75`).then(async res =>
Buffer.from(await res.arrayBuffer()).toString('base64')
);
const blurSvg = `
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 5'>
<filter id='b' color-interpolation-filters='sRGB'>
<feGaussianBlur stdDeviation='1' />
</filter>
<image preserveAspectRatio='none' filter='url(#b)' x='0' y='0' height='100%' width='100%'
href='data:image/avif;base64,${base64str}' />
</svg>
`;
const toBase64 = (str: string) =>
typeof window === 'undefined' ? Buffer.from(str).toString('base64') : window.btoa(str);
return NextResponse.json({ data: `data:image/svg+xml;base64,${toBase64(blurSvg)}` }, { status: 200 });
}
API.BASE_URL ์. env์ ํ๊ฒฝ๋ณ์๋ก ์ค์ ํด ์ค ์น์ฌ์ดํธ URL ์์๊ฐ (dev ํ๊ฒฝ์ผ ๋๋ localhost//...)์ด๋ค.
w์ width๋ฅผ ๋ํ๋ธ๋ค. ์ฆ, ์ด๋ฏธ์ง๊ฐ ํฝ์ ๋จ์๋ก ๋์ด๊ฐ ์ผ๋ง์ธ์ง๋ฅผ ๋ํ๋ด, 16w์ธ ๊ฒฝ์ฐ 16 ํฝ์ ์ ์ด๋ฏธ์ง๋ก ์ค์ ํ๋ค๋ ๊ฒ์ด๋ค.
q๋ quality๋ฅผ ๋ํ๋ด๋๋ฐ, ์ด๋ฏธ์ง๊ฐ ์ผ๋ง๋ ์์ถ๋์ด ํ์ง์ ์ค์ ํ ์ ์๋ค. q๊ฐ์ด ํด์๋ก ๋ ํ์ง์ด ์ข์์ง์ง๋ง ์ด๋ฏธ์ง ํฌ๊ธฐ ์ฌ์ด์ฆ๊ฐ ์ปค์ง๋ค. ๋ฐ๋๋ก, ๋ฎ์ ๊ฐ์ผ์๋ก ์ด๋ฏธ์ง ํ์ผ ์ฌ์ด์ฆ๋ ์์์ง์ง๋ง ์ด๋ฏธ์ง์ ์ง ๋ํ ๋ฎ์์ง๋ค. ์๋ฅผ ๋ค์ด q=75์ธ ๊ฒฝ์ฐ ์ด๋ฏธ์ง์ ํ๋ฆฌํฐ๋ 75%๋ก ์ค์ ํ ์ ์๋ค.
w์ q ๊ฐ์ ์ต์๋ก ์ค์ ํ ๊ฒฝ์ฐ ๋ก๋๊ฐ ๊ฐ์ฅ ๋นจ๋ผ์ง๋ค. ๋ค๋ง ์ด๋, blur ์ฒ๋ฆฌ๋ ์ด๋ฏธ์ง๋ฅผ ๋ณด์ฌ์ค ๊ฒ์ด๊ธฐ ๋๋ฌธ์ ํ์ง์ด ๋๋น ์ง๋ ๊ฑด ๋ฌธ์ ๊ฐ ๋์ง ์๋๋ค.
ํธ๋ฆฌํผ ์ฑ์์ ๋ฐ์ดํฐ๋ฅผ ๊ธ์ด์๋๋ฐ, ๋ค์๊ณผ ๊ฐ์ด img url 'https://media.triple.guide/triple-cms/c_fill,f_auto,h_256,w_256/54e3594d-1087-43ff-b760-7231d4103edf.jpeg'๋ฅผ ํด๋น ํจ์๋ก ๋๋ฆฌ๋ฉด 'data:image/svg+xml;base64,CiAg....' ๊ฐ์ด ๋์ด ์์๊ณผ ๊ฐ์ด ๋ก๋ฉ ์ค blur ์ฒ๋ฆฌ๋์ด ์ ๊ณต๋๋ค๋ ๊ฑธ ๋ณผ ์ ์๋ค.
์ฑ์์ ํ์ฉ๋๋ ๋ชจ์ต์ ๋ค์๊ณผ ๊ฐ๋ค.
Tripie โ๏ธ
ํต์·๊ฑฐ์ ·๋จํดํต์·๊ฑฐ์ ·๋จํด
tripie-mauve.vercel.app
๐ค ์ฌ๊ธฐ๊น์ง๋ ์์ํ๋๋ฐ, ๋ก๋๋๋ ์๊ฐ์ด ๋๋ฌด ๊ธธ์ด์ง ๊ฑฐ ๊ฐ์์ ์์ฌ์ ๋ค.
ํ์ฌ๋ ์ด๋ฏธ์ง ๋ฐ์ดํฐ๋ฅผ url๋ง ์ ์ฅํ๋๋ฐ, ์ธ์ฝ๋ฉํ ๊ฐ๋ ๊ฐ์ด ์ ์ฅํด ์ฃผ๋ฉด ์ด ์์ฒญ์ ํ๋ ์๊ฐ์ ๋จ์ถํ ์ ์์ง ์์๊น ์ถ์ด์ ์ ์ฅํ๊ธฐ๋ก ํ๋ค. firebase์ ํด๋น ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ณ ์ถ์์ผ๋, ๊ฐ์ฒด๋ฅผ ์ค์ฒฉ๋ ๋ฐ์ดํฐ๋ฅผ ๊ทธ๋๋ก ์ ์ฅํ๋ ๊ฒ์ด ๋ถ๊ฐ๋ฅํ๋ค๋ ์ ์์ ๋ชจ๋ blurDataURL์ ์ ์ฅํ ์ ์์๋ค. (ํ์ฌ๋ JSON์ ๋ฌธ์๋ก parseํ ๊ฐ์ ํต์ผ๋ก ์ ์ฅํ๊ณ ์๋๋ฐ, ๋ฌธ์์ด์ ํฌ๊ธฐ ๋ํ ๋๋ฌด ์ปค์ ธ์ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฅธ ํ์ ์ผ๋ก ์ ์ฅํด์ผ ํ ๊ฑฐ ๊ฐ๋ค...).
๐ ๊ทธ๋๋ ์ค์ผ๋ ํค๋ง ๋ฉ๊ทธ๋ฌ๋ ๋ณด์ด๋ ํ์ด์ง๋ณด๋ค๋ ๋ธ๋ฌ๋ ์ด๋ฏธ์ง๋ผ๋ ๋ณด์ฌ์ ๋ญ๊ฐ ์์ด ๋ณด์ด๋ ๋๋์ด ๋ํ๊ธด ํ๋ค.
Reference
Learn how to create dynamic blur data URLs for images in Next.js.
A guide to creating a dynamic blurDataURL property encoded in base64 for Next.js Image.
medium.com
'๐ฉ๐ปโ๐ป dev' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Nextjs] Dynamic metadata tag ์์ฑํ๊ธฐ (Part.2) (1) | 2025.02.26 |
---|---|
[Nextjs] Dynamic metadata tag ์์ฑํ๊ธฐ (SEO ์งํ 54 โก๏ธ 91) (0) | 2025.02.25 |
polling mechanism์ผ๋ก react ref ํ์ธํ๊ธฐ (0) | 2025.01.22 |
puppeteer aws lambda์ ๋ฐฐํฌํ๊ธฐ (0) | 2024.11.02 |
[โ๏ธ React conf 2024] ๋ ๋์ ์ปดํจํฐ๋ก ๋์๊ฐ๋ ๋ฆฌ์กํธ (React for Two Computers | Dan Abramov) (1) | 2024.09.27 |