【03】Next.js app routerのチュートリアルやってみる(フォントと画像の最適化)

# はじめに

Next.js app routerのチュートリアルの第３章のアウトプットします。

https://nextjs.org/learn/dashboard-app/optimizing-fonts-images

<details><summary>前の記事</summary>

【01】Next.js app routerのチュートリアルやってみる

https://qiita.com/naoyuki2/items/af58da3d20cbc790e767

【02】Next.js app routerのチュートリアルやってみる

https://qiita.com/naoyuki2/items/edf450b3ee135e83d1e8

</details>

# 第３章 フォントと画像の最適化

この章では下記を学習しました。

- `next/font`を利用して、カスタムフォントを追加する
- `next/image`を利用して、画像を追加する

# next/font

`next/font`を利用することで、ビルド時にフォントファイルをダウンロードすることができる。

これにより、パフォーマンスへの影響が少なくなる。

### メインで使うフォントを追加する

#### fonts.ts

まず、フォントを管理するために`/app/ui/fonts.ts`ファイルを作成する。

そして、下記を貼り付ける。

```ts:/app/ui/fonts.ts
import { Inter } from 'next/font/google';
 
export const inter = Inter({ subsets: ['latin'] });
```

`subsets`というのは、使用する文字セットを指定するためのパラメータです。

これにより、必要な文字だけを含むフォントファイルをダウンロードすることができます。

上記のコードでは、`subsets: ['latin']`と指定しているため、`Inter`のフォントは`ラテン文字`のみダウンロードしています。

#### layout.tsx

メインフォントはすべてのページに適用させたいです。

そんな時は[前回](https://qiita.com/naoyuki2/items/edf450b3ee135e83d1e8)勉強したように、`layout.tsx`に記述をします。

```diff_jsx:/app/layout.tsx
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>
  );
}
```

`body`の`className`に`inter.className`と記述することで、このフォントがアプリケーション全体に適用されます。

`antialiased`は、`tailwind`のクラスで、フォントを滑らかにするクラスだそうです。

https://tailwindcss.com/docs/font-smoothing

下に比較画像を張ってみました。

上がデフォルトの文字で、

下が`antialiased`を適用した文字だそうです。

正直違い分からん。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3582046/a31d9bb1-9a43-11fb-c033-afd7e29ed1a0.png)

### セカンダリフォントを追加する

#### fonts.ts

セカンダリフォントとして、`Lusitana`フォントを使用するために`/app/ui/fonts.ts`ファイルを編集する。

```diff_tsx:/app/ui/fonts.ts
+ import { Inter, Lusitana } from 'next/font/google';
 
export const inter = Inter({ subsets: ['latin'] });
 
+ export const lusitana = Lusitana({
+   weight: ['400', '700'],
+   subsets: ['latin'],
+ });
```

`weight`を指定しているのは、必要な文字の太さだけを指定してダウンロードすることで、不必要なデータのダウンロードを避け、パフォーマンスを向上させることができます。

でも`Lusitana`フォントはそもそも、`weight`が`400`と`700`のやつしかないんだけどね。

#### page.tsx

セカンダリフォントは使用したい要素にのみ、クラス名を割り当てます。

```diff_jsx:/app/page.tsx
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>
    // ...
  );
}
```

今回は、`p`タグに`Lusitana`フォントを適用したいので、`p`タグの`className`に`lusitana.cassName`を記述しています。

これでフォントは完了です。

# next/image

以下のことを自動で行ってくれるのが`next/image`コンポーネントです。

- 画像読み込み時に自動的にレイアウトがずれるのを防ぐ
- 小さなビューポートを備えたデバイスに大きな画像が送信されるのを避けるために、画像のサイズを変更する
- デフォルトで画像を遅延読み込みします (画像はビューポートに入るときに読み込まれます)
- ブラウザがサポートしている場合、AVIFやWebPなどの最新の形式で画像を提供する

`next/iamge`を使用しない場合は、これらを手動ですることになるため大変です。

### 画像を配置してみよう

```diff_tsx:/app/page.tsx
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>
    //...
  );
}
```

上のコードは、チュートリアル公式のコードです。

ですが、`width`と`height`を指定するのが、私は面倒なので他の方法を使います。

```diff_tsx:/app/page.tsx
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';
+ import hero_desktop from '../public/hero-desktop.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">
      {/* Add Hero Images Here */}
+       <Image
+           src={hero_desktop}
+           alt="Acme Hero Image Desktop"
+           className="hidden md:block"
+       />
    </div>
    //...
  );
}
```

下記の記事を参考にしたのですが、Next.js 11のアップデートにより、`width`と`height`の指定が不要になったそうです。

画像を`import`して、それを`Image`コンポーネントの`src`に指定することで、記述が楽になります。

公式でも書いてくれればいいのに。

https://qiita.com/dtakkiy/items/d153cf44de7a6dae9a8f

# おわりに

こういう最適化の積み重ねでWebアプリのパフォーマンスが上がるんでしょうね。

# 次の記事

https://qiita.com/naoyuki2/items/62f9beccbfe36eaf7f90

# 参考

https://nextjs.org/learn/dashboard-app/optimizing-fonts-images

https://tailwindcss.com/docs/font-smoothing

https://qiita.com/dtakkiy/items/d153cf44de7a6dae9a8f