Next.js에서 제공하는 Learn Next.js App Router 튜토리얼을 부연 설명과 함께 한국어로 정리해봤다. 공식 가이드는 총 16개 챕터로 구성되어 있지만, 아래 내용에선 프로젝트 세팅을 다루는 챕터 1과 CSS 스타일링 방법을 설명하는 챕터 2는 생략했다.

<aside> <img src="/icons/search_gray.svg" alt="/icons/search_gray.svg" width="40px" />

최초 페이지 진입시에는 SSR, SSG 등으로 초기 렌더링이 이루어지지만, 이후 next/link 같은 내부 링크를 통해 다른 페이지로 이동할 땐 클라이언트 사이드 라우팅으로 동작한다(새로고침 없이 페이지 전환). 헷갈릴 수 있으니 주의하자 — 참고하면 좋은 글

</aside>

Optimizing Fonts and Images


Cumulative Layout Shift(CLS, 누적 레이아웃 이동)는 구글이 웹사이트의 성능과 사용자 경험을 평가하는 데 사용하는 지표로, 페이지 로드 중 레이아웃 이동으로 인해 발생하는 사용자 경험의 불편함을 측정한다.

예를 들어, 브라우저가 폴백(fallback) 폰트나 시스템 폰트로 텍스트를 먼저 렌더링한 뒤 사용자 지정 폰트로 교체할 때 텍스트 크기, 간격 또는 레이아웃이 변경되어 요소가 이동하는 현상이 발생할 수 있다. 이러한 레이아웃 이동의 빈도와 심각도를 평가하는 것이 CLS의 목적인 것.

Next.js의 next/font 모듈을 사용하면 빌드 시점에 폰트 파일을 다운로드하여 다른 정적 에셋과 함께 호스팅한다. 즉, 사용자가 웹어플리케이션을 방문할 때 성능에 영향을 줄 수 있는 폰트 관련 추가 네트워크 요청이 없다는 것을 의미한다.

Adding a primary font

일반적으로 /app/ui/fonts.ts 같은 파일을 만들어서 어플리케이션 전체에 사용할 글꼴을 정의한다. 아래는 next/font/google 모듈에서 Inter 폰트를 불러온 후 latin 서브셋을 지정한 예시.

import { Inter } from 'next/font/google';

// 폰트 파일은 다양한 언어와 문자(글리프)를 포함하고 있어 크기가 커질 수 있다.
// subsets 옵션을 사용하면 필요한 문자 세트만 포함하도록 제한하여 파일 크기를 줄일 수 있다.
// 아래는 라틴 문자 세트만 포함된 Inter 폰트를 가져온다.
export const inter = Inter({ subsets: ['latin'] });

그런 다음 <body> 엘리먼트에 폰트를 추가한다. 폰트를 body 요소에 추가했으므로 어플리케이션 전체에 적용된다. 참고로 antialiased Tailwind 클래스는 텍스트 렌더링을 부드럽게 만들어 글자를 더 깔끔하게 렌더링해준다.

import '@/app/ui/global.css';
import { inter } from '@/app/ui/fonts';
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={`${inter.className} antialiased`}>{children}</body>
    </html>
  );
}

특정 요소에 secondary font를 적용할 수도 있다. 아래는 Lusitana 폰트를 <p> 태그에만 적용한 예시.

import { Inter, Lusitana } from 'next/font/google';
 
export const inter = Inter({ subsets: ['latin'] });
 
export const lusitana = Lusitana({
  weight: ['400', '700'],
  subsets: ['latin'],
});
// ...
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`}
    >
      {/* ... */}
    </p>
    // ...
  );
}

Why optimize images?

최상위 레벨 /public 폴더에 이미지 같은 파일을 포함시키면, Next.js가 이를 참조하여 정적 파일로 제공할 수 있다. 일반적인 HTML에선 아래처럼 이미지를 경로를 추가하여 사용한다.

<img
  src="/hero.png"
  alt="Screenshots of the dashboard project showing desktop version"
/>