Next.js 교재 · 15편 · 미디어 최적화

Image·Font 최적화 — next/image·next/font 실전

한 줄 컴포넌트 교체로 라이트하우스 점수가 그냥 오른다.

이미지 압축과 폰트 프리로드 컨셉 일러스트

웹 페이지 무게의 70% 이상은 보통 이미지·폰트. 4MB JPG 한 장에 1MB Google Font 두 개면 — 모바일에서 첫 화면 그리는 데 10초가 우습다. 라이트하우스가 빨갛게 떠도 어디서 손대야 할지 막막.

Next.js 가 이 둘에 특화된 컴포넌트를 표준 제공한다. next/imagenext/font. 한 줄 교체로 자동 최적화. 손쓰는 시간 0, 효과 즉시.

1. next/image — 자동 압축·반응형·lazy

기본 사용은 <img> 와 거의 같다.

// app/page.tsx import Image from 'next/image'; import hero from './hero.jpg'; export default function HomePage() { return ( <Image src={hero} alt="hero" placeholder="blur" // 옵션 priority // 첫 화면이면 priority /> ); }

이 한 줄이 자동으로 해 주는 일 — WebP/AVIF 변환, 화면 크기별 다른 사이즈 제공(srcset), lazy 로딩, blur placeholder, 가로/세로 자동 설정(CLS 0). <img> 로 직접 하면 30분 걸리는 일.

로컬 import vs 외부 URLimport hero from './hero.jpg' 처럼 import 하면 Next 가 빌드 타임에 크기를 알아내 자동 처리. 외부 URL 이면 width·height 명시 필수 + next.config.jsremotePatterns 에 도메인 등록.

2. 외부 이미지 + 반응형 sizes

외부 CDN·CMS 에서 받는 이미지는 약간 더 적는다.

// next.config.mjs export default { images: { remotePatterns: [ { protocol: 'https', hostname: 'cdn.example.com' }, ], }, }; // page.tsx <Image src="https://cdn.example.com/photo.jpg" alt="..." width={1200} height={800} sizes="(max-width: 768px) 100vw, 50vw" />

sizes 가 핵심 — 모바일에선 전체 너비, 데스크탑에선 절반. 브라우저가 이 정보로 적절한 크기 이미지를 받아 데이터 절약. 큰 화면에 3MB, 작은 화면엔 200KB.

3. 흔한 함정 4가지

실수 1위 — fill 모드와 부모 positionfill prop 으로 부모 컨테이너를 꽉 채울 땐 부모에 position: relative 필수. 빠뜨리면 이미지가 페이지 밖으로 튀어나간다.

다른 함정 3가지:

  • priority 남발 금지priority 는 첫 화면 LCP 이미지 한 장만. 나머지에 붙이면 lazy 무효화되어 오히려 느림.
  • alt 비우지 말 것 — SEO·접근성 둘 다 깎인다. 의미 없는 장식이면 alt="" 라도.
  • animated GIF — next/image 가 GIF 를 정적 첫 프레임으로 변환할 수 있다. 애니메이션 필요하면 unoptimized 또는 WebM 영상으로 교체.

4. next/font — 셀프 호스팅 자동

옛 방식은 <link href="https://fonts.googleapis.com/..."> — 외부 요청 + 캐싱 정책 다름 + 폰트 깜빡임(FOUT).

Next 의 next/font/google 은 빌드 타임에 폰트를 받아 셀프 호스팅한다. 같은 도메인에서 서빙 → 외부 요청 0, CSP 친화적, 폰트 깜빡임 거의 없음.

// app/layout.tsx import { Inter, Noto_Sans_KR } from 'next/font/google'; const inter = Inter({ subsets: ['latin'], display: 'swap', }); const notoKR = Noto_Sans_KR({ subsets: ['latin'], // 한글은 별도 처리 weight: ['400', '700'], display: 'swap', variable: '--font-noto-kr', }); export default function RootLayout({ children }) { return ( <html lang="ko" className={`${inter.className} ${notoKR.variable}`}> <body>{children}</body> </html> ); }

className 으로 직접 적용 또는 variable 로 CSS 변수 등록 후 Tailwind/CSS 에서 var(--font-noto-kr) 사용. 둘 다 가능.

한글 폰트 주의 — Noto Sans KR 같은 한글 폰트는 글리프가 많아(7000+) subset 적용이 까다롭다. Google 제공 subset 옵션이 라틴 기반이라 한글이 빠질 수 있다. 그 경우 display: 'swap' + preload: false 로 두고 fallback 폰트 잘 설정. 또는 next/font/local 로 직접 .woff2 파일 호스팅.

5. 로컬 폰트 — next/font/local

커스텀 .woff2 파일이 있을 때.

// app/fonts.ts import localFont from 'next/font/local'; export const pretendard = localFont({ src: [ { path: '../public/fonts/Pretendard-Regular.woff2', weight: '400' }, { path: '../public/fonts/Pretendard-Bold.woff2', weight: '700' }, ], display: 'swap', variable: '--font-pretendard', });

Pretendard 처럼 한국어 디자이너가 많이 쓰는 무료 폰트는 이 방식이 표준. Google Fonts 보다 훨씬 빠르다 (외부 요청 0).

요약 — 15편 좌표

여기까지 정리. next/image — WebP/AVIF·반응형·lazy·blur 자동, 로컬 import 가 가장 편함. priority 는 첫 화면 LCP 한 장만. sizes 로 화면별 다른 크기 제공. next/font — Google Fonts 를 빌드 타임에 받아 셀프 호스팅, 외부 요청 0. 한글은 subset 주의, Pretendard 같은 로컬 .woff2 는 next/font/local. 두 컴포넌트만 제대로 써도 라이트하우스 LCP·CLS 점수가 자동 상승. 다음 편에서 렌더링 모드 — ISR·SSG·SSR 차이.

다음 편 예고 — ISR·SSG·SSR 차이

언제 어떤 렌더링을 고르나. 16편.

© 2026 주나이테크(주) @JUNAITECH