LoginSignup
1
0

Next.jsでsitemap.xmlを自動生成する実装を自作する(PageRouterの場合)

Last updated at Posted at 2023-09-02

背景

  • Next.jsでも sitemap.xmlを生成したい(WordPress等ならプラグインでいけそう)
  • Next.jsでもプラグインがあるが、こんなことのために依存関係を増やすことはあまりしたくない(個人プロジェクトだと尚更)

方法

src/pages/sitemap.xmlに以下を記載

import type { GetServerSideProps } from 'next';
import generateSitemap from '@/libs/helper/generateSitemap';

export const getServerSideProps: GetServerSideProps = async ({ res }) => {
  const xml = await generateSitemap();

  // この辺はAPI Routesの書き方とにてます
  res.statusCode = 200;
  // キャッシュは一旦、1日にしておく
  res.setHeader('Cache-Control', 's-maxage=86400, stale-while-revalidate');
  res.setHeader('Content-Type', 'text/xml');
  
    // xmlとしてレスポンス
  res.end(xml);

  return {
    props: {},
  };
};

const SitemapPage = (): void => {};

export default SitemapPage;

URLは以下のように管理されてることとします。

const url = {
    API_ROUTES:{
        API1: '/api/api1'
    },
    ROUTER_URL_ITEMS_ID: '/items',
    USERS: {
        INDEX: '/users',
        IMAGES: {
            THUMBNAIL: '/users/images/thumbnail'
        }
    }
    ...
}

src/libs/helper/generateSitemap/index.tsを作成し以下のように記述


type SitemapXmlField = {
  path: string;
  lastmod: string;
};

// urlの構造を
const flattenRouter: (
  url: {
    // 4階層まであると仮定
    [key: string]:
      | string
      | { [key: string]: string }
      | { [key: string]: { [key: string]: string } }
      | { [key: string]: { [key: string]: { [key: string]: string } } } } 
  },
  prefix?: string,
) => { [key: string]: string } = (url, prefix = ''): { [key: string]: string } => {
  // urlがstringの場合は、そのまま返す
  if (typeof url === 'string') {
    return { [prefix]: uri };
  }
  
  // urlがobjectの場合は、再帰的に処理
  return Object.entries(url).reduce((result, [key, value]) => {
    const newPrefix = prefix + key;
    if (typeof value === 'object') {
      const flattened = flattenRouter(value, newPrefix);
      return { ...result, ...flattened };
    }
    return { ...result, [newPrefix]: value };
  }, {});
};

// 動的Routeのものを削除
const flattenedRouterURI = Object.values(flattenRouterURI(routerURI)).filter(
  (value) => !value.startsWith('/items')
);

/**
 * sitemap.xmlのうち固定値の部分
 */
const xmlGeneralFields: Array<SitemapXmlField> = flattenedRouterURI.map((key, index) => ({
  path: flattenedRouterURI[index],
  lastmod: new Date().toISOString(),
}));

/**
 * TopLevelAwaitを防止するためにmain関数を作成
 */
const main = async () => {

  /**
   * 動的ルーティングのページデータを取得
   */
  const resDynamicRoutes = {
    items: // 全アイテムを取得するようなfetch関数をここに入れる ,
    // ...その他に動的ルーティングのものがあれば追加
  };

  // 動的ルーティングのページ
  const xmlDynamicFields: SitemapXmlField[] = resDynamicRoutes.items
    .map((item) => ({
      path: `/[動的ルーティングのベースURL]/${item.id}`,
      lastmod: new Date().toISOString(),
    })
    .concat(
       ...追加のものについて同様の実装
    )

  const generateSitemapXml = async () => {
    // 取得できない場合undefined
    if (Object.keys(resDynamicRoutes).some((key) => key.length === 0)) {
      return undefined;
    }

    // sitemap.xmlのうち固定値の部分と動的ルーティングのページを結合する
    const fields = xmlGeneralFields.concat(xmlDynamicFields);

    let xml = `<?xml version="1.0" encoding="UTF-8"?>`;
    xml += `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">`;

    // この辺で文字列結合する
    fields.forEach((field) => {
      const url = new URL(field.path, `${config.appBaseUrl}`);

      xml += `
        <url>
          <loc>${url.toString().replace(/\/$/, '')}</loc>
          <lastmod>${field.lastmod}</lastmod>
          <changefreq>weekly</changefreq>
          <priority>0.7</priority>
        </url>
      `;
    });

    xml += `</urlset>`;

    return xml;
  };

  return generateSitemapXml;
};

const generateSitemap = async () => {
  const sitemap = await main()
    .then((generateSitemapXml) => generateSitemapXml())
    .then((xml) => xml)
    .catch((err) => {
      console.error(err);
      return undefined;
    });
  return sitemap;
};

export default generateSitemap;


結果

無駄な依存関係を作らないので嬉しい

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0