🗾

Nuxt3のSSRでmicroCMSのsitemap.xmlを出力する

2024/02/27に公開

Nuxt3のsitemap.xmlを出力する場合は事前生成することもできますが、複雑な場合はSSRで作成したほうが融通が効きます。

You can fetch data from your database or another server, create APIs, or even generate static server-side content like a sitemap or a RSS feed - all from a single codebase.

https://nuxt.com/docs/getting-started/server

serverディレクトリの説明にはsitemapのオンデマンド生成も利用目的としてあげられていますのでserverディレクトリを利用するのがよいでしょう。

microCMSのAPIでsitemap.xmlを出力する

今回はmicroCMSを利用している場合にsitemap.xmlを出力する方法を解説していきます。

microCMSのブログテンプレートからAPIを作成した場合、ブログ(blogs)とカテゴリ(categories)というコンテンツが作成されるのでそのページ一覧が表示されるイメージで作成していきます。

sitemapを分割して生成

microCMSの場合は1度のリクエストで取得できるコンテンツの数は100が上限です(一部例外があり)。
そのため記事数が1500件だと15回のリクエストが必要です。

再帰的に取りに行っても良いのですがリクエストが集中するとレスポンスが遅くなるので、ページを分割することで1ページ1リクエストのシンプルな構成が可能にしたほうが良いと思います。

生成するsitemapは以下のように分割しました。

  • /sitemap.xml
    • /sitemap/blogs.xml
      • /sitemap/blogs-1.xml
      • /sitemap/blogs-2.xml
    • /sitemap/categories.xml
      • /sitemap/categories-1.xml
      • /sitemap/categories-2.xml

/sitemap.xml

/sitemap.xmlは以下のように静的に各サイトマップインデックスのURLが記載されるようにします。

/sitemap.xml
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <sitemap>
        <loc>https://example.com/sitemap/blogs.xml</loc>
    </sitemap>
    <sitemap>
        <loc>https://example.com/sitemap/categories.xml</loc>
    </sitemap>
</sitemapindex>

Nuxt.js側では静的URLなのでpublic/sitemap.xmlに用意しても良いですが、環境変数等が利用したい場合などは server/routes/sitemap.xml.tsに以下のような記述を行います。

server/routes/sitemap.xml.ts
export default defineEventHandler(() => {

    const sitemapXml = `<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <sitemap>
        <loc>${process.env.BASE_URL}/sitemap/blogs.xml</loc>
    </sitemap>
    <sitemap>
        <loc>${process.env.BASE_URL}/sitemap/categories.xml</loc>
    </sitemap>
</sitemapindex>
`

    return new Response(sitemapXml, {
        headers: {
            'Content-Type': 'text/xml'
        },
    });
})

環境変数などは.envで定義しておいてください。

.env
BASE_URL = https://example.com

/sitemap/blogs.xml と /sitemap/categories.xml

/sitemap/blogs.xml/sitemap/categories.xmlは各サイトマップへのサイトマップインデックスが記載されます。

コンテンツ量が100件未満ならサイトマップインデックスではなくて直接サイトマップを記載してもよいですが、今後増加することを考えるとサイトマップインデックスを置くと良いでしょう。

/sitemap/blogs.xml
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <sitemap>
        <loc>https://example.com/sitemap/blogs-1.xml</loc>
    </sitemap>
    <sitemap>
        <loc>https://example.com/sitemap/blogs-2.xml</loc>
    </sitemap>
    <sitemap>
        <loc>https://example.com/sitemap/blogs-3.xml</loc>
    </sitemap>
</sitemapindex>
/sitemap/categories.xml
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <sitemap>
        <loc>https://example.com/sitemap/categories-1.xml</loc>
    </sitemap>
    <sitemap>
        <loc>https://example.com/sitemap/categories-2.xml</loc>
    </sitemap>
    <sitemap>
        <loc>https://example.com/sitemap/categories-3.xml</loc>
    </sitemap>
</sitemapindex>

Nuxt.js側では以下のような動的ルーティングで生成を行います。

URLをもとにblogs.xmlcategories.xmlの場合のみサイトマップインデックスを生成しています。
APIを一度叩けばコンテンツの総数がわかるのでここでは最小数の取得でよいでしょう。

