[Next.js V14] 정리 노트 - 9

Next앱 최적화

시작

범위

많은 최적화방법 중 이미지 최적화, SEO를 위한 메타데이터 최적화에 대해 알아보겠습니다.

이미지 최적화

NextJS는 특정 이미지 컴포넌트(Image)를 제공하는데 기본적으로 다음과 같은 최적화가 이루어집니다.

  • 자동으로 이미지의 크기를 조정하여 최적화된 포맷의 작은 파일을 불러온다.
  • 시각적 안정성에 도움을 줌 페이지 로딩을 줄이거나 이미지가 비정상적으로 wrap에서 빠져나오지 않게 해준다.
  • 이미지가 화면에 나타날때 스트림으로 보여준다.

이미지 객체

기존 img 태그의 경우 이미지가 프로젝트내에 저장되어 있더라고 src 요소로 경로를 직접 명시해줘야만 했습니다.

js
  <img src={logo.src} alt="Mobile phone with posts feed on it" />

Image 태그로 변경한 후에는 logo라는 이미지 객체를 src 프로퍼티의 값으로 주면 됩니다.

js
import logo from '@/assets/logo.png';
import Image from 'next/image';

// ...
<Image src={logo} alt="Mobile phone with posts feed on it" />

그럼 이미지 객체는 어떤 정보들이 포함되는지 확인해보겠습니다.

console.log로 표시해보면 다음과 같은 객체 정보과 출력됩니다.

bash
 ✓ Compiled in 64ms (507 modules)
{
  # 이미지 경로
  src: '/_next/static/media/logo.6ad4479c.png',
  # 이미지 크기
  height: 600,
  width: 600,
  blurDataURL: '/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flogo.6ad4479c.png&w=8&q=70',
  blurWidth: 8,
  blurHeight: 8
}

각 객체의 경우 Image 태그 내에서 어떻게 적용되는지 보기 위해 HTML 페이지로 조회하면 다음과 같은 정보들을 볼 수 있습니다.

html
<img alt="Mobile phone with posts feed on it" loading="lazy" 
width="600" height="600" decoding="async" data-nimg="1" style="color:transparent" 
srcset="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flogo.6ad4479c.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flogo.6ad4479c.png&amp;w=1200&amp;q=75 2x" 
src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flogo.6ad4479c.png&amp;w=1200&amp;q=75">

loading="lazy"프로퍼티는 Image컴포넌트의 기본 설정 옵션으로 이미지를 로드해오는대로 화면에 보여주는 방법이고 srcset은 다양한 화면 해상도에 맞도록 이미지를 조정해주는 옵션입니다. 둘다 Image태그내 기본적으로 설정됩니다. width, height의 경우 import logo from '@/assets/logo.png로 가져오는 순간 자동으로 설정되며 사용자가 이 너비, 높이값들을 수정할 수도 있습니다.

이미지 크기 조절하기

js
<Image 
  src={logo} 
  width={100} 
  height={100} 
  alt="Mobile phone with posts feed on it"
/>

위와 같은 방법은 권장되는 방법은 아니지만 조작이 가능합니다. 로컬 이미지를 가져올때는 원본 크기를 그대로 유지한 채로 Image 자동 리사이징을 이용하는 방법을 권장합니다.

고정적인 사이즈를 이용하는 사용자가 올리는 게시글의 썸네일 등에는 유효하게 이용할 수 있을겁니다. 하지만 이 또한 권장되는 방법은 아닙니다.

혹은 아래와 같이 sizes옵션을 설정해 뷰포트 가로 너비의 10%로 이미지를 조정할 수도 있습니다.

js
<Image 
  src={logo} 
  // width={100} 
  // height={100} 
  sizes="10vw"
  alt="Mobile phone with posts feed on it"
/>

이미지 우선순위 설정하기

Image 컴포넌트의 경우 loading를 lazy로 기본으로 설정하여 지연로딩을 발생시키는데, 우선순위를 설정해 지연로딩이 아닌 사전 렌더링 방식을 설정할 수 있습니다.

js
<Image 
  src={logo} 
  width={100} 
  height={100} 
  priority
  alt="Mobile phone with posts feed on it"
/>

priority 프로퍼티의 경우 렌더링된 HTML에서 프로퍼티 fetchpriority="high"가 추가되며 loading="lazy"옵션은 제거되는것을 확인할 수 있습니다.

페이지가 로드된 후 이미지를 확실히 보여줘야 하는 경우 이용합니다.

알 수 없는 이미지 로드하기

로컬 이미지가 아닌 사용자가 업로드하는 이미지의 경우 구체적인 정보를 알 수 없죠. 이 문제를 보완해봅시다.

기본적으로 NextJS의 경우 외부 스토리지 서버에서 가져오는 리소스를 보안적으로 막고 있습니다. 때문에 next.config.mjs에 외부에서 가져오는 이미지 서버는 보안 목록에서 제외해야합니다.

js
/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    remotePatterns: [{
      hostname: 'res.cloudinary.com'
    }]
  }
};

export default nextConfig;

witdh, height의 경우 직접적으로 수정하는 것은 권장되지 않습니다. 이를 위한 보완 방법으로는 Image태그내 프로퍼티로 fill(부모 태그 공간 차지)를 추가하고 부모 태그의 크기를 조정하는 방법입니다.

