개발자 되는 법.../TIL...

2-6. Next Js의 Asset 최적화

개발_자 2024. 10. 18. 21:28

Next/Image를 이용한 이미지 최적화
이미지는 일반적인 웹사이트 페이지 무게의 아주 큰 부분을 차지하며, 웹사이트의 LCP(Largest Contentful Paint) 성능에 큰 영향을 미칠 수 있음

Next.js는 자동으로 이미지 크기를 최적화 해주는 이미지 컴포넌트 Image컴포넌트를 제공

자동 이미지 최적화 기능

  • 크기 최적화
    각 긱기에 맞는 크기의 이미지를 자동으로 제공, WebP 및 AVIF와 같은 최신 이미지 형식을 사용
  • 시각적 안정성
    이미지가 로딩 될 때 레이아웃 이동을 자동으로 방지
  • 빠른 페이지 로드
    네이티브 브라우저 지연 로딩을 사용하여 이미지가 뷰포트에 들어올 때만 로드됨. 블러업 플레이스 홀더를 옵션으로 사용 가능.
  • 자산 유연성
    원격 서버에 저장된 이미지도 포함하여 필요에 따라 이미지 크기 조정

로컬 이미지 사용

import Image from 'next/image'
import profilePic from './me.png' 

export default function Page() { 
	return ( 
		<Image src={profilePic} 
		alt="Picture of the author" 
		// width={500} 자동 제공 
		// height={500} 자동 제공 
		// blurDataURL="data:..." 자동 제공 
		// placeholder="blur" // 로딩 중 블러업 옵션 
		/> 
	) 
}

원격 이미지, 네트워크 이미지

import Image from 'next/image'

export default function Page() {
  return (
    <Image
      src="<network-image>"
      alt="Picture of the author"
      width={500}
      height={500}
    />
  )
}

원격 이미지 허용을 위한 설정

  • next.config.js파일에 지원되는 URL 패턴을 정의하여 안전하게 이미지 최적화
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: false,
  images: {
    remotePatterns: [
      {
        protocol: "https",
        hostname: "fakestoreapi.com",
        pathname: "/**",
      },
    ],
    formats: ["image/avif", "image/webp"],
  },
};

이미지를 최적화해주는 원리

  • lazy loading
    스크린 내(뷰포트)에 노출되는 이미지만 로드하여 페이지 로드 속도 개선
    Next.js에서 next/image컴포넌트를 사용하면 기본적으로 지원함
  • 이미지 사이즈 최적화
    Next.js는 Next.js서버에서 이미지를 다운로드 받아, 압축해서 파일의 사이즈를 줄인채로 저장함
    최초 요청 시에 Next.js 서버에서 이미지를 용량이 작은 WebP, AVIF 포맷으로 변환하는 작업을 함
    이후 요청에는 캐시 된 이미지가 전달되기 때문에 훨씬 빠른 이미지를 서빙할 수 있음

next.config.js

module.exports = {
  images: {
	  // ...
    formats: ['image/avif', 'image/webp'],
  },
}

이 설정을 통해 AVIF, 혹은 WebP로 자동으로 변환해줌

AVIF, WebP 확장자
압축 효율이 뛰어나 많은 관심을 받고 있는 이미지 포맷
WebP는 2010년 AVIF는 2019년에 정식 개발된 이미지 포맷으로 AVIF가 WebP보다 조금 더 나은 압축 성능을 낸다고 알려져있으나, WebP를 지원하는 브라우저 범위가 조금 넓음

Next.js 서버의 이미지 최적화 모듈은 요청 헤더에 있는 Accept 헤더를 읽어서 요청한 브라우저에서 처리할 수 있는 이미지 형식으로 최적화하여 내려주게 됨

그렇기 때문에, AVIF를 사용하겠다고 설정해둬도, AVIF를 지원하지 않는 브라우저에게는 WebP포맷을, AVIF를 지원하는 브라우저에서는 AVIF포맷을 응답으로 내려주기 때문에 편하게 사용해도 좋음

Metadata를 통한 SEO 최적화

메타 데이터는 애플리케이션의 SEO 및 웹 공유성을 개선하기 위해 HTML 헤드 요소 내부의 메타 및 링크 태그를 정의하는데 사용됨

