공식 문서 보기를 돌같이 하는 버릇을 고치자!
이 장에서는 next/font
와 next/image
적용과 최적화 방법을 배운다.
기본 폰트가 아닌 사용자 정의 폰트는 외부에서 로드하는 시간이 필요하다. 그 과정에서 Cumulative Layout Shift(CLS) 점수를 높여 나쁜 사용자 경험을 제공할 수도 있다.
Cumulative Layout Shift란, 구글에서 레이아웃 변경을 측정하는 지표이다. 요소의 이동이나 변경이 많을수록 높은 점수가 측정된다.
Next
에서는 next/font
를 이용해 빌드 시점에 폰트를 다운로드하여 자동 최적화한다.
next/font/google
에서 Inter
폰트를 가져온다.
// app/ui/fonts.ts
import { Inter } from 'next/font/google';
export const inter = Inter({ subsets: ['latin'] });
폰트를 layout
의 body
에 추가하여 기본 폰트로 설정한다.
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>
);
}
antialiased
는 Tailwind
코드로, 폰트를 부드럽게 처리하는 클래스이다. 멋지라고 넣었다고 한다.
이미지 파일을 수동으로 지정할 경우 다양한 부분을 고려하기 어려워 진다.
이런 포인트를 next/image
모듈의 <Image>
컴포넌트를 이용해 자동으로 최적화한다.
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">
<Image
src="/hero-desktop.png"
width={1000}
height={760}
className="hidden md:block"
alt="Screenshots of the dashboard project showing desktop version"
/>
</div>
//...
);
}
이미지가 로드되는 동안 레이아웃이 변경되지 않도록 width
와 height
를 지정하고, 원본과 같은 비율로 설정하는 것이 좋다.
width
와 height
를 비율에 맞춰 자동으로 설정하려면 이미지를 import해 이미지 컴포넌트에 제공한다.
import Image from 'next/image';
import heroMobile from '../public/hero-mobile.png';
export default function Page() {
return (
// ...
<div className="flex items-center justify-center p-6 md:w-3/5 md:px-28 md:py-12">
<Image
src={heroMobile}
className="block md:hidden"
alt="Screenshots of the dashboard project showing mobile version"
/>
</div>
//...
);
}
이미지나 폰트 최적화에 관한 자세한 정보는 Image Optimization과 Font Optimization를 참고한다.
새로운 페이지를 만드는 장이다. 파일 시스템 라우트를 사용하여 파일과 폴더의 역할과 layout
을 이해하는 것이 주 목표이다.
파일 시스템 라우팅에서 폴더는 URL
의 path에 따라 구분하는 역할을 한다. 최상위인 app
은 root(/)
를 의미하고, 하위 폴더들은 pathname
을 의미한다. 즉, /
로 구분되는 영역이다. 예를 들어, localhost / dashboard / invoices
경로라면, 🗂app/🗂dashboard/🗂invoices
이다.
각각의 폴더는 layout.tsx
과 page.tsx
파일을 갖는다. page.tsx
는 라우트에 해당하는 view 컴포넌트를 내보내는 역할을 한다. 🗂app/🗂dashboard/page.tsx
를 만들고 localhost:3000/dashboard
에 접근하면 해당 컴포넌트가 렌더링된 화면을 볼 수 있다.
🗂app/🗂dashboard/sidebar.tsx
라는 파일을 만들었을 때, /dashboard/sidebar
로 접근할 수 있는 게 아닌가 하는 의문이 들었다. 하지만 Next.js
에서는 page
와 ui 컴포넌트, 테스트 파일 등이 공존 가능한 colocation을 허용한다. 오직 page.tsx
파일이 있어야 라우트로 접근할 수 있다.
layout.tsx
파일은 여러 페이지에서 같은 레이아웃을 공유하는 역할이다. layout
의 이점 중 하나는 다른 페이지 컴포넌트를 업데이트할 때 layout
은 리렌더링을 하지 않는다는 것이다. partial rendering이라고 부른다고 한다.
나의 실행 환경에서는 페이지 이동마다 layout
도 리렌더링되던데...게다가 SPA
가 아닌 MPA
처럼 동작한다. 뭔가 조치가 더 필요한 건가? 약간의 의문 추가.
🗂app/layout.tsx
는 Root layout으로, 모든 페이지가 공유하는 필수 레이아웃이다. 여기에서 <html>
과 <body>
태그를 수정하거나 metadata
를 추가할 수도 있다.
페이지 간의 이동을 다루는 장이다. 위에서 품었던 의문이 곧바로 해결되었다.
dashboard
의 하위 페이지 간 이동은 <a>
태그를 이용하고 있었다. 이동마다 전체 페이지가 새로고침되는 원인이었다. next/link
의 <Link>
컴포넌트로 대체하면 새로고침 없이 이동한다.
import Link from 'next/link';
export default function NavLinks() {
return (
<>
{/* ... */}
<Link key={link.name} href={link.href}>
<LinkIcon className="w-6" />
<p className="hidden md:block">{link.name}</p>
</Link>
{/* ... */}
</>
);
}
Next.js
는 자동으로 코드를 분할한다. 분할된 코드는 고립되었다는 의미이며, 이 페이지에서 에러가 발생해도 다른 페이지는 정상 동작한다. 또한, <Link>
컴포넌트가 동작할 때마다 백그라운드에서 라우트의 코드를 prefetch
한다. 백그라운드에서 미리 로드된 목적지 페이지는 사용자가 클릭했을 때 거의 즉시 전환된다.
일반적인 UI는 현재 페이지와 같은 링크를 활성화 상태로 보여준다. 그러기 위해서 현재 pathname
을 알아야 한다. next/navigation
의 usePathname
을 사용해 pathname
에 접근한다.
'use client';
import { usePathname } from 'next/navigation';
훅은 Client Component
에서 사용하므로 최상단에 use client
를 명시해야 한다.