🌱

Next.jsでお手軽CGMサイトマップ

2022/05/03に公開約3,500字

下の記事に型をつけて扱いやすくしました。詳しい部分や動機は下の記事を参考にしてください。

https://zenn.dev/catnose99/articles/c441954a987c24

1. サイトマップとは

サイトマップには、

  • サイト内のコンテンツを検索エンジンに知らせる。
  • 検索エンジンがサイト内をクロール・インデックスするのを助ける。

役割があります。

タグ Head
<loc> 必須 ページのURLを示す。
<lastmod> オプション ページの最終更新日を示す。
<changefreq> オプション always, hourly, daily, weekly, monthly, yearly, never でページの更新頻度を示す。
<priority> オプション 0.0-1.0でサイト内の他のURLとの相対的な優先度を示す。デフォルトの優先度は0.5。

基本的な形式は下のような感じです。

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://google.com/</loc>
    <lastmod>2022-05-02T22:30:57.000Z</lastmod>
    <changefreq>hourly</changefreq>
    <priority>0.5</priority>
  </url>
</urlset>

2. サイトマップで使うタグを拡張する

JSXにサイトマップで使うXMLタグを追加して、JSX.Elementとして扱えるようにします。

src/@types/xml.d.ts
declare namespace JSX {
  interface IntrinsicElements {
    urlset: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> & {
      xmlns: string;
    };
    url: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
    loc: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
    lastmod: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
    changefreq: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
    priority: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
  }
}

3. サイトマップを生成する関数を作る

URLのリストから<urlset>を作成して、JSX.Elementを返す関数を作成します。

その後、renderToStringで文字列に変換し、頭に<?xml version="1.0" encoding="UTF-8"?>を追加してサイトマップが完成します。

src/sitemap/generator.tsx
import { renderToString } from 'react-dom/server';

export type URL = {
  loc: string;
  lastMod?: Date;
  freq?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
  priority?: 0.0 | 0.1 | 0.2 | 0.3 | 0.4 | 0.5 | 0.6 | 0.7 | 0.8 | 0.9 | 1.0;
};

function generateURLSet(list: URL[]): JSX.Element {
  return (
    <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
      {list.map(({ loc, lastMod, freq, priority }: URL, i) => (
        <url key={i}>
          <loc>{loc}</loc>
          {lastMod && <lastmod>{lastMod.toISOString()}</lastmod>}
          {freq && <changefreq>{freq}</changefreq>}
          {priority && <priority>{priority}</priority>}
        </url>
      ))}
    </urlset>
  );
}

export function generateSiteMap(list: URL[]): string {
  return '<?xml version="1.0" encoding="UTF-8"?>' + renderToString(generateURLSet(list));
}

4. サイトマップページを作る

サイトマップページを作成します。getServerSidePropsの中でバックエンドから記事一覧、ユーザーリスト等を取得して、listへ追加していきます。

XMLの文字列へ変換して、レスポンスへ返します。

src/pages/sitemap.xml.tsx
import { GetServerSidePropsContext } from 'next';
import { generateSiteMap, URL } from 'frameworks/sitemap/generator';

export default function Page(): null {
  return null;
}

export async function getServerSideProps({ res }: GetServerSidePropsContext): Promise<{
  props?: Record<string, unknown>;
  notFound?: boolean;
}> {
  try {
    const list: URL[] = [
      {
        loc: 'https://google.com/',
        lastMod: new Date(1651530657000),
        freq: 'hourly',
        priority: 0.5,
      },
    ];

    const xml = generateSiteMap(list);

    res.setHeader('Cache-Control', 'public, max-age=3600, s-maxage=86400, stale-while-revalidate');
    res.setHeader('Content-Type', 'text/xml');
    res.end(xml);

    return {
      props: {},
    };
  } catch (error) {
    return {
      notFound: true,
    };
  }
}

Discussion

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