🦾

Next.js チュートリアルやってみた③

2024/12/15に公開

初めに

今回の記事はNext.jsのチュートリアル12章から16章までの記事です。

12章 データの変更

サーバーアクションとは何ですか?

React Server Actions を使用すると、サーバー上で直接非同期コードを実行できます。これにより、データを変更するための API エンドポイントを作成する必要がなくなります。代わりに、サーバー上で実行され、クライアントまたはサーバー コンポーネントから呼び出すことができる非同期関数を記述します。

ウェブ アプリケーションはさまざまな脅威に対して脆弱であるため、セキュリティは最優先事項です。ここで、サーバー アクションが役立ちます。サーバー アクションは、さまざまな種類の攻撃から保護し、データを保護し、承認されたアクセスを保証する効果的なセキュリティ ソリューションを提供します。サーバー アクションは、POST リクエスト、暗号化されたクロージャ、厳格な入力チェック、エラー メッセージのハッシュ化、ホスト制限などの手法を使用してこれを実現し、これらがすべて連携してアプリの安全性を大幅に強化します。

サーバーアクションでフォームを使用する

Reactでは、要素action内の属性を使用して<form>アクションを呼び出すことができます。アクションは自動的にネイティブのFormDataを受け取ります。キャプチャされたデータを含むオブジェクト。

// Server Component
export default function Page() {
  // Action
  async function create(formData: FormData) {
    'use server';
 
    // Logic to mutate data...
  }
 
  // Invoke the action using the "action" attribute
  return <form action={create}>...</form>;
}

サーバー コンポーネント内でサーバー アクションを呼び出す利点は、プログレッシブ エンハンスメントです。つまり、クライアントで JavaScript が無効になっている場合でもフォームが機能します。

サーバーアクションを備えた Next.js

サーバーアクションはNext.jsのキャッシュとも深く統合されています。フォームがサーバーアクションを通じて送信されると、アクションを使用してデータを変更できるだけでなく、revalidatePathrevalidateTagなどの API を使用して関連するキャッシュを再検証することもできます。

知っておくとよいこと: HTML では、属性に URL を渡しますaction。この URL は、フォーム データの送信先 (通常は API エンドポイント) になります。
ただし、React では、action属性は特別なプロパティと見なされます。つまり、React はその上に構築され、アクションを呼び出せるようになります。
バックグラウンドでは、サーバー アクションがPOSTAPI エンドポイントを作成します。そのため、サーバー アクションを使用するときに API エンドポイントを手動で作成する必要はありません。

13章 エラーの処理

JavaScriptのステートメントと Next.js API を使用してエラーを適切にtry/catch処理する方法を見てみましょう。
error.tsx特別なファイルを使用してルート セグメント内のエラーをキャッチし、ユーザーにフォールバック UI を表示する方法。
notFound関数とnot-foundファイルを使用して 404 エラー (存在しないリソースの場合) を処理する方法。
try/catchの処理方法
tryの内容が失敗したら、catch内の処理に移行するのみ…

before

'use server';
import { z } from 'zod';
import { sql } from '@vercel/postgres';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';

const FormSchema = z.object({
  id: z.string(),
  customerId: z.string(),
  amount: z.coerce.number(),
  status: z.enum(['pending', 'paid']),
  date: z.string(),
});
 
const CreateInvoice = FormSchema.omit({ id: true, date: true });

export async function createInvoice(formData: FormData) {
  const { customerId, amount, status } = CreateInvoice.parse({
    customerId: formData.get('customerId'),
    amount: formData.get('amount'),
    status: formData.get('status'),
  });
  const amountInCents = amount * 100;
  const date = new Date().toISOString().split('T')[0];

  await sql`
  INSERT INTO invoices (customer_id, amount, status, date)
  VALUES (${customerId}, ${amountInCents}, ${status}, ${date})
`;
  revalidatePath('/dashboard/invoices');
  redirect('/dashboard/invoices');
}

const UpdateInvoice = FormSchema.omit({ id: true, date: true });