우선 fill의 경우 position: absolute가 설정되므로 부모 태그에 position: releative를 적용합니다.

css
/* app/global.css */
.post-image {
  width: 8rem;
  height: 6rem;
  position: relative;
}

부모 태그에는 해당 클래스를 명시합니다.

js
// component/posts.js

<div className="post-image">
  <Image 
    src={post.image}
    fill
    alt={post.title}
  />
</div>

로더와 클라우디너리 옵션 알아보기

강의에서 사용하는 클라우디너리의 경우 URL 조정 및 여러 추가 옵션이 있습니다. 또한 이미지 조정 옵션도 포함되어 있습니다. NextJS는 클라우디너리와 긴밀히 사용되는 관계로 이런 설정에 최적화되어 있습니다.

NextJS의 loader를 이용하여 이미지 프로바이더의 클라우디너리의 옵션들을 이용할 수 있습니다. 우선 클라우디너리로부터 오는 객체는 다음과 같이 조회할 수 있습니다.

js
// component/posts.js
import Image from 'next/image';

function imageLoader(config) {
  console.log(config);
  return config.src;
}

function Post({ post, action }) {
  return (
    <article className="post">
      <div className="post-image">
        <Image 
          loader={imageLoader}
          src={post.image}
          fill
          alt={post.title}
        />
        {/* ... */}
bash
{
  src: 'https://res.cloudinary.com/dhwxctrxs/image/upload/v1719109727/nextjs-course-mutations/oxy12jfs1pr1yckgxve4.jpg',
  quality: undefined,
  width: 1200
}
{
  src: 'https://res.cloudinary.com/dhwxctrxs/image/upload/v1719109727/nextjs-course-mutations/oxy12jfs1pr1yckgxve4.jpg',
  quality: undefined,
  width: 1920
}
{
  src: 'https://res.cloudinary.com/dhwxctrxs/image/upload/v1719109727/nextjs-course-mutations/oxy12jfs1pr1yckgxve4.jpg',
  quality: undefined,
  width: 2048
}
{
  src: 'https://res.cloudinary.com/dhwxctrxs/image/upload/v1719109727/nextjs-course-mutations/oxy12jfs1pr1yckgxve4.jpg',
  quality: undefined,
  width: 3840
}

소스를 담은 src, 너비값을 가진 width, quality프로퍼티가 존재하죠. 클라우드너리의 이미지 조정 옵션은 다음 링크를 참조하면됩니다.

클라우드너리의 경우 URL을 수정해 이미지의 조정이 가능한데, 예시는 다음과 같습니다.

text
https://res.cloudinary.com/demo/image/upload/c_thumb,g_face,h_200,w_200/r_max/f_auto/woman-blackdress-stairs.png

위 예제를 보면 다음과 같이 loader내 함수에서 URL을 조정하면 됩니다.

js
// component/posts.js
function imageLoader(config) {
  const urlStart = config.src.split('upload/')[0];
  const urlEnd = config.src.split('upload/')[1];

  const transformations = `w_200,q_${config.quality}`;

  return `${urlStart}upload/${transformations}/${urlEnd}`;
}

NextJS에서 fill 프로퍼티의 경우 sizes를 같이 사용하길 권장하여 warning으로 표시합니다. 다만 위 이미지 조정을 통해 처리가 끝났으므로 width, height를 명시적으로 설정합니다.

js
// component/posts.js
<div className="post-image">
    <Image 
      loader={imageLoader}
      src={post.image}
      width={200}
      height={120}
      alt={post.title}
      quality={50}
    />
  </div>

페이지 메타데이터

구글 검색엔진 등 크롤링을 통해 SEO로 등록해줄때 메타데이터가 중요하게 작동합니다. 메타데이터의 종류에 대해 알아봅시다.

정적 메타데이터

사전에 미리 렌더링되는 메타데이터 정보로, page.js, layout.js 에 설정합니다. 반드시 metadata로 명시하고 export 하면 해당 페이지의 메타데이터로 반영됩니다.

js
// app/page.js

export const metadata = {
  title: 'Latest Posts',
  description: 'Browse out latest posts.',
};

위와 같이 설정한 메타데이터는 렌더링된 HTML에 등록됩니다.

html
<head>
  <!-- ... -->
  <title>Latest Posts</title>
  <meta name="description" content="Browse out latest posts.">
  <!-- ... -->
</head>

동적 메타데이터

사용자가 게시한 글의 경우 동적인 데이터들입니다. 이 경우 메타데이터 설정에 대해 알아봅시다.

동적인 데이터가 포함된 메타데이터 정보로, page.js 에 설정합니다. 반드시 generateMetatadata로 명시하고 export 하면 해당 페이지의 메타데이터로 반영됩니다.

js
// app/feed/page.js
// 매개변수로는 경로 Param, 검색 필터 searchParam 등으로 메타데이터에 적용할 수도 있다.
export async function generateMetatadata(data) {
  const posts = await getPosts();
  const numberOfPosts = posts.length;

  return {
    title: `Browse all out ${numberOfPosts} posts.`,
    description: 'Browse all out posts.'
  }
}

추가로 layout.js에 메타데이터를 등록한 경우 동일 경로 page.js에 기본적인 메타데이터 설정이 없다면 layout.js의 메타데이터가 적용됩니다.