Next.jsNext.js 기초 · 5기초

이미지와 폰트 최적화 — next/image와 next/font

Next.jsnext/imagenext/font최적화CoreWebVitals

next/image: 이미지 최적화

일반 <img> 태그 대신 next/image를 사용하면 자동으로:

  • WebP/AVIF 변환
  • 화면 크기에 맞는 리사이징
  • lazy loading
  • layout shift 방지 (CLS 개선)

기본 사용법

import Image from "next/image";

// 정적 이미지: import하면 크기 자동 감지
import profilePic from "@/public/profile.jpg";

function Profile() {
    return (
        <Image
            src={profilePic}
            alt="프로필 사진"
            // width, height 불필요 (자동 감지)
        />
    );
}

// 원격 이미지: 크기 명시 필수
function ProductImage({ url }: { url: string }) {
    return (
        <Image
            src={url}
            alt="상품 이미지"
            width={400}
            height={300}
        />
    );
}

fill: 컨테이너 채우기

function HeroImage() {
    return (
        // 부모에 position: relative 필요
        <div className="relative h-96 w-full">
            <Image
                src="/hero.jpg"
                alt="히어로"
                fill
                className="object-cover"  // CSS로 크기 제어
                priority  // LCP 이미지에 사용
            />
        </div>
    );
}

원격 이미지 허용 설정

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
    images: {
        remotePatterns: [
            {
                protocol: "https",
                hostname: "images.example.com",
                pathname: "/products/**",
            },
            {
                protocol: "https",
                hostname: "*.amazonaws.com",
            },
        ],
    },
};

module.exports = nextConfig;

next/font: 폰트 최적화

폰트를 빌드 시 내려받아 self-hosting합니다. 외부 도메인 요청 없음 → 성능 향상.

// app/layout.tsx
import { Noto_Sans_KR, Inter } from "next/font/google";

const notoSansKR = Noto_Sans_KR({
    subsets: ["latin"],
    weight: ["400", "700"],
    display: "swap",   // FOUT 방지
});

const inter = Inter({
    subsets: ["latin"],
    variable: "--font-inter",  // CSS 변수로 사용
});

export default function RootLayout({ children }: { children: React.ReactNode }) {
    return (
        <html lang="ko" className={inter.variable}>
            <body className={notoSansKR.className}>
                {children}
            </body>
        </html>
    );
}

로컬 폰트

import localFont from "next/font/local";

const myFont = localFont({
    src: [
        {
            path: "../public/fonts/MyFont-Regular.woff2",
            weight: "400",
            style: "normal",
        },
        {
            path: "../public/fonts/MyFont-Bold.woff2",
            weight: "700",
            style: "normal",
        },
    ],
    variable: "--font-my",
});

Metadata API로 SEO 최적화

// 정적 메타데이터
export const metadata = {
    title: {
        template: "%s | 내 사이트",   // 페이지 제목 | 사이트명
        default: "내 사이트",
    },
    description: "사이트 설명",
    openGraph: {
        type: "website",
        locale: "ko_KR",
        url: "https://example.com",
        siteName: "내 사이트",
    },
    twitter: {
        card: "summary_large_image",
    },
};

// 동적 메타데이터 (generateMetadata 함수 사용)

Script 최적화

import Script from "next/script";

export default function Layout({ children }: { children: React.ReactNode }) {
    return (
        <html>
            <body>
                {children}
                {/* 페이지 로드 후 비동기 로드 */}
                <Script
                    src="https://analytics.example.com/script.js"
                    strategy="lazyOnload"
                />
            </body>
        </html>
    );
}
strategy설명
beforeInteractive가능한 빨리
afterInteractivehydration 후
lazyOnload모든 리소스 로드 후

정리

최적화방법효과
이미지next/imageWebP 변환, lazy load
폰트next/font/googleself-hosting, no FOUT
스크립트next/script로드 전략 제어
SEOmetadata APIOG 태그, 타이틀 관리

다음 편에서는 인증 구현 — NextAuth.js로 소셜 로그인과 세션 관리를 구현하는 방법을 배웁니다.

궁금한 점이 있으신가요?

협업·의뢰는 아래로, 가벼운 소통은 인스타그램 @bluefox._.hi도 환영이에요.