export async function updateInvoice(id: string, formData: FormData) {
  const { customerId, amount, status } = UpdateInvoice.parse({
    customerId: formData.get('customerId'),
    amount: formData.get('amount'),
    status: formData.get('status'),
  });
 
  const amountInCents = amount * 100;
 
  await sql`
    UPDATE invoices
    SET customer_id = ${customerId}, amount = ${amountInCents}, status = ${status}
    WHERE id = ${id}
  `;
 
  revalidatePath('/dashboard/invoices');
  redirect('/dashboard/invoices');
}

export async function deleteInvoice(id: string) {
  await sql`DELETE FROM invoices WHERE id = ${id}`;
  revalidatePath('/dashboard/invoices');
}

after

'use server';
import { z } from 'zod';
import { sql } from '@vercel/postgres';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';

const FormSchema = z.object({
  id: z.string(),
  customerId: z.string(),
  amount: z.coerce.number(),
  status: z.enum(['pending', 'paid']),
  date: z.string(),
});
 
const CreateInvoice = FormSchema.omit({ id: true, date: true });

export async function createInvoice(formData: FormData) {
  const { customerId, amount, status } = CreateInvoice.parse({
    customerId: formData.get('customerId'),
    amount: formData.get('amount'),
    status: formData.get('status'),
  });
 
  const amountInCents = amount * 100;
  const date = new Date().toISOString().split('T')[0];
 
  try {
    await sql`
      INSERT INTO invoices (customer_id, amount, status, date)
      VALUES (${customerId}, ${amountInCents}, ${status}, ${date})
    `;
  } catch (error) {
    return {
      message: 'Database Error: Failed to Create Invoice.',
    };
  }
 
  revalidatePath('/dashboard/invoices');
  redirect('/dashboard/invoices');
}

const UpdateInvoice = FormSchema.omit({ id: true, date: true });

export async function updateInvoice(id: string, formData: FormData) {
  const { customerId, amount, status } = UpdateInvoice.parse({
    customerId: formData.get('customerId'),
    amount: formData.get('amount'),
    status: formData.get('status'),
  });
 
  const amountInCents = amount * 100;
 
  try {
    await sql`
        UPDATE invoices
        SET customer_id = ${customerId}, amount = ${amountInCents}, status = ${status}
        WHERE id = ${id}
      `;
  } catch (error) {
    return { message: 'Database Error: Failed to Update Invoice.' };
  }
 
  revalidatePath('/dashboard/invoices');
  redirect('/dashboard/invoices');
}

export async function deleteInvoice(id: string) {
  try {
    await sql`DELETE FROM invoices WHERE id = ${id}`;
    revalidatePath('/dashboard/invoices');
    return { message: 'Deleted Invoice.' };
  } catch (error) {
    return { message: 'Database Error: Failed to Delete Invoice.' };
  }
}

redirectがブロックの外で呼び出されていることに注意してください。これは、redirectがエラーを投げる事で動作し、それがcatchブロックによってキャッチされるためです。

これを回避するには、try/catchの後にredirectを呼び出すことができます。redirectは、 try/catchが成功した場合にのみ到達可能です。

error.tsx

'use client';
 
import { useEffect } from 'react';
 
export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    // Optionally log the error to an error reporting service
    console.error(error);
  }, [error]);
 
  return (
    <main className="flex h-full flex-col items-center justify-center">
      <h2 className="text-center">Something went wrong!</h2>
      <button
        className="mt-4 rounded-md bg-blue-500 px-4 py-2 text-sm text-white transition-colors hover:bg-blue-400"
        onClick={
          () => reset()
        }
      >
        Try again
      </button>
    </main>
  );
}
  • クライアント コンポーネントである必要があります。
  • 2 つのプロパティを受け入れます:
    • error[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) : このオブジェクトはJavaScriptのネイティブインスタンスです。
    • reset: これはエラー境界をリセットする関数です。実行すると、この関数はルートセグメントの再レンダリングを試みます。

notFound関数による404エラーの処理

エラーを適切に処理するもう 1 つの方法は、notFound関数を使用することです。error.tsxすべてのエラーをキャッチするのに便利ですが、存在しないリソースを取得しようとするときにも使用できます。

