📘

App RouterのOGP設定方法まとめ [Next.js]

2023/02/17に公開

Next.js 13.4でStableになったApp Routerにおける、メタタグ・OGP(いわゆるSEO情報)の設定方法をまとめました。

大体のサイトで使える設定内容の例

app/layout.tsxに書くことで、デフォルトのメタデータが設定できます。サイト全体に適用されます。

以下は、日本語のWebサイトを想定した一例となります。

app/layout.tsx
const siteName= 'サイト名';
const description = 'サイトの説明';
const url = 'https://本番のドメイン';

export const metadata = {
  title: {
    default: siteName,
    /** `next-seo`の`titleTemplate`に相当する機能 */
    template: `%s - ${siteName}`,
  },
  description,
  openGraph: {
    title: siteName,
    description,
    url,
    siteName,
    locale: 'ja_JP',
    type: 'website',
  },
  twitter: {
    card: 'summary_large_image',
    title: siteName,
    description,
    site: '@サイト用アカウントのTwitterID',
    creator: '@作者のTwitterID',
  },
  verification: {
    google: 'サーチコンソールのやつ',
  },
  alternates: {
    canonical: url,
  },
};

// 以下略

なんとOGP、Googleのサイト確認などが一気に済みます

faviconとOG画像はファイルを設置するだけでいい

上記の設定には、あえてfaviconやOG画像を含めていません。

faviconの設置方法

https://nextjs.org/docs/app/api-reference/file-conventions/metadata/app-icons

app 直下に、 icon.(ico|jpg|jpeg|png|svg)apple-icon.(jpg|jpeg|png|svg) というファイル名で画像を設置してください。

<link rel="icon" href="/favicon.ico" sizes="any" />
<link rel="icon" href="/icon?<generated>" type="image/<generated>" sizes="<generated>">
<link rel="apple-touch-icon" href="/apple-icon?<generated>" type="image/<generated>" sizes="<generated>">

画像を適切なファイル名で置いただけで、faviconのメタタグが設定されます。

OG画像の設置方法

https://nextjs.org/docs/app/api-reference/file-conventions/metadata/opengraph-image

app 直下に、 opengraph-image.(jpg|jpeg|png|gif) というファイル名で画像を設置してください。

<meta property="og:image" content="<generated>" />
<meta property="og:image:type" content="<generated>" />
<meta property="og:image:width" content="<generated>" />
<meta property="og:image:height" content="<generated>" />

画像を適切なファイル名で置いただけで、OG画像のメタタグが設定されます。 これなら画像の絶対URLを考える必要が一切なくなります。

https://zenn.dev/temasaguru/articles/2968736b5a2f41

なお、opengraph-image.tsx を設置すれば、zennや質問箱のような動的OG画像を生成できます。詳しくは上記の記事をご覧ください。

サイトURLの設定

metadata.metadataBase is not set for resolving social open graph or twitter images, fallbacks to...

上記の設定ではmetadataBaseを書いていないため、ローカル環境でこのような警告が出るはずです。

export const metadata = {
+  // Netlifyなので指定が必要 https://docs.netlify.com/configure-builds/environment-variables/
+  metadataBase: new URL(process.env.URL ?? 'http://localhost:3000'),
  // 後略

もしVercel以外にデプロイする場合は、metadataBaseにURLオブジェクトを設定してください。直書きしてしまうと、プレビューデプロイのOG画像がプロダクションに向いてしまうといった問題が発生するため、ホスティングサービスが用意する環境変数を使うことを推奨します。

https://nextjs.org/docs/app/api-reference/functions/generate-metadata#metadatabase

なぜVercelでは必要ないかというと、https://${process.env.VERCEL_URL}にフォールバックするからです。

文字コードとビューポート

文字コードとビューポートは不要です。以下の内容をNext.jsが自動で設定します。

<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />

robots.txt

https://nextjs.org/docs/app/api-reference/file-conventions/metadata/robots

app 直下に、 robots.ts を設置してください。

テキストファイルを設置しても認識されますが、MetadataRoute.Robots という型まで用意されていますから、どうぞTSで書いてください。

サイトマップ

https://nextjs.org/docs/app/api-reference/file-conventions/metadata/sitemap

app 直下に、 sitemap.ts を設置してください。

XMLを設置しても認識されますが、MetadataRoute.Sitemap という型まで用意されていますから、どうぞTSで書いてください。


https://beta.nextjs.org/docs/api-reference/metadata

これ以外の設定は、上記ページを参照ください。


動的に設定する場合 (MicroCMSの例)

上記の設定は、基本的に全ページで使われますが、タイトルや説明、OG画像などは動的に設定するでしょう。その場合は以下のように書きます。

utils/micro-cms.ts
import { createClient } from 'microcms-js-sdk';
/**
 * みんなも `envsafe` を使ってね
 * @see https://github.com/KATT/envsafe
 */
import { env } from './server-env';
import type { MicroCMSImage, MicroCMSListContent } from 'microcms-js-sdk';


export const client = createClient({
  serviceDomain: env.MICROCMS_SERVICE_DOMAIN,
  apiKey: env.MICROCMS_API_KEY,
});

export interface Article extends MicroCMSListContent {
  title: string;
}

export const getAllArticles = async () => {
  return await client.getList<Article>({
    endpoint: 'article',
    queries: {
      limit: 1000,
      fields: 'id',
      orders: '-createdAt',
    },
  });
};

export const getArticle = async (contentId: string) => {
  return await client.get<Article>({
    endpoint: 'article',
    contentId,
  });
};

例えばMicroCMSから記事を取得するとしましょう。

パスに[articleId]というパスパラメータがあるとします。

app/articles/[articleId]/page.tsx
import { getAllArticles, getArticle } from '@/utils/micro-cms';
import { Metadata } from 'next';

export const revalidate = 600;

type Props = {
  params: { articleId: string };
};

/**
 * パスの事前決定
 */
export async function generateStaticParams() {
  const articles = await getAllArticles();
  return articles.contents.map(({ id: articleId }) => ({
    articleId,
  }));
}

+ /**
+  * メタデータの設定
+  */
+ export async function generateMetadata({ params }: Props): Promise<Metadata> {
+   const { title } = await getArticle(params.articleId);
+   // templateを設定しているので、サイト名は自動で付く
+   return { title };
+ }

export default async function ArticlePage({ params: { articleId } }: Props) {
  const { title, body } = await getArticle(articleId);

  return (
    <div>
      <h1>{title}</h1>
      {/* 以下略 */}
    </div>
  );
}

こういう場合、記事ページでgenerateMetadataを使ってください。 metadataと違って、返り値の型指定が必要です。(next@13.1.7-canary.16現在)

実際にタイトルが変わる様子

templateのおかげで、サイト名は自動で挿入されます。


型について

metadataの補完の様子

型指定は? と思うかもしれませんが、export const metadataを書いた時点で、中身の型チェック・補完がされます。 これはNext.js側がapp directory用の型定義を用意しているからですが、ここらへんを実現するコードを後で読んでみたいですね。Nuxt3の自動インポートとか、TypeScriptのスマートな活用法が日々増えていてワクワクしますね。

Discussion