NextJS의 <Image>
컴포넌트는 로드된 이미지 파일의 크기를 자동으로 최적화하여 최적화된 포맷의 작은 파일을 불러온다.
이 파일에서 로고 이미지를 로컬 시스템에서 불러와 사용하고 있다. 이전의 <img>
를 사용했을 때 logo 이미지를 임포트하여 소스를 설정했다.(logo.src
로 설정)
하지만 NextJS의 <Image>
는 이미지의 소스를 설명하는 logo 객체 전체를 src
로 설정해야한다.
이 변경이 필요한 이유는 Logo 객체는 이미지를 가리키는 경로같은 것이 아니기 때문이다. 실제로 logo 객체는 콘솔로 찍어보면 다음과 같이 나온다.
{
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
}
실제로 logo PNG 파일을 가리키는 경로가 아니라 배포 후에 작동하거나 개발 과정에서 이미 작동하고 있는 경로이다. 또한 해당 객체는 이미지의 높이와 너비 정보도 담고 있다. 이 정보는 <Image>
컴포넌트에 중요한데, 높이와 너비 정보는 이미지를 표시하고 로드하는 방식을 결정하는데 필요하기 때문이다.
import Image from "next/image";
import logo from "@/assets/logo.png";
import Link from "next/link";
export default function Header() {
return (
<header id="main-header">
<Link href="/">
<Image src={logo} alt="Mobile phone with posts feed on it" />
</Link>
<nav>
<ul>
<li>
<Link href="/feed">Feed</Link>
</li>
<li>
<Link className="cta-link" href="/new-post">
New Post
</Link>
</li>
</ul>
</nav>
</header>
);
}
만약 원본 입력 파일의 높이와 너비가 너무 크면 NextJS에서 아무리 조절하더라도 NextJS에서 수행하는 리사이징 작업이 잘못 설정된다.
이를 방지하기 위해 <Image>
컴포넌트에서 너비와 높이를 오버라이딩할 수 있다.
<Image
src={logo}
width={100}
height={100}
alt="Mobile phone with posts feed on it"
/>
이렇게하면 원본이미지가 아무리 크더라도 NextJS에서 작은 크기로 이미지를 렌더링해준다.
그러나 로컬 시스템에서 불러오는 이미지의 크기를 오버라이딩 하는 이 방법은 권장하는 방법이 아니다.
sizes
프로퍼티 사용하기sizes는 이미지 요소에 추가할 수 있는 프로퍼티로 이 프로퍼티에는 여러 가지 크기를 대입할 수 있다.
<Image src={logo} sizes="10vw" alt="Mobile phone with posts feed on it" />
NextJS는 네이티브 이미지 요소에서 loding="lazy"
로 설정한다. 이 설정은 모든 최신 브라우저에서 이미지를 로드할떄 화면에 보이는 경우에만 표시하도록 한다.
로고 이미지는 항상 화면에 보이게 되는데, 이때 지연 로딩은 불필요한 절차일 뿐이다.
이미지를 표시할 때 해당 이미지가 페이지가 로드될 때 화면에 표시될 것을 알고 있다면 이미지 컴포넌트에 priority
속성을 추가하면 된다.
<Image
src={logo}
width={100}
height={100}
priority
alt="Mobile phone with posts feed on it"
/>
이 프로퍼티는 NextJS 뿐만 아니라 브라우저에게도 이 이미지가 페이지가 로드될때 항상 표시된다는 것을 의미한다. 지연 로딩도 비활성화되고 '/'로 네비게이션할 때 이미지가 사전 로드되는 효과를 갖게 된다.
만약 사용자가 런타임 내에 추가한 이미지는 이미지를 최적화할 때 고려해야하는 점이 몇가지 있다.
이때, 사용자가 업로드한 파일의 크기를 미리 알 수 없다는 문제가 있다.
// /components/posts.js
<Image src={post.image} alt={post.title} />
위와 같이 설정만 하면 에러가 발생하는데 이는 문자열로 이미지를 사용하고 있기 때문이다. 이미지 컴포넌트에는 어떠한 객체가 필요한데 만약 객체가 주어지지 않는다면 수동으로 너비와 높이 프로퍼티를 설정해야한다.
이미지를 외부 사이트에서 불러오는 경우 NextJS는 보안상의 이유로 이미지를 차단한다. 따라서 next.config 파일에서 특정 외부 사이트의 잠금을 해제하여 이미지를 로드할 수 있도록 설정해야한다.
// /next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [{ hostname: "res.cloudinary.com" }], // 이미지를 로딩해오려는 원격 호스트 주소를 배열로 설정
},
};
export default nextConfig;
// /components/posts.js
<Image src={post.image} fill alt={post.title} />;
fill
설정으로 이미지에 대한 CSS 구성하기단지 위에서 처럼 fill
로만 구성한다면 전체 페이지의 크기에 맞게 이미지가 표시된다.
이미지를 fill
속성과 함께 사용할 때 이미지의 컨테이너 요소로 가서 컨테이너의 CSS스타일 설정을 변경해야한다.
/* /app/global.css */
.post-image {
/* fill prop을 사용할때는 부모 컨테이너를 설정해야한다. -> 상대적인 위치를 표현하기 위해서 */
position: relative;
width: 8rem;
height: 6rem;
}
Cloudinary와 같은 이미지 호스팅에서는 이미지와 파일을 불러올 수 있는 더 최적화된 방법을 제공한다.
이미지 호스트를 이용하면 이미지 URL을 조작하고 추가 설정을 할 수 있다. 해당 이미지 파일의 여러 버전을 생성하고 캐싱하여 어플리케이션에 여러 버전을 추가할 수 있다.
NextJS 이미지 요소에 loader
라는 프로퍼티를 추가하고 이 프로퍼티에는 함수를 값으로 대입해야하는데 이 함수는 NextJS에서 이미지의 경로를 결정할 때 실행되는 함수이다. 소스를 결정할 때 loader라는 함수를 통해 전달하여 경로를. 조작할 수 있다. → 이미지가 실제로 제공되고 화면에 렌더링되기 전에 이미지 경로를 덮어쓸 수 있다.
// components/posts.js
function imageLoader(config) {
console.log(config);
return config.src;
}
// ...
<Image loader={imageLoader} src={post.image} fill alt={post.title} />;
{
src: 'https://res.cloudinary.com/dwarwdsuf/image/upload/v1719377658/nextjs-course-mutations/k3t11k3zky99jmmmfh62.jpg',
quality: undefined,
width: 3840 # NextJS는 이미지 크기를 조절할 수 없다.
}
// /components/posts.js
<Image
loader={imageLoader}
src={post.image}
fill
alt={post.title}
quality={50} // 0~100까지의 값
/>
🔗 Cloudinary Docs
🔗 Cloudinary : Image Transformations
// /components/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}`; // q: quality
return `${urlStart}upload/${transformations}/${urlEnd}`;
}
<Image
loader={imageLoader}
src={post.image}
fill
alt={post.title}
quality={50}
/>;
const urlStart = config.src.split("upload/")
: 'https://res.cloudinary.com/dwarwdsuf/image/'와 'v1719377658/nextjs-course-mutations/k3t11k3zky99jmmmfh62.jpg'로 분리w_200,h_150,q_${config.quality}
를 사용하면 이미지에 따라 왜곡이 발생할 수 있다. 이를 방지하기 위한 방법은 다음과 같다.const transformations = `w_200,q_${config.quality}`;
fill
을 사용할 때 sizes
를 추가하는 것이 좋다.위의 과정에서 로더를 이용하여 이미지의 특정 너비를 고정해두었기 때문에 사실 sizes 프로퍼티를 추가하지 않아도 된다. 이 경우 fill 프로퍼틑 제거하고 수동으로 width를 추가하여 너비를 설정해도 된다. → height도 설정해야한다는 단점이 있다.
// /components/posts.js
<Image
loader={imageLoader}
src={post.image}
width={200}
height={150}
alt={post.title}
quality={50}
/>
이 데이터는 웹사이트가 크롤러에 표시되는 방식을 정하는 역할을 한다.
직관적으로 메타데이터를 추가하는 방법은 추가하고자하는 페이지로 가서 메타데이터를 추가하고 해당 page.js에서 metadata 상수를 export하는 방법이 있다.
// /app/page.js
import { Suspense } from "react";
import Posts from "@/components/posts";
import { getPosts } from "@/lib/posts";
// 메타데이터 추가
export const metadata = {
title: "Latest Posts",
description: "Browse our latest posts",
};
async function LatestPosts() {
const latestPosts = await getPosts(2);
return <Posts posts={latestPosts} />;
}
export default async function Home() {
return (
<>
<h1>Welcome back!</h1>
<p>Here's what you might've missed.</p>
<section id="latest-posts">
<Suspense fallback={<p>Loading recent posts...</p>}>
<LatestPosts />
</Suspense>
</section>
</>
);
}
메타데이터 객체에는 제목과 설명 필드 외에도 더 많은 필드를 추가할 수 있다.
/app/feed/page.js는 모든 프스트들을 표현할 수 있다. 이곳에 정적 메타데이터를 설정할 수 있다. 그러나 몇몇 페이지에는 동적 메타데이터가 있어 메타데이터가 일정하지 않아 페이지의 데이터가 변경될 때마다 메타데이터도 변경될 수 있다.
// /app/feed/page.js
import Posts from "@/components/posts";
import { getPosts } from "@/lib/posts";
// export const metadata = {
// title: 'Browse all our N posts.',
// description: 'Browse all our posts.'
// }
export async function generateMetadata(config) {
const posts = await getPosts();
const numberOfPosts = posts.length;
return {
title: `Browse all our ${numberOfPosts} posts.`,
description: "Browse all our posts here.",
};
}
export default async function FeedPage() {
const posts = await getPosts();
return (
<>
<h1>All posts by all users</h1>
<Posts posts={posts} />
</>
);
}
layout.js에서도 메타데이터를 설정할 수 있다.
// /app/layout.js
import Header from "@/components/header";
import "./globals.css";
export const metadata = {
title: "NextPosts",
description: "Browse and share amazing posts.",
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<Header />
<main>{children}</main>
</body>
</html>
);
}
layout.js에서 메타데이터를 설정할 때는 자체 메타데이터를 설정하지 않는 모든 페이지에 이 메타데이터가 적용된다. 이 메타데이터를 페이지에서 설정한 메타데이터와 병합될 것이고 페이지에서 메타데이터를 설정하지 않았다면 그 페이지에는 layout.js에서 설정한 메타데이터 객체 전체가 사용된다.