例えばユーザーがデータベースに存在しない偽の UUID をクリックした際に、404エラーの内容が必要になります。

import Link from 'next/link';
import { FaceFrownIcon } from '@heroicons/react/24/outline';
 
export default function NotFound() {
  return (
    <main className="flex h-full flex-col items-center justify-center gap-2">
      <FaceFrownIcon className="w-10 text-gray-400" />
      <h2 className="text-xl font-semibold">404 Not Found</h2>
      <p>Could not find the requested invoice.</p>
      <Link
        href="/dashboard/invoices"
        className="mt-4 rounded-md bg-blue-500 px-4 py-2 text-sm text-white transition-colors hover:bg-blue-400"
      >
        Go Back
      </Link>
    </main>
  );
}

notFounderror.tsxよりも優先されるため、より具体的なエラーを処理したいときに を使用できます。

14章 アクセシビリティの向上

前の章では、エラー(404エラーを含む)をキャッチしてユーザーにフォールバックを表示する方法を見てきました。しかし、まだパズルのもう1つのピースであるフォーム検証について議論する必要があります。サーバーアクションを使用してサーバーサイド検証を実装する方法と、ReactのuseActionStateフック - アクセシビリティを考慮しながら!

アクセシビリティとは何ですか?

アクセシビリティとは、障害のある人を含め、誰もが使用できる Web アプリケーションを設計および実装することを指します。これは、キーボード ナビゲーション、セマンティック HTML、画像、色、ビデオなど、多くの領域をカバーする広範なトピックです。

このコースではアクセシビリティについて詳しく説明しません。ただし、Next.js で利用できるアクセシビリティ機能と、アプリケーションのアクセシビリティを向上させるための一般的な方法について説明します。

アクセシビリティについてさらに詳しく知りたい場合は、「アクセシビリティを学ぶ」をお勧めします。

Next.js で ESLint アクセシビリティ プラグインを使用する

Next.jsには、 eslint-plugin-jsx-a11y プラグインを ESLint 設定に追加すると、アクセシビリティの問題を早期に検出できるようになります。たとえば、このプラグインは、altテキストのない画像がある場合や、 aria-* および role 属性を誤って使用している場合などに警告します。

package.jsonに"lint": "next lint"を追加

"scripts": {
    "build": "next build",
    "dev": "next dev",
    "start": "next start",
    "lint": "next lint"
},

次に、pnpm lintターミナルで次のコマンドを実行します:

pnpm lint

これは、プロジェクトに ESLint をインストールして構成する手順をガイドします。
pnpm lintを実行すると、次の出力が表示されれば問題ないです。

✔ No ESLint warnings or errors

フォームのアクセシビリティの向上

フォームのアクセシビリティを向上させるために、すでに次の 3 つのことを行っています。

  • セマンティック HTML<input><option><div>

    セマンティック要素を使用します。これにより、支援技術 (AT) が入力要素に焦点を合わせ、ユーザーに適切なコンテキスト情報を提供できるようになり、フォームの操作と理解が容易になります。

  • ラベリング<label> : 属性を含めると、htmlFor各フォーム フィールドに説明的なテキスト ラベルが付けられます。これにより、コンテキストが提供されて AT サポートが向上し、ユーザーがラベルをクリックして対応する入力フィールドにフォーカスできるようになるため、使いやすさも向上します。

  • フォーカス アウトライン: フィールドは、フォーカスされているときにアウトラインが表示されるように適切にスタイル設定されています。これは、ページ上のアクティブな要素を視覚的に示し、キーボードとスクリーン リーダーの両方のユーザーがフォーム上のどこにいるかを理解するのに役立つため、アクセシビリティにとって重要です。tabを押すとこれを確認できます。

これらのプラクティスは、フォームを多くのユーザーが利用しやすくするための優れた基盤となります。ただし、フォームの検証エラーには対処していません。

クライアント側の検証

クライアントでフォームを検証する方法はいくつかあります。最も簡単な方法は、フォームのrequired要素を<input><select>要素に追加して、ブラウザが提供するフォーム検証を利用することです。