메타 데이터를 정의하기 위한 두 가지 방법

  1. Config 기반 메타데이터
    layout.js또는 page.js파일에서 정적 메타데이터 객체 또는 동적 generateMetadata함수를 내보냄
  2. 파일 기반 메타데이터
    라우트 세그먼트에 정적 또는 동적으로 생성된 특별한 파일을 추가

두 옵션 모두에서 Next.js는 페이지에 대한 관련 <head>요소를 자동으로 생성함

정적 메타데이터
정적 메타 데이터를 정의하려면 layout.js또는 page.js파일에서 Metadata객체를 내보냄

// layout.tsx | page.tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: '...',
  description: '...',
}

export default function Page() {}

동적 메타데이터
동적 값이 필요한 메타 데이터를 가져오려면 generateMetadata함수를 사용할 수 있음

// app/products/[id]/page.tsx
import type { Metadata, ResolvingMetadata } from 'next'

type Props = {
  params: { id: string }
  searchParams: { [key: string]: string | string[] | undefined }
}

export async function generateMetadata(
  { params, searchParams }: Props,
): Promise<Metadata> {
  // 경로 매개변수 읽기
  const id = params.id

  // 데이터 가져오기
  const product = await fetch(`https://.../${id}`).then((res) => res.json())

  return {
    title: product.title,
  }
}

export default function Page({ params, searchParams }: Props) {}

정적 및 동적 메타 데이터는 Server Components에서만 지원됨

파일 기반 메타데이터

  • favicon.ico, apple-icon.jpg, icon.jpg
  • opengraph-image.jpg및 twitter-image.jpg
  • robots.txt
  • sitemap.xml
    파일 기반 메타 데이터가 더 높은 우선순위를 가지고, config 기반 메타 데이터를 덮어씀
  • --기본 필드__

경로가 메타 데이터를 정의하지 않아도 항상 추가되는 두 개의 기본 메타 태그가 있음

<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />

순서

메타 데이터는 루트 세그먼트에서 최종 page.js 세그먼트에 가장 가까운 세그먼트까지 순서대로 평가 됨. 예를 들어,

app/layout.tsx (루트 레이아웃)
app/product/layout.tsx (상품 레이아웃)
app/product/[id]/page.tsx (상품 페이지)

덮어쓰기

평가 순서를 따르며, 동일한 경로의 여러 세그먼트에서 내보낸 Metadata 객체는 얕게 병합되어 경로의 최종 메타데이터 출력을 형성. 중복 키는 순서에 따라 교체됨.

이는 openGraph  robots와 같은 중첩된 필드를 가진 메타 데이터가 이전 세그먼트에서 정의된 경우, 이를 설정한 마지막 세그먼트에 의해 덮어씌워짐을 의미

필드 덮어쓰기

// app/layout.js
export const metadata = {
  title: 'Acme',
  openGraph: {
    title: 'Acme',
    description: 'Acme is a...',
  },
}

// app/product/page.js
export const metadata = {
  title: 'Product',
  openGraph: {
    title: 'Product',
  },
}

// 출력:
// <title>Product</title>
// <meta property="og:title" content="Product" />

  • app/layout.js의 title은 app/product/page.js의 title로 대체
  • app/blog/page.js가 openGraph 메타데이터를 설정하기 때문에 app/layout.js의 모든 openGraph 필드는 app/product/page.js에서 교체됨

일부 중첩 필드를 세그먼트 간에 공유하면서 다른 필드를 덮어 쓰려면 별도의 변수로 추출할 수 있음

// app/shared-metadata.js
export const openGraphImage = { images: ['http://...'] }

// app/page.js
import { openGraphImage } from './shared-metadata'

export const metadata = {
  openGraph: {
    ...openGraphImage,
    title: 'Home',
  },
}

// app/about/page.js
import { openGraphImage } from '../shared-metadata'

export const metadata = {
  openGraph: {
    ...openGraphImage,
    title: 'About',
  },
}

필드 상속

// app/layout.js
export const metadata = {
  title: 'Acme',
  openGraph: {
    title: 'Acme',
    description: 'Acme is a...',
  },
}

// app/about/page.js
export const metadata = {
  title: 'About',
}

// 출력:
// <title>About</title>
// <meta property="og:title" content="Acme" />
// <meta property="og:description" content="Acme is a..." />
  • app/layout.js의 title은 app/about/page.js의 title로 대체
  • app/about/page.js가 openGraph 메타데이터를 설정하지 않기 때문에 app/layout.js의 모든 openGraph 필드는 app/about/page.js에 상속됨