📘

Next13 app directoryのSEO対策が超楽になったので実例で解説

2023/02/16に公開約6,000字

app directoryを使っている場合限定ですが、Next.jsにおけるmetaタグの設定方法が大きく変わります。バージョン13.2よりhead.js(tsx)は非推奨になり、将来的に廃止される予定です。

明らかにpagesより楽にSEOの設定ができるため、これだけでも(プロダクションでは使えませんが)app directoryを試す理由になります。

デフォルトのfavicon・OGP等の設定

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

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

app/layout.tsx
const siteName= 'サイト名';
const description = 'サイトの説明';
const url = 'https://ドメイン';
const ogImageUrl = 'https://OG画像のURL';

export const metadata = {
  title: {
    default: siteName,
    /** `next-seo`の`titleTemplate`に相当する機能 */
    template: `%s - ${siteName}`,
  },
  description,
  icons: {
    icon: '/icon.png',
    shortcut: '/shortcut-icon.png',
    apple: '/apple-icon.png',
    // ドキュメントを参照して適宜増やすこと
  },
  openGraph: {
    title: siteName,
    description,
    url,
    siteName,
    images: [
      {
        url: ogImageUrl,
        width: 1800,
        height: 1600,
        alt: '画像の説明文',
      },
    ],
    locale: 'ja_JP',
    type: 'website',
  },
  twitter: {
    card: 'summary_large_image',
    title: siteName,
    description,
    site: '@サイト用アカウントのTwitterID',
    creator: '@作者のTwitterID',
    images: [ogImageUrl],
  },
  verification: {
    google: 'サーチコンソールのやつ',
  },
  alternates: {
    canonical: url,
  },
};

// 以下略

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

metadataの補完の様子

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

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

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


https://zenn.dev/temasaguru/articles/546f0fcdd9d131

なお、htmlとbodyもapp/layout.tsxに書く必要があります。 pagesからappに切り替える場合、こういった点が抜けると動かないため、ドキュメントを熟読してください。


動的に設定する場合 (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 {
  MicroCMSContentId,
  MicroCMSImage,
  MicroCMSDate,
} from 'microcms-js-sdk';

type MicroCMSBase = MicroCMSContentId & MicroCMSDate;

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

export type Article = {
  title: string;
} & MicroCMSBase;

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 Params = {
  params: { articleId: string };
};

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

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

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

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

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

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

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


head.js(tsx)を使っていると警告される

`\`head.js\` is being used in route /(パス). Please migrate to the Metadata API for an improved experience: https://beta.nextjs.org/docs/api-reference/metadata`

head.js(tsx)はバージョン13.2で非推奨になり、将来的に廃止される予定です。 もし使っていると上記のような警告が出ます。

従来のSEO情報実装との違いと、metadata登場の経緯

Next.jsにおいて、metaタグの設定は非常に面倒でした。faviconは_document.tsxに、ビューポートは_app.tsxに...

設定を簡単にするnext-seoといったパッケージが登場し、Next13現在のpagesではデファクトスタンダードになりつつあります。

しかし、app directoryはページ描画の仕組みが根本的に違いますから、既に難しかったSEOが余計ややこしくなってしまいました。

継承できなくて不便なhead.js

Layouts RFCより引用

(※Layouts RFCのCombining Intercepting and Parallel Routesより画像を転載。かなり複雑な命名に恐れ慄いた人も多いだろう)

Layouts RFCでは、「Web APIに準拠(要審議)したfetchが使える」[1]と同時に、「Next.js固有のファイル名が増えまくる」 という相反した方向性が示されました。

https://beta.nextjs.org/docs/api-reference/file-conventions/head

さらに、RFC時点で登場した layout.jspage.js というファイル名に加え head.jsというファイルまで登場してしまったのです。

app/head.tsx
export default function Head({ params }: { params: { slug: string } }) {
  return (
    <>
      <title>My Page</title>
    </>
  );
}

metaタグのためだけにファイルを作成するという、大変面倒な仕様になってしまいました。

しかもこれ、配下のルートに継承されません。 もう一度言います。継承されません。 まあimportすればいいんですが、大変でしょう。

ヒーローShu Ding氏により画期的な解決策が示される

https://github.com/vercel/next.js/pull/45196

(大規模なオープンソースプロジェクトの苦労が伝わるコミットメッセージ。毎日のようにリリースを出すため、時に一旦revertを繰り返すことになる)

そこで、NextraSatoriで知られる天才Shu Ding氏が、配下のルートに継承されるmetadataを実装しました。


脚注
  1. 標準という表現には語弊がある。Reactが拡張したfetchを、Next.jsはさらに改造してしまっているので、批判もされている。 ↩︎

Discussion

ログインするとコメントできます