<input
  id="amount"
  name="amount"
  type="number"
  placeholder="Enter USD amount"
  className="peer block w-full rounded-md border border-gray-200 py-2 pl-10 text-sm outline-2 placeholder:text-gray-500"
  required
/>

サーバー側検証

サーバー上でフォームを検証することで、次のことが可能になります。

  • データをデータベースに送信する前に、データが期待どおりの形式であることを確認してください。
  • 悪意のあるユーザーがクライアント側の検証をバイパスするリスクを軽減します。
  • 有効なデータとみなされるものについて、信頼できる唯一の情報源を確保します。

import { useActionState } from 'react';を追加する。

フォーム コンポーネント内のuseActionStateフック:

  • 2 つの引数を取ります: (action, initialState)

  • 2 つの値を返します:[state, formAction]

    フォームの状態と、フォームが送信されたときに呼び出される関数。
    createInvoiceアクションを の引数として渡しuseActionState<form action={}>属性内で formActionを呼び出します。
    下記のコードでは、次の aria ラベルも追加しています。

  • aria-describedby="customer-error
    これにより、select要素とエラーメッセージ コンテナーの関係が確立されます。
    これはid="customer-error"のコンテナがselect要素を記述していることを示します。
    スクリーン リーダーは、ユーザーがselectボックスを操作してエラーを通知するときにこの説明を読み取ります。

  • id="customer-error"idselectaria-describedby
    このid属性は、入力のエラー メッセージを保持する HTML 要素を一意に識別します。これは、aria-describedby関係を確立するために必要です。

  • aria-live="polite"
    スクリーン リーダーは、divコンテンツ内のエラーが更新されたときに、ユーザーに丁寧に通知する必要があります
    。コンテンツが変更されると (たとえば、ユーザーがエラーを修正したとき)、スクリーン リーダーはこれらの変更を通知しますが、ユーザーの邪魔にならないように、ユーザーがアイドル状態のときにのみ通知します。

15章 認証の追加

認証とは何ですか?

認証は、今日の多くの Web アプリケーションにとって重要な部分です。認証とは、ユーザーが本人であるかどうかをシステムが確認する方法です。

安全なウェブサイトでは、多くの場合、複数の方法でユーザーの身元を確認します。たとえば、ユーザー名とパスワードを入力すると、サイトからデバイスに確認コードが送信されたり、Google Authenticator などの外部アプリが使用されたりします。この 2 要素認証 (2FA) により、セキュリティが強化されます。たとえ誰かがパスワードを知ったとしても、固有のトークンがなければアカウントにアクセスすることはできません。

認証と承認

Web 開発では、認証と承認はそれぞれ異なる役割を果たします。

  • 認証
    ユーザーが本人であることを確認することです。ユーザー名やパスワードなど、何かを使って自分の身元を証明します。

  • 次のステップは承認
    ユーザーの身元が確認されると、承認によってアプリケーションのどの部分をユーザーが使用できるかが決定されます。

したがって、認証によってユーザーが誰であるかが確認され、認可によってアプリケーション内でユーザーが実行できる操作やアクセスできる内容が決定されます。
NextAuth.jsを使用します。

NextAuth.js は、セッション、サインインとサインアウト、その他の認証の側面の管理に伴う複雑さの多くを抽象化します。これらの機能を手動で実装することもできますが、プロセスには時間がかかり、エラーが発生しやすくなります。NextAuth.js はプロセスを簡素化し、Next.js アプリケーションでの認証のための統合ソリューションを提供します。

ターミナルで次のコマンドを実行して NextAuth.js をインストールします。
pnpm i next-auth@beta
betaここでは、 Next.js 14 と互換性のあるバージョンの NextAuth.js をインストールします。
次に、アプリケーションの秘密鍵を生成します。この鍵は Cookie を暗号化して、ユーザー セッションのセキュリティを確保するために使用されます。これを行うには、ターミナルで次のコマンドを実行します。
openssl rand -base64 32
次に、.envファイル内で、生成されたキーをAUTH_SECRET変数に追加します。
AUTH_SECRET=your-secret-key

