Next.jsでmetaタグをカスタマイズ！OpenGraphのみを出力しtwitter:~を出力しない方法

# やりたいこと・前提条件
* Next.jsのバージョンは15.2.1
* ルーティングシステムはApp Routerを採用
* metaタグのog:~は出力するが、twitter:~は出力しない

# 課題
Next.jsでmetaタグを出力する方法については、以下の公式ドキュメントに記載されている通り、generateMetadata関数を利用します。
https://nextjs.org/docs/app/building-your-application/optimizing/metadata

### サンプルコード
```typescript
import type { Metadata, ResolvingMetadata } from 'next'
 
type Props = {
  params: Promise<{ id: string }>
  searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}
 
export async function generateMetadata(
  { params, searchParams }: Props,
  parent: ResolvingMetadata
): Promise<Metadata> {
  // read route params
  const { id } = await params
 
  // fetch data
  const product = await fetch(`https://.../${id}`).then((res) => res.json())
 
  // optionally access and extend (rather than replace) parent metadata
  const previousImages = (await parent).openGraph?.images || []
 
  return {
    title: product.title,
    openGraph: {
      images: ['/some-specific-page-image.jpg', ...previousImages],
    },
  }
}
 
export default function Page({ params, searchParams }: Props) {}
```

MetadataにはopenGraphというプロパティがあり、そこに必要な情報を設定します。
しかし、このままだとtwitter:~タグもog:~と同じ設定値で出力されてしまう仕様になっています。
SEO的にはtwitter:~のmetaタグも出力した方が良いとされていることから、このような配慮がされているのかもしれません。

# 解決策
Metadataにはtwitterというプロパティも存在しますが、nullやundefined、空のオブジェクトを設定しても、どれもうまくいかず困っていました。
その後、下記のGitHubのページでtwitter:~を出力させない方法が紹介されていたので、試してみました。
https://github.com/vercel/next.js/discussions/75319

解決方法としては、MetadataのopenGraphには何も設定せず、代わりにotherプロパティを使用してOpenGraph情報を設定する方法です。
具体的には以下のように設定します。

```typescript
other: {
    'og:title': meta.title,
    'og:type': meta.type,
    'og:image': `${APP_BASE_URL}/og-image?fbgBCzjj`,
    'og:url': APP_BASE_URL,
},
// openGraphには何も設定しない
// openGraph: {
//   title: meta.title,
//   type: meta.type,
//   image: `${APP_BASE_URL}/og-image?fbgBCzjj`,
//   url: APP_BASE_URL,
// },
```

この方法であれば、twitter:~のmetaタグは出力せず、og:~のmetaタグのみを出力できます。

# 実際に試してみる
まずは適当なNext.jsプロジェクトをApp Routerで作成しておきます。
※この記事作成時点におけるNext.jsの最新バージョンは15.2.1です。
```
npx create-next-app@latest
```

次にsrc > appの配下に[(pages)](https://nextjs.org/docs/app/building-your-application/routing/route-groups) > metaディレクトリを作成し、ogとotherページを作成します。
```
src
└── app
    ├── (pages)
    │   └── meta
    │       ├── og
    │       │   └── page.tsx
    │       └── other
    │           └── page.tsx
    ├── favicon.ico
    ├── globals.css
    ├── layout.tsx
    └── page.tsx
```

### src/app/(pages)/meta/og/page.tsx
```typescript
import { Metadata } from "next";

export const generateMetadata = (): Metadata => {
  return {
    title: "Meta Tags Test Page OpenGraph",
    description: "Meta Tags Test Page OpenGraph Description",
    keywords: ["Meta Tags", "Test", "Page", "OpenGraph"],
    openGraph: {
      "siteName": "Meta Tags Test Page OpenGraph",
      "type": "article",
      "title": "Meta Tags Test Page OpenGraph",
      "description": "Meta Tags Test Page OpenGraph Description",
      "locale": "ja_JP",
    },
  };
};

export default function Page() {
  return (
    <div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
      <main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
        Meta Tags Test Page OpenGraph
      </main>
    </div>
  );
}
```
### src/app/(pages)/meta/other/page.tsx
```typescript
import { Metadata } from "next";

export const generateMetadata = (): Metadata => {
  return {
    title: "Meta Tags Test Page Other",
    description: "Meta Tags Test Page Other Description",
    keywords: ["Meta Tags", "Test", "Page"],
    other: {
      "og:siteName": "Meta Tags Test Page Other",
      "og:type": "article",
      "og:title": "Meta Tags Test Page Other",
      "og:description": "Meta Tags Test Page Other Description",
      "og:locale": "ja_JP",
    },

  };
};

export default function Page() {
  return (
    <div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
      <main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
        Meta Tags Test Page Other
      </main>
    </div>
  );
}
```

## 画面表示
### OpenGraph
twitter:~のmetaタグも出力されていますね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2503161/7a90f450-bd09-467e-afd9-0a10f439b9a1.png)

### Other
twitter:~のmetaタグは出力されなくなりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2503161/734ece6a-78fa-4121-b7f7-c73cfab170df.png)


# おわりに
今回の課題について調査を進める中で、あまり参考になる技術記事がすぐには見つけられなかったため、Qiitaに投稿することを決めました。
私たちのサービスではtwitter:~タグも出力した方が良いという結論に至ったため、今回紹介した方法は最終的に採用しませんでしたが、同じ課題で困っている方には参考になるかもしれませんので、少しでもお役に立てれば幸いです。



## KIYOラーニング株式会社について
当社のビジョンは『世界一「学びやすく、分かりやすく、続けやすい」学習手段を提供する』ことです。革新的な教育サービスを作り成長させていく事で、オンライン教育分野でナンバーワンの存在となり、世界に展開していくことを目指しています。

https://kiyo-learning.com/

### プロダクト

* [スタディング](https://studying.jp/)：「学びやすく・わかりやすく・続けやすい」オンライン資格対策講座
* [スタディングキャリア](https://career.studying.jp/)：資格取得者の仕事探しやキャリア形成を支援する転職サービス
* [AirCourse](https://aircourse.com/)：受け放題の動画研修がついたeラーニングシステム（LMS）

### KIYOラーニング株式会社では一緒に働く仲間を募集しています

https://www.wantedly.com/companies/kiyo-learning/projects

https://herp.careers/v1/kiyolearning