안녕하세요
K-인사이트입니다.
웹사이트에서 폰트는 매우 중요한 요소입니다. 전체적인 마감을 결정하고 서비스의 분위기를 형성하는데 도움을 줍니다. 잘 만들어진 웹사이트들을 보면 폰트 또한 세심하게 조절하여 배치된 것을 볼 수 있습니다. 즉, 여러분의 서비스의 품질을 결정짓는 요소 중에 하나입니다. 기술적인 관점에서도 이러한 폰트 파일을 어떻게 전달하는지도 중요한 요소입니다.
Google은 CLS(Cumulative Layout Shift, 레이아웃 변경 횟수)라는 기준을 통해 웹사이트 성능과 사용자 경험을 평가를 합니다. CLS 측정 항목은 시각적 안정성을 측정하는 항목으로 사용자가 예기치 않은 레이아웃 변경을 경험하는 빈도를 수치화합니다. 그렇다면 폰트가 CLS에 어떤 영향을 끼칠까요?
브라우저가 웹사이트를 로드할 때 시스템 폰트로 렌더링한 다음에 사용자 지정 폰트로 교체할 때 레이아웃 변경이 발생합니다. 즉, 텍스트 크기, 간격의 변화로 인해 주변 요소가 이동할 수 있습니다. 아래의 그림에서 검은색 폰트는 브라우저가 처음에 로딩하였을 때 입니다. 그리고 파란색 글자는 레이아웃 이동이 발생한 다음의 모습입니다. 이러한 구현은 CLS 측정 항목에서 좋은 점수를 받기 어렵습니다. 다시말해 사용자 경험이 나쁨을 의미합니다.
이전 글 바로가기
이 글은 이어진 글입니다. 샘플 프로젝트를 다운로드 받아 환경을 구성해야 한다면 아래의 글을 통해 편리하게 실습 환경을 구성할 수 있습니다.
이 글에서 다루는 내용
Next.js 에서 폰트와 이미지를 최적화하는 방법을 살펴봅니다. 원하는 폰트를 선택해서 적용하는 방법을 다룹니다. 그리고 화면 크기에 따라 이미지를 다르게 보이도록 설정하는 방법을 배웁니다.
- 커스텀 기본 글꼴(Primary Font)을 추가하는 방법
- 커스텀 보조 글꼴(Secondary Font)을 추가하는 방법
- 이미지 최적화에 필요한 기술적 요건
- Image 컴포넌트를 사용해 디바이스 별로 이미지 변경하는 방법
Next.js의 폰트
next/font 모듈을 사용하면, Next.js는 자동으로 폰트를 최적화합니다. 빌드 시간에 폰트 파일들을 다운로드하고 정적 assets 으로 호스팅을 합니다. 이는 사용자가 웹사이트에 방문하면 성능에 영향을 주는 부가적인 네트워크 요청이 발생하지 않음을 의미합니다. 따라서 레이아웃 이동이 발생하지 않으며 CLS 측정 항목에서도 좋은 점수를 받을 수 있게됩니다. 이는 사용자 경험이 개선되는 것을 의미합니다.
커스텀 기본 글꼴 추가
구글 폰트를 추가하고 어떻게 동작하는지 알아보겠습니다. app/ui 폴더에 fonts.ts 파일을 생성합니다. 이 파일을 이용해 어플리케이션 전반의 폰트를 적용할 것입니다. Inter 폰트를 next/font/google 모듈에서 임포트합니다. Inter 폰트는 기본 글꼴이 될것입니다. 그리고 영어로된 사이트를 개발하므로 문자 집합(subset)을 latin 으로 설정하여 알파벳 및 로마자 언어를 표현하도록 설정합니다. fonts.ts 코드는 아래와 같습니다.
import { Inter } from 'next/font/google';
export const inter = Inter({ subsets: ['latin'] });
다음으로는 app/layout.tsx 의 body 에 폰트를 적용합니다. npm run dev 명령어를 통해서 앱을 실행시키고 있다면 변경을 저장함과 동시에 좌측에서 우측으로 폰트 스타일이 변경됩니다.
제대로 잘 적용되었는지 살펴보려면 개발자 도구를 통해서 font-family 속성이 변경된 것을 알 수 있습니다.
body 엘리먼트에 Inter 를 추가하였으므로 어플리케이션 전체에 폰트가 적용되었습니다. Tailwind 에서 제공하는 유틸 클래스인 antialiased 클래스는 폰트를 좀더 부드럽게 만들어줍니다.
보조 글꼴을 추가
이번에는 보조 글꼴을 추가해서 지정된 엘리먼트에 적용해보겠습니다. 다시 fonts.ts 파일으로 돌아가 Lusitana 를 임포트하고 app/page.tsx 파일의 p 엘리먼트에 적용해보겠습니다. 우선 Lusitana 폰트를 불러와 inter와 동일하게 인스턴스를 생성하고 export 합니다.
import { Inter, Lusitana } from 'next/font/google';
export const inter = Inter({ subsets: ['latin'] });
export const lusitana = Lusitana({ weight: "700", subsets: ['latin']});
여기서, Lusitana 폰트의 특징은 weight 옵션을 반드시 제공해야 합니다. 타입 힌트를 보게되면 700 또는 400의 값을 지정하도록 정의되어 있습니다. 만약 이 규칙을 어기면 타입스크립트는 에러를 출력합니다.
export declare function Lusitana<T extends CssVariable | undefined = undefined>(options: {
weight: '400' | '700' | Array<'400' | '700'>;
style?: 'normal' | Array<'normal'>;
display?: Display;
variable?: T;
preload?: boolean;
fallback?: string[];
adjustFontFallback?: boolean;
subsets?: Array<'latin'>;
}): T extends undefined ? NextFont : NextFontWithVariable;
app/page.tsx 파일로 이동하여 p 태그의 className에 보조 글꼴을 추가합니다.
import AcmeLogo from '@/app/ui/acme-logo';
import { ArrowRightIcon } from '@heroicons/react/24/outline';
import Link from 'next/link';
import { lusitana } from '@/app/ui/fonts';
export default function Page() {
return (
// ...
<p
className={`${lusitana.className} text-xl text-gray-800 md:text-3xl md:leading-normal`}
>
<strong>Welcome to Acme.</strong> This is the example for the{' '}
<a href="https://nextjs.org/learn/" className="text-blue-500">
Next.js Learn Course
</a>
, brought to you by Vercel.
</p>
// ...
);
}
또한, AcmeLogo 컴포넌트가 주석처리되어 있는데 이를 주석을 해제시킵니다.
// ...
export default function Page() {
return (
<main className="flex min-h-screen flex-col p-6">
<div className="flex h-20 shrink-0 items-end rounded-lg bg-blue-500 p-4 md:h-52">
<AcmeLogo />
{/* ... */}
</div>
</main>
);
}
이제, 보조 글꼴이 적용되었습니다. 저장을 누르고 폰트가 어떻게 달라졌는지 살펴봅니다. 이제 여러분은 Next.js 에서 원하는 글꼴을 사용하고 적용하는 방법을 익혔습니다. 이를 응용해서 다양한 폰트들을 적용해보면서 홈페이지 성격에 맞는 폰트를 찾는 과정을 거치면 이제 폰트와 관련된 걱정은 필요가 없습니다.
구글 폰트를 통해서 원하는 폰트들을 검색할 수 있습니다. 아래의 링크로 접속하여 다양한 폰트를 테스트해봅니다.
이미지 최적화란?
Next.js 는 이미지와 같은 정적인 에셋(assets)을 제공할 수 있습니다. 프로젝트 루트에 위치한 public 폴더 내에 있는 파일들은 어플리케이션에서 참조가 가능하며 아래와 같은 형태로 참조합니다. 아래의 샘플 코드는 public 폴더 내 hero.png 이미지 파일을 참조하는 코드입니다.
<img
src="/hero.png"
alt="Screenshots of the dashboard project showing desktop version"
/>
하지만 여기에 문제가 여럿 있습니다.
- 디바이스 화면 별로 이미지가 반응하는지 확인
- 디바이스 크기에 맞는 이미지 크기를 지정
- 이미지가 로드 될때 레이아웃 이동이 발생하지 않도록 제어
- 사용자 뷰포트에서 벗어난 이미지를 Lazy Load 처리
이미지 최적화를 수동으로 구현하는 대신 next/image 컴포넌트를 사용하면 자동으로 이미지를 최적화할 수 있습니다.
Image 컴포넌트 사용
Image 컴포넌트는 img 태그의 확장입니다. 자동으로 이미지 최적화를 제공해줍니다.
- 이미지가 로드될 때 레이아웃이 자동으로 바뀌는 것을 방지
- 뷰포트가 작은 장치에 큰 이미지가 전송되지 않도록 이미지 크기 조정.
- 기본적으로 이미지 지연 로딩(이미지가 뷰포트에 들어갈 때 이미지가 로드됨).
- 브라우저에서 WebP 및 AVIF와 같은 최신 형식을 지원하는 경우 이미지를 제공
Image 컴포넌트 사용 예시
Image 컴포넌트를 사용해보겠습니다. public 폴더를 보면 이미 hero-desktop.png 와 hero-mobile.png 파일을 볼 수 있습니다. 두 파일의 모양은 사용자 디바이스에 맞추어 모바일 버전과 데스크탑 버전으로 구분되어 있음을 알 수 있습니다. 이제 사용자 디바이스 유형에 맞추어 이미지가 바뀌도록 구현해보겠습니다.
app/page.tsx 파일에 next/image 모듈로부터 Image 컴포넌트를 불러옵니다. 그리고 아래와 같이 코드를 추가해줍니다.
import AcmeLogo from '@/app/ui/acme-logo';
import { ArrowRightIcon } from '@heroicons/react/24/outline';
import Link from 'next/link';
import { lusitana } from '@/app/ui/fonts';
import Image from 'next/image';
export default function Page() {
return (
// ...
<div className="flex items-center justify-center p-6 md:w-3/5 md:px-28 md:py-12">
{/* Add Hero Images Here */}
<Image
src="/hero-desktop.png"
width={1000}
height={760}
className="hidden md:block"
alt="Screenshots of the dashboard project showing desktop version"
/>
</div>
//...
);
}
코드의 내용은 단순합니다. 너비를 1000픽셀, 높이를 760픽셀로 설정합니다. 여기서 중요한 것은 원본 이미지와 동일한 가로 세로 비율을 유지해야 합니다.
그리고 모바일 디바이스일 경우 DOM에서 이미지를 제거하기 위한 hidden 유틸 클래스를 지정합니다. 데스크톱 디바이스일 경우 이미지를 표시하기 위한 md:block 을 나란히 지정합니다.
데스크탑 화면일 경우 왼쪽 그림처럼 이미지가 출력됩니다. 그리고 모바일 화면으로 전환될 경우 이미지가 DOM에서 제거된 것을 확인할 수 있습니다.
앞서 배운것을 응용해서 모바일 화면일 경우 반대로 block 을 설정하고 데스크톱 디바이스일 경우 이미지를 제거하는 md:hidden 설정을 적용하면 모바일 화면 적용도 쉽게 마무리할 수 있습니다.
<div className="flex items-center justify-center p-6 md:w-3/5 md:px-28 md:py-12">
{/* Add Hero Images Here */}
<Image
src="/hero-desktop.png"
width={1000}
height={760}
className="hidden md:block"
alt="Screenshots of the dashboard project showing desktop version"
/>
<Image
src="/hero-mobile.png"
width={560}
height={620}
className="block md:hidden"
alt="Screenshot of the dashboard project showing mobile version"
/>
</div>
이상입니다.
K-인사이트 올림.
'프로그래밍 > Next.js' 카테고리의 다른 글
NEXT.JS 페이지 이동(Navigating)과 코드 스플리팅 (83) | 2024.04.21 |
---|---|
NEXT.JS 중첩 라우팅(Nested Routing)과 레이아웃(Layout) 적용 (88) | 2024.04.21 |
NEXT.JS 웹사이트 스타일링(CSS)을 위한 전문적인 방법들 (50) | 2024.04.05 |
NEXT.JS 일단 프로젝트부터 생성해서 공부해보자! (59) | 2024.04.02 |