- Published on
Next.js 14 이미지 LCP 느림 - next/image 설정 7가지
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버 컴포넌트/클라이언트 컴포넌트 혼합이 일반화된 Next.js 14에서 LCP(Largest Contentful Paint) 가 느린 가장 흔한 이유는 ‘히어로 이미지(혹은 폴드 상단 이미지)’가 늦게 다운로드되거나, 늦게 디코딩되거나, 레이아웃이 확정되지 않아 렌더링이 지연되기 때문입니다.
next/image는 기본적으로 최적화를 많이 해주지만, 반대로 설정이 애매하면 “최적화가 오히려 LCP를 늦추는” 상황도 자주 발생합니다. 이 글에서는 “이미지가 LCP를 잡아먹는 상황”을 전제로, Next.js 14에서 바로 적용 가능한 next/image 관련 7가지 설정/패턴을 실전 관점에서 정리합니다.
> 참고: 서버 액션/캐시 꼬임 같은 성능 이슈를 함께 겪는 경우도 많습니다. 이미지 최적화와 별개로 Next.js 14 디버깅이 필요하면 Next.js 14 서버액션 500·CSRF·캐시 꼬임 해결도 같이 보면 좋습니다.
0) 먼저 확인: “정말 이미지가 LCP인가?”
Chrome DevTools의 Performance / Lighthouse / Web Vitals에서 LCP 엘리먼트를 확인하세요.
- LCP 엘리먼트가
img또는picture인가? - LCP phase 중 Load Delay / Resource Load Time이 큰가?
- Render Delay가 큰가? (JS 실행/레이아웃/폰트/오버레이 등)
이미지가 LCP인데도 next/image를 썼다고 안심하면 안 됩니다. 특히 다음 케이스에서 LCP가 느려집니다.
- 히어로 이미지를
priority없이 로드 fill+ 부모 높이 미확정(레이아웃 확정 지연)sizes미설정으로 과도한 해상도 다운로드- 원격 이미지 최적화(프록시)로 TTFB/처리 지연
- blur placeholder를 큰 이미지로 만들어 초기 페인트 지연
이제부터 7가지 설정으로 “LCP 이미지를 가장 먼저, 가장 가볍게, 가장 확실하게” 그리도록 만듭니다.
1) LCP 후보 이미지는 priority + fetchPriority="high"로 끌어올리기
히어로 이미지(폴드 상단)라면 priority는 사실상 필수입니다. priority는 Next가 프리로드 힌트를 생성해 브라우저가 가장 먼저 가져오도록 유도합니다.
또한 최신 브라우저는 fetchpriority 힌트를 지원합니다. Next.js의 Image는 fetchPriority prop을 지원하므로 함께 지정해 “이 리소스가 LCP다”를 더 강하게 표현할 수 있습니다.
// app/page.tsx (Server Component)
import Image from "next/image";
export default function Home() {
return (
<main>
<section style={{ position: "relative", height: 420 }}>
<Image
src="/hero.jpg"
alt="Product hero"
fill
priority
fetchPriority="high"
sizes="100vw"
style={{ objectFit: "cover" }}
/>
</section>
</main>
);
}
체크 포인트
priority는 페이지당 남발하면 오히려 네트워크 경합이 생깁니다. 정말 LCP인 1장(가끔 2장)만.- LCP 후보가 슬라이더/캐러셀이라면, 첫 장만
priority로 제한.
2) sizes를 반드시 지정해서 “필요한 만큼만” 다운로드시키기
next/image는 srcset을 만들어주지만, sizes가 없으면 브라우저가 큰 이미지를 고를 확률이 올라갑니다. 결과적으로 LCP 이미지가 필요 이상으로 무거워져 LCP가 늘어납니다.
대표적인 패턴:
- 풀폭 히어로:
sizes="100vw" - 3열 그리드(데스크탑):
sizes="(max-width: 768px) 100vw, 33vw"
import Image from "next/image";
export function CardGridItem() {
return (
<div>
<Image
src="/thumb.jpg"
alt="thumbnail"
width={640}
height={480}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
</div>
);
}
체크 포인트
fill을 쓰는 경우sizes는 거의 필수입니다.- DevTools Network에서 실제로 어떤 후보(
w=...)가 선택되는지 확인하세요.
3) fill을 쓴다면 “부모 레이아웃 확정”이 LCP의 절반이다
LCP가 느린 프로젝트를 보면 fill을 쓰면서도 부모 컨테이너의 높이가 늦게 확정되는 경우가 많습니다.
- 부모가
position: relative가 아니어서 레이아웃이 꼬임 - 높이를
vh/min-height로 주지 않고 콘텐츠에 의존 - CSS 로딩/폰트 로딩/클라이언트 렌더링 이후에 높이가 결정
권장 패턴: 부모에 명시적 높이(혹은 aspect-ratio) 를 부여합니다.
import Image from "next/image";
import styles from "./hero.module.css";
export function Hero() {
return (
<div className={styles.hero}>
<Image
src="/hero.jpg"
alt="hero"
fill
priority
fetchPriority="high"
sizes="100vw"
style={{ objectFit: "cover" }}
/>
</div>
);
}
/* hero.module.css */
.hero {
position: relative;
width: 100%;
height: 420px; /* 또는 min-height: 60vh */
overflow: hidden;
}
/* 대안: 고정 높이 대신 비율 고정 */
/*
.hero {
position: relative;
width: 100%;
aspect-ratio: 16 / 9;
}
*/
체크 포인트
- LCP는 “이미지 다운로드”만이 아니라 “레이아웃 확정”에도 크게 좌우됩니다.
- CLS(누적 레이아웃 시프트)도 함께 개선되는 경우가 많습니다.
4) 원격 이미지 최적화가 느리면: remotePatterns + 캐시 + (필요 시) unoptimized 전략
next/image의 원격 이미지 최적화는 편리하지만, 다음 상황에서 LCP를 늦출 수 있습니다.
- 이미지 서버가 느림(TTFB)
- Next 이미지 옵티마이저가 런타임에서 변환하느라 지연
- 서버리스 환경에서 콜드스타트/CPU 부족
- 캐시가 적중하지 않아 매 요청 변환
4-1) 우선 remotePatterns를 정확히
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "images.example.com",
pathname: "/**",
},
],
},
};
module.exports = nextConfig;
4-2) “이미지 CDN이 이미 최적화”라면 unoptimized도 고려
이미 Cloudflare Images, Imgix, Akamai Image Manager 등에서 변환/캐시를 강하게 해주고 있다면 Next의 추가 변환이 오히려 병목이 될 수 있습니다.
<Image
src="https://images.example.com/hero.webp"
alt="hero"
width={1600}
height={900}
priority
fetchPriority="high"
unoptimized
/>
주의: unoptimized는 Next의 리사이즈/포맷 변환 이점을 포기합니다. “CDN에서 이미 최적화된 URL을 내려준다”는 전제가 있을 때만.
5) quality와 포맷(AVIF/WebP)로 LCP 바이트를 줄이기
LCP가 이미지라면 결국 전송 바이트를 줄이는 게 가장 확실한 개선입니다.
- 사진 계열 히어로:
quality={60~75}부터 테스트 - 가능하면 AVIF/WebP 사용
<Image
src="/hero.jpg"
alt="hero"
fill
priority
sizes="100vw"
quality={70}
/>
추가로, 원본이 PNG인데 사진이라면 JPEG/WebP/AVIF로 바꾸는 것만으로도 큰 폭으로 줄어듭니다.
체크 포인트
- Lighthouse의 “Properly size images”, “Serve images in next-gen formats” 항목을 함께 확인.
- 품질은 정답이 아니라 트레이드오프입니다. LCP가 KPI라면 과감히 낮추는 게 맞는 경우가 많습니다.
6) placeholder="blur"는 히어로에 신중히: 작은 blurDataURL만 쓰기
placeholder="blur"는 UX에 좋지만, 구현에 따라 LCP를 늦출 수 있습니다.
blurDataURL이 크면(혹은 base64가 비대하면) HTML/JS 페이로드가 늘어 초기 렌더링이 느려질 수 있습니다.- 히어로는 어차피
priority로 빨리 뜨게 만들 것이므로 blur가 꼭 필요하지 않을 때가 많습니다.
권장:
- 히어로는 placeholder 없이
priority로 승부 - blur를 쓴다면 아주 작은(예: 10~20px) 썸네일을 base64로
<Image
src="/hero.jpg"
alt="hero"
fill
priority
sizes="100vw"
// 히어로는 blur를 빼는 편이 LCP에 유리한 경우가 많음
// placeholder="blur"
// blurDataURL="data:image/jpeg;base64,...(tiny)"
/>
7) “위는 빨리, 아래는 느리게”: loading, decoding, 섹션 분리로 경합 제거
LCP 이미지를 빠르게 하려면, 아래쪽 이미지들이 네트워크/디코딩을 선점하지 않도록 해야 합니다.
- 폴드 아래 이미지는 기본
loading="lazy"를 유지 - 필요한 경우
decoding="async"로 메인 스레드 부담 완화 - 한 섹션에 이미지가 많다면 컴포넌트 분리 + 지연 렌더링(IntersectionObserver 등)
import Image from "next/image";
export function BelowTheFoldGallery() {
return (
<section>
{Array.from({ length: 12 }).map((_, i) => (
<Image
key={i}
src={`/gallery/${i + 1}.jpg`}
alt={`gallery ${i + 1}`}
width={600}
height={400}
loading="lazy"
decoding="async"
sizes="(max-width: 768px) 100vw, 50vw"
/>
))}
</section>
);
}
체크 포인트
- LCP와 무관한 이미지가 초반 네트워크를 점유하면 LCP가 밀립니다.
- 특히 홈 화면에서 “추천 상품 20개 썸네일” 같은 구성은 LCP의 적입니다.
실전 점검 체크리스트(요약)
아래 7가지를 위에서부터 순서대로 적용하면서, DevTools에서 LCP가 어떻게 변하는지 확인하세요.
- LCP 이미지에
priority+fetchPriority="high" sizes지정(특히fill사용 시)fill이면 부모 레이아웃(높이/비율) 확정- 원격 이미지면
remotePatterns정리 + 최적화 경로 점검(필요 시unoptimized) quality조정 + 차세대 포맷(AVIF/WebP)- 히어로의
placeholder="blur"는 신중(blurDataURL 비대 방지) - 폴드 아래 이미지는 lazy/async로 경합 제거
마무리: LCP는 “한 장을 빨리”가 아니라 “경로 전체를 단순화”하는 게임
Next.js 14의 next/image는 강력하지만, LCP는 이미지 컴포넌트 하나만 잘 만져서 해결되는 경우가 반, 페이지 전체 리소스 우선순위/레이아웃 확정/원격 최적화 경로까지 정리해야 해결되는 경우가 반입니다.
특히 원격 이미지 최적화가 얽혀 있으면 “왜 느린지”가 감춰지기 쉽습니다. 네트워크 워터폴에서 LCP 이미지 요청이 언제 시작되고 언제 끝나는지, 그리고 그 사이에 다른 리소스가 경합을 만드는지를 먼저 보고, 위 7가지 설정을 체크리스트처럼 적용해보면 재현 가능하게 개선할 수 있습니다.