Next13 app directoryのSEO対策が超楽になったので実例で解説
app directoryを使っている場合限定ですが、Next.jsにおけるmetaタグの設定方法が大きく変わります。バージョン13.2よりhead.js(tsx)
は非推奨になり、将来的に廃止される予定です。
明らかにpages
より楽にSEOの設定ができるため、これだけでも(プロダクションでは使えませんが)app directoryを試す理由になります。
デフォルトのfavicon・OGP等の設定
app/layout.tsx
に書くことで、デフォルトのメタデータが設定できます。サイト全体に適用されます。
以下は、日本語のWebサイトを想定した一例となります。
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のサイト確認などが一気に済みます。
型指定は? と思うかもしれませんが、export const metadata
を書いた時点で、中身の型チェック・補完がされます。 これはNext.js側がapp directory用の型定義を用意しているからですが、ここらへんを実現するコードを後で読んでみたいですね。Nuxt3の自動インポートとか、TypeScriptのスマートな活用法が日々増えていてワクワクしますね。
これ以外の設定は、上記ページを参照ください。
なお、htmlとbodyもapp/layout.tsx
に書く必要があります。 pages
からapp
に切り替える場合、こういった点が抜けると動かないため、ドキュメントを熟読してください。
動的に設定する場合 (MicroCMSの例)
上記の設定は、基本的に全ページで使われますが、タイトルや説明、OG画像などは動的に設定するでしょう。その場合は以下のように書きます。
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]
というパスパラメータがあるとします。
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のCombining Intercepting and Parallel Routes
より画像を転載。かなり複雑な命名に恐れ慄いた人も多いだろう)
Layouts RFCでは、「Web APIに準拠(要審議)したfetch
が使える」[1]と同時に、「Next.js固有のファイル名が増えまくる」 という相反した方向性が示されました。
さらに、RFC時点で登場した layout.js
と page.js
というファイル名に加え head.js
というファイルまで登場してしまったのです。
export default function Head({ params }: { params: { slug: string } }) {
return (
<>
<title>My Page</title>
</>
);
}
metaタグのためだけにファイルを作成するという、大変面倒な仕様になってしまいました。
しかもこれ、配下のルートに継承されません。 もう一度言います。継承されません。 まあimportすればいいんですが、大変でしょう。
ヒーローShu Ding氏により画期的な解決策が示される
(大規模なオープンソースプロジェクトの苦労が伝わるコミットメッセージ。毎日のようにリリースを出すため、時に一旦revertを繰り返すことになる)
そこで、NextraやSatori
で知られる天才Shu Ding氏が、配下のルートに継承されるmetadata
を実装しました。
-
標準という表現には語弊がある。Reactが拡張した
fetch
を、Next.jsはさらに改造してしまっているので、批判もされている。 ↩︎
Discussion