認証を本番環境で機能させるには、Vercelプロジェクトの環境変数も更新する必要があります。このガイドをご覧ください。Vercel に環境変数を追加する方法について説明します。

auth.config.tsを作成して下記のようにします。

import type { NextAuthConfig } from 'next-auth';
 
export const authConfig = {
  pages: {
    signIn: '/login',
  },
  callbacks: {
    authorized({ auth, request: { nextUrl } }) {
      const isLoggedIn = !!auth?.user;
      const isOnDashboard = nextUrl.pathname.startsWith('/dashboard');
      if (isOnDashboard) {
        if (isLoggedIn) return true;
        return false; // Redirect unauthenticated users to login page
      } else if (isLoggedIn) {
        return Response.redirect(new URL('/dashboard', nextUrl));
      }
      return true;
    },
  },
  providers: [], // Add providers with an empty array for now
} satisfies NextAuthConfig;

callbacks はログイン後の処理と思ってください
authorizedcallbacksは、リクエストがNext.jsミドルウェア経由でページにアクセスすることを許可されているかどうかを確認するために使用されます。
リクエストが完了する前に呼び出され、auth プロパティと request プロパティを持つオブジェクトを受け取ります。
authプロパティにはユーザーのセッションが含まれ、requestプロパティには入力されたリクエストが含まれる。
プロバイダオプションは、さまざまなログインオプションを列挙する配列です。
今のところ、NextAuthの設定を満たすための空の配列です。 詳しくは認証情報プロバイダの追加で説明します。

次に、authConfigオブジェクトをMiddlewareファイルにインポートします。 プロジェクトのルートに middleware.ts というファイルを作成し、以下のコードを貼り付けます

import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
 
export default NextAuth(authConfig).auth;
 
export const config = {
  // https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
  matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
};

ここではNextAuth.jsをauthConfigオブジェクトで初期化し、authプロパティをエクスポートしています。
また、Middleware の matcher オプションを使用して、特定のパスで実行するように指定しています。
このタスクに Middleware を使用する利点は、Middleware が認証を確認するまで、保護されたルートのレンダリングが開始されないことです。

パスワードのハッシュ化

パスワードをデータベースに保存する前にハッシュ化するのは良い習慣です。
ハッシュはパスワードを固定長の文字列に変換し、ランダムに見えるようにします。 seed.jsファイルでは、データベースに保存する前にbcryptというパッケージを使ってユーザーのパスワードをハッシュ化しました。
ユーザーが入力したパスワードがデータベースのパスワードと一致するかどうかを比較するために、再びこのパッケージを使用します。

ただし、bcryptパッケージ用に別のファイルを作成する必要があります。 これは、bcryptがNext.jsミドルウェアでは利用できないNode.js APIに依存しているためです。
auth.tsという新しいファイルを作成し、authConfigオブジェクトを広げます。

そしてNextAuth.jsのプロバイダオプションを追加します。プロバイダとは、GoogleやGitHubなどのさまざまなログインオプションを列挙する配列です。 このコースでは、Credentials プロバイダーだけを使うことにします。 Credentials プロバイダーは、ユーザーがユーザー名とパスワードでログインできるようにします。

import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
import Credentials from 'next-auth/providers/credentials';
 
export const { auth, signIn, signOut } = NextAuth({
  ...authConfig,
  providers: [Credentials({})],
});

サインイン機能の追加

認証ロジックを処理するには、authorize関数を使用します。 Server Actions と同様に、zod を使用してメールとパスワードを検証してから、そのユーザがデータベースに存在するかどうかをチェックします。

資格情報を検証した後、getUserデータベースからユーザーを照会する新しい関数を作成します。
次に、bcrypt.compareパスワードが一致するかどうかを確認するために呼び出します。
最後に、パスワードが一致する場合はユーザーを返し、一致しない場合はnullユーザーがログインできないように戻します。

import NextAuth from 'next-auth';
import Credentials from 'next-auth/providers/credentials';
import { authConfig } from './auth.config';
import { z } from 'zod';
import { sql } from '@vercel/postgres';
import type { User } from '@/app/lib/definitions';
import bcrypt from 'bcrypt';
 