server/routes/sitemap/[file].ts
export default defineEventHandler(async (event) => {
    const file = getRouterParam(event, 'file') || ''

    let sitemapXml: string | undefined
    if (file === 'blogs.xml') {
        sitemapXml = await generateSitemapIndex(file.replace('.xml', ''))
    } else if (file === 'categories.xml') {
        sitemapXml = await generateSitemapIndex(file.replace('.xml', ''))
    }

    if (sitemapXml) {
        return new Response(sitemapXml, {
            headers: {
                'Content-Type': 'text/xml'
            },
        });
    }

})

/**
 * マイクロCMSのデータを取得
 */
export const getMicroCmsData = async ({ target, limit = 100, offset = 0, fields = 'id' }: { target: string; limit?: number, offset?: number, fields?: string }) => {
    const response = await fetch(`${process.env.MICROCMS_API_URL}/${target}?limit=${limit}&offset=${offset}&fields=${fields}`, {
        headers: { 'X-MICROCMS-API-KEY': process.env.MICROCMS_API_KEY || '' },
    })
    return await response.json();
}

/**
 * インデックスサイトマップ作成
 */
const generateSitemapIndex = async (target: string) => {
    // 総数の取得
    const { totalCount } = await getMicroCmsData({ target, limit: 1 })
    const maxPage = Math.ceil(totalCount / 100)
    const siteMapXMLs = []
    for (let page = 1; page <= maxPage; page++) {
        siteMapXMLs.push(`
            <sitemap>
                <loc>${process.env.BASE_URL}/sitemap/${target}-${page}.xml</loc>
            </sitemap>
        `)
    }
    return `<?xml version="1.0" encoding="UTF-8"?>
        <sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
            ${siteMapXMLs.join('')}
        </sitemapindex>
    `
}

/sitemap/blogs-{page}.xml と /sitemap/categories-{page}.xml

詳細のサイトマップを作成していきましょう。詳細のサイトマップでは各ページのURLが最大100件記載されます。

/sitemap/blogs-1.xml
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <url>
        <loc>https://example.com/blogs/s73yzb0-y/</loc>
    </url>
    <url>
        <loc>https://example.com/blogs/asdfae-y/</loc>
    </url>
    <url>
        <loc>https://example.com/blogs/s73yzbadfy/</loc>
    </url>
</urlset>
/sitemap/categories-1.xml
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <url>
        <loc>https://example.com/categories/s73yzb0-y/</loc>
    </url>
    <url>
        <loc>https://example.com/categories/asdfae-y/</loc>
    </url>
    <url>
        <loc>https://example.com/categories/s73yzbadfy/</loc>
    </url>
</urlset>

先ほど作成したserver/routes/sitemap/[file].tsに追記していきます。

defineEventHandlerにはURL判定を追加してgenerateSitemapPageを実行しています。
generateSitemapPageは新たにページサイトマップを作成する関数として作成しています。

server/routes/sitemap/[file].ts
export default defineEventHandler(async (event) => {
    const file = getRouterParam(event, 'file') || ''

    let sitemapXml: string | undefined
    if (file === 'blogs.xml') {
        sitemapXml = await generateSitemapIndex(file.replace('.xml', ''))
    } else if (file === 'categories.xml') {
        sitemapXml = await generateSitemapIndex(file.replace('.xml', ''))
+    } else if (/^(blogs|categories)-([0-9]+).xml$/.test(file)) {
+        const matches = file.match(/^(blogs|categories)-([0-9]+).xml$/);
+        sitemapXml = await generateSitemapPage(matches![1], Number(matches![2]))
    }

    if (sitemapXml) {
        return new Response(sitemapXml, {
            headers: {
                'Content-Type': 'text/xml'
            },
        });
    }

})

(中略、以下は追記)

/**
 * ページサイトマップ作成
 */
const generateSitemapPage = async (target: string, page: number) => {
    const { contents } = await getMicroCmsData({ target, offset: (page - 1) * 100, fields: "id" })
    const siteMapXMLs = contents.map((page: { id: string }) => {
        return `
            <url>
                <loc>${process.env.BASE_URL}/${target}/${page.id}</loc>
            </url>
        `
    })

    return `<?xml version="1.0" encoding="UTF-8"?>
    <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
      ${siteMapXMLs.join('')}
    </urlset>
    `
}

以上で完成です。

これでNuxt3のSSRでmicroCMSのsitemap.xmlを出力することができるようになります。

株式会社トゥーアール

Discussion