async function getUser(email: string): Promise<User | undefined> {
  try {
    const user = await sql<User>`SELECT * FROM users WHERE email=${email}`;
    return user.rows[0];
  } catch (error) {
    console.error('Failed to fetch user:', error);
    throw new Error('Failed to fetch user.');
  }
}
 
export const { auth, signIn, signOut } = NextAuth({
  ...authConfig,
  providers: [
    Credentials({
      async authorize(credentials) {
        const parsedCredentials = z
          .object({ email: z.string().email(), password: z.string().min(6) })
          .safeParse(credentials);
 
        if (parsedCredentials.success) {
          const { email, password } = parsedCredentials.data;
          const user = await getUser(email);
          if (!user) return null;
          const passwordsMatch = await bcrypt.compare(password, user.password);

          if (passwordsMatch) return user;
        }
 
        console.log('Invalid credentials');
        return null;
      },
    }),
  ],
});

ログインフォームの更新

認証ロジックをログインフォームに接続する必要があります。
actions.tsファイルに、authenticateという新しいアクションを作成します。
このアクションは、auth.tsからsignIn関数をインポートする必要があります:

'use server';
 
import { signIn } from '@/auth';
import { AuthError } from 'next-auth';
 
export async function authenticate(
  prevState: string | undefined,
  formData: FormData,
) {
  try {
    await signIn('credentials', formData);
  } catch (error) {
    if (error instanceof AuthError) {
      switch (error.type) {
        case 'CredentialsSignin':
          return 'Invalid credentials.';
        default:
          return 'Something went wrong.';
      }
    }
    throw error;
  }
}

CredentialsSignin' エラーが発生した場合は、適切なエラーメッセージを表示します。
NextAuth.jsのエラーについてはドキュメントを参照してください。
Auth.js | Errors

最後に、login-form.tsxコンポーネントで、ReactのuseActionStateを使用して、サーバーアクションを呼び出し、フォームエラーを処理し、フォームの保留状態を表示することができます:

ログアウト機能の追加

<SideNav />にログアウト機能を追加するには、signOut要素内のauth.tsから<form>関数を呼び出します。

import Link from 'next/link';
import NavLinks from '@/app/ui/dashboard/nav-links';
import AcmeLogo from '@/app/ui/acme-logo';
import { PowerIcon } from '@heroicons/react/24/outline';
import { signOut } from '@/auth';
 
export default function SideNav() {
  return (
    <div className="flex h-full flex-col px-3 py-4 md:px-2">
      // ...
      <div className="flex grow flex-row justify-between space-x-2 md:flex-col md:space-x-0 md:space-y-2">
        <NavLinks />
        <div className="hidden h-auto w-full grow rounded-md bg-gray-50 md:block"></div>
        <form
          action={async () => {
            'use server';
            await signOut();
          }}
        >
          <button className="flex h-[48px] grow items-center justify-center gap-2 rounded-md bg-gray-50 p-3 text-sm font-medium hover:bg-sky-100 hover:text-blue-600 md:flex-none md:justify-start md:p-2 md:px-3">
            <PowerIcon className="w-6" />
            <div className="hidden md:block">Sign Out</div>
          </button>
        </form>
      </div>
    </div>
  );
}

16章 メタデータの追加

メタデータは SEO と共有性にとって重要です。この章では、Next.js アプリケーションにメタデータを追加する方法について説明します。

メタデータとは何ですか?

Web 開発では、メタデータは Web ページに関する追加の詳細を提供します。メタデータは、ページを訪問するユーザーには表示されません。代わりに、ページの HTML (通常は<head>要素内) に埋め込まれ、舞台裏で機能します。この非表示の情報は、Web ページのコンテンツをよりよく理解する必要がある検索エンジンやその他のシステムにとって重要です。

メタデータはなぜ重要ですか?

メタデータは、ウェブページの SEO を強化し、検索エンジンやソーシャル メディア プラットフォームにとってアクセスしやすく理解しやすいものにする上で重要な役割を果たします。適切なメタデータは、検索エンジンがウェブページを効果的にインデックスし、検索結果でのランキングを向上させるのに役立ちます。さらに、Open Graph などのメタデータは、ソーシャル メディアで共有されるリンクの外観を改善し、ユーザーにとってコンテンツをより魅力的で有益なものにします。

メタデータの種類

メタデータにはさまざまな種類があり、それぞれ独自の目的を持っています。一般的な種類には次のようなものがあります。

タイトル メタデータ: ブラウザ タブに表示される Web ページのタイトルを担当します。検索エンジンが Web ページの内容を理解するのに役立つため、SEO にとって非常に重要です。
<title>Page Title</title>

説明メタデータ: このメタデータは、Web ページのコンテンツの簡単な概要を提供し、多くの場合、検索エンジンの結果に表示されます。
<meta name="description" content="A brief description of the page content." />

キーワード メタデータ: このメタデータには、Web ページのコンテンツに関連するキーワードが含まれており、検索エンジンがページをインデックスするのに役立ちます。
<meta name="keywords" content="keyword1, keyword2, keyword3" />

Open Graph メタデータ: このメタデータは、タイトル、説明、プレビュー画像などの情報を提供することで、ソーシャル メディア プラットフォームで共有されるときに Web ページが表示される方法を強化します。
<meta property="og:title" content="Title Here" />
<meta property="og:description" content="Description Here" />
<meta property="og:image" content="image_url_here" />

ファビコン メタデータ: このメタデータは、ブラウザのアドレス バーまたはタブに表示されるファビコン (小さなアイコン) を Web ページにリンクします。
<link rel="icon" href="path/to/favicon.ico" />
Next.jsには、アプリケーションのメタデータを定義するためのMetadata APIがあります。
アプリケーションにメタデータを追加するには、次の2つの方法があります
設計ベース: 静的なメタデータオブジェクトまたは動的なgenerateMetadata関数をlayout.jsまたはpage.jsファイルにエクスポートします。

ファイルベース

  • favicon.ico、apple-icon.jpg、icon.jpg: ファビコンやアイコンに使用
  • opengraph-image.jpg、twitter-image.jpg:ソーシャルメディアの画像に使用
  • robots.txt:検索エンジンのクロールのための指示
  • sitemap.xml:ウェブサイトの構造に関する情報を提供

これらのファイルを静的なメタデータとして使用することも、プロジェクト内でプログラム的に生成することもできます。 いずれのオプションでも、Next.jsはページに関連する<head>要素を自動的に生成します。

ページのタイトルと説明

また、layout.jspage.jsファイルからメタデータ・オブジェクトをインクルードして、タイトルや説明文などのページ情報を追加することもできます。 layout.jsのメタデータは、それを使用するすべてのページに継承されます。

import { Metadata } from 'next';
 
export const metadata: Metadata = {
  title: 'Acme Dashboard',
  description: 'The official Next.js Course Dashboard, built with App Router.',
  metadataBase: new URL('https://next-learn-dashboard.vercel.sh'),
};
 
export default function RootLayout() {
  // ...
}

Next.js はタイトルとメタデータをアプリケーションに自動的に追加します。
しかし、特定のページにカスタム タイトルを追加したい場合はどうすればよいでしょうか。これは、ページ自体にmetadataオブジェクトを追加することで実行できます。ネストされたページのメタデータは、親のメタデータを上書きします。

import { Metadata } from 'next';
 
export const metadata: Metadata = {
  title: 'Invoices | Acme Dashboard',
};

これは機能しますが、アプリケーションのタイトルがすべてのページで繰り返されます。
会社名など何かが変更された場合は、すべてのページで更新する必要があります。
テンプレート内の%sは、特定のページ タイトルに置き換えられます。

import { Metadata } from 'next';
 
export const metadata: Metadata = {
  title: {
    template: '%s | Acme Dashboard',
    default: 'Acme Dashboard',
  },
  description: 'The official Next.js Learn Dashboard built with App Router.',
  metadataBase: new URL('https://next-learn-dashboard.vercel.sh'),
};

最後に

ご覧いただきありがとうございます!
この投稿が誰かの役に立てれば幸いです。

Discussion