コンテンツにスキップ

Workersを使用してSanity CMSからサイトマップを作成する

Last reviewed: 5 months ago

Developer Spotlight community contribution

Written by: John Siciliano

Profile: LinkedIn

このチュートリアルでは、ヘッドレスCMSであるSanity.ioからデータを使用してサイトマップを作成し、提供するCloudflare Workerを組み立てます。

このチュートリアルで構築するソリューションの高レベルのワークフローは次のとおりです。

  1. あなたのドメイン上のURL(例: cms.example.com/sitemap.xml)がCloudflare Workerにルーティングされます。
  2. Workerは、スラッグや最終更新日などのCMSデータを取得します。
  3. Workerはそのデータを使用してサイトマップを組み立てます。
  4. 最後に、Workerは検索エンジン用に準備されたXMLサイトマップを返します。

始める前に

始める前に、次のものを用意してください。

  • Cloudflareアカウント。まだ持っていない場合は、サインアップしてください。
  • フルセットアップを使用してCloudflareアカウントに追加されたドメイン(つまり、Cloudflareを権威DNSネームサーバーとして使用)。
  • マシンにNode.jsnpmがインストールされていること。

新しいWorkerを作成する

Cloudflare Workersは、インフラストラクチャを構成または維持することなく、新しいアプリケーションを作成したり、既存のアプリケーションを拡張したりできるサーバーレス実行環境を提供します。

CloudflareダッシュボードでWorkerを作成できますが、ローカルで作成することがベストプラクティスです。これにより、バージョン管理やWrangler(Workersのコマンドラインインターフェース)を使用してデプロイできます。

C3create-cloudflare CLI)を使用して新しいWorkerプロジェクトを作成します。

Terminal window
pnpm create cloudflare@latest

このチュートリアルでは、Workerの名前はcms-sitemapになります。

コマンドラインインターフェース(CLI)で、JavaScriptまたはTypeScriptを使用するなど、最適なオプションを選択してください。選択するスターターテンプレートは、このチュートリアルがプロジェクトに貼り付けるためのすべての必要なコードを提供するため、重要ではありません。

次に、@sanity/clientパッケージを要求します。

Terminal window
pnpm install @sanity/client

wrangler.tomlを設定する

前のステップでデフォルトのwrangler.tomlが生成されました。

wrangler.tomlファイルは、プロジェクト設定とデプロイ構成を構造化された形式で指定するために使用される設定ファイルです。

このチュートリアルのwrangler.tomlは次のようになります。

name = "cms-sitemap"
main = "src/index.ts"
compatibility_date = "2024-04-19"
minify = true
[vars]
# CMSは相対URLを返すため、サイトのベースURLを知る必要があります。
SITEMAP_BASE = "https://example.com"
# プロジェクトIDに合わせて修正してください。
SANITY_PROJECT_ID = "5z5j5z5j"
SANITY_DATASET = "production"

[vars]セクションをニーズに合わせて更新する必要があります。各エントリの目的を理解するためにインラインコメントを参照してください。

コードを追加する

このステップでは、完全なソリューションに近づけるためのボイラープレートコードを追加します。

このチュートリアルの目的のために、コードは2つのファイルに凝縮されています。

  • index.ts|js: Workerへのリクエストのエントリポイントとして機能し、適切な場所にルーティングします。
  • Sitemap.ts|js: サイトマップに変換されるCMSデータを取得します。関心の分離と整理のために、CMSロジックは別のファイルにするべきです。

次のコードを既存のindex.ts|jsファイルに貼り付けます。

/**
* Cloudflare Workersへようこそ!
*
* - ターミナルで`npm run dev`を実行して開発サーバーを起動します
* - http://localhost:8787/でブラウザタブを開いてWorkerの動作を確認します
* - `npm run deploy`を実行してWorkerを公開します
*
* `wrangler.toml`でWorkerにリソースをバインドします。バインディングを追加した後、`npm run cf-typegen`で
* `Env`オブジェクトの型定義を再生成できます。
*
* 詳細はhttps://developers.cloudflare.com/workers/で確認できます。
*/
import { Sitemap } from "./Sitemap";
// イベントハンドラーを含むデフォルトオブジェクトをエクスポートします。
export default {
// fetchハンドラーは、このWorkerがHTTPSリクエストを受信したときに呼び出され、
// Responseを返す必要があります(オプションでPromiseでラップできます)。
async fetch(request, env, ctx): Promise<Response> {
const url = new URL(request.url);
// if/switch文のようなシンプルなロジックでかなりのことができます。
// より複雑なルーティングが必要な場合は、Hono https://hono.dev/を検討してください。
if (url.pathname === "/sitemap.xml") {
const handleSitemap = new Sitemap(request, env, ctx);
return handleSitemap.fetch();
}
return new Response(`/sitemap.xmlをリクエストしてみてください`, {
headers: { "Content-Type": "text/html" },
});
},
} satisfies ExportedHandler<Env>;

上記のコードを貼り付けた後、このファイルを変更する必要はありません。

次に、Sitemap.ts|jsという新しいファイルを作成し、次のコードを貼り付けます。

import { createClient, SanityClient } from "@sanity/client";
export class Sitemap {
private env: Env;
private ctx: ExecutionContext;
constructor(request: Request, env: Env, ctx: ExecutionContext) {
this.env = env;
this.ctx = ctx;
}
async fetch(): Promise<Response> {
// クエリをCMSのスキーマに合わせて修正してください。
//
// これらをリクエストします:
// - "slug": 投稿のスラッグ。
// - "lastmod": 投稿が更新された日時。
//
// 注意:
// - スラッグは、サイトマップ内の完全な相対URLを形成するのに役立つようにプレフィックスが付けられています。
// - スラッグを順序付けて、サイトマップが一貫した順序になるようにします。
const query = `*[defined(postFields.slug.current)] {
_type == 'articlePost' => {
'slug': '/posts/' + postFields.slug.current,
'lastmod': _updatedAt,
},
_type == 'examplesPost' => {
'slug': '/examples/' + postFields.slug.current,
'lastmod': _updatedAt,
},
_type == 'templatesPost' => {
'slug': '/templates/' + postFields.slug.current,
'lastmod': _updatedAt,
}
} | order(slug asc)`;
const dataForSitemap = await this.fetchCmsData(query);
if (!dataForSitemap) {
console.error(
"サイトマップのデータ取得中にエラーが発生しました",
JSON.stringify(dataForSitemap),
);
return new Response("サイトマップのデータ取得中にエラーが発生しました", { status: 500 });
}
const sitemapXml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${dataForSitemap
.filter(Boolean)
.map(
(item: any) => `
<url>
<loc>${this.env.SITEMAP_BASE}${item.slug}</loc>
<lastmod>${item.lastmod}</lastmod>
</url>
`,
)
.join("")}
</urlset>`;
return new Response(sitemapXml, {
headers: {
"content-type": "application/xml",
},
});
}
private async fetchCmsData(query: string) {
const client: SanityClient = createClient({
projectId: this.env.SANITY_PROJECT_ID,
dataset: this.env.SANITY_DATASET,
useCdn: true,
apiVersion: "2024-01-01",
});
try {
const data = await client.fetch(query);
return data;
} catch (error) {
console.error(error);
}
}
}

ステップ4と5では、src/Sitemap.tsに貼り付けたコードをニーズに合わせて修正します。

CMSデータをクエリする

src/Sitemap.ts内の次のクエリは、CMSから取得するデータを定義します。正確なクエリはスキーマによって異なります。

const query = `*[defined(postFields.slug.current)] {
_type == 'articlePost' => {
'slug': '/posts/' + postFields.slug.current,
'lastmod': _updatedAt,
},
_type == 'examplesPost' => {
'slug': '/examples/' + postFields.slug.current,
'lastmod': _updatedAt,
},
_type == 'templatesPost' => {
'slug': '/templates/' + postFields.slug.current,
'lastmod': _updatedAt,
}
} | order(slug asc)`;

必要に応じて、提供されたクエリを特定のスキーマに合わせて調整してください。以下の点に留意してください。

  • クエリは、サイトマップを作成する際に参照されるsluglastmodの2つのプロパティを返す必要があります。GROQ(Graph-Relational Object Queries)およびGraphQLは、プロパティに名前を付けることを可能にします。たとえば、"lastmod": _updatedAtのように、カスタムフィールド名を必要なプロパティにマッピングできます。
  • 各スラッグにはベースパスをプレフィックスする必要があります。www.example.com/posts/my-postの場合、返されるスラッグはmy-postですが、プレフィックスが必要なのはベースパス(/posts/)です(ドメインは自動的に追加されます)。
  • 一貫した順序を提供するために、クエリにソートを追加します(提供されたチュートリアルコードのorder(slug asc))。

クエリによって返されるデータは、XMLサイトマップを生成するために使用されます。

CMSデータからサイトマップを作成する

src/Sitemap.ts内でサイトマップを生成し、正しいコンテンツタイプで返す関連コードは次のとおりです。

const sitemapXml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${dataForSitemap
.filter(Boolean)
.map(
(item: any) => `
<url>
<loc>${this.env.SITEMAP_BASE}${item.slug}</loc>
<lastmod>${item.lastmod}</lastmod>
</url>
`,
)
.join("")}
</urlset>`;
return new Response(sitemapXml, {
headers: {
"content-type": "application/xml",
},
});

サイトマップに追加されるのは、URL(loc)と最終更新日(lastmod)の2つのプロパティのみです。これは、Googleによると、prioritychangefreqなどの他のプロパティは無視されるためです。

最後に、サイトマップはapplication/xmlのコンテンツタイプで返されます。

この時点で、次のコマンドを実行してローカルでWorkerをテストできます。

Terminal window
wrangler dev

このコマンドは、ターミナルにlocalhostのURLを出力します。このURLに/sitemap.xmlを追加してブラウザでサイトマップを表示します。エラーがある場合は、ターミナルの出力に表示されます。

サイトマップが正常に動作していることを確認したら、次のステップに進みます。

Workerをデプロイする

プロジェクトがローカルで正常に動作しているので、残りのステップは2つです。

  1. Workerをデプロイします。
  2. ドメインにバインドします。

Workerをデプロイするには、ターミナルで次のコマンドを実行します。

Terminal window
wrangler deploy

ターミナルには、デプロイに関する情報がログに記録され、新しいカスタムURLが{worker-name}.{account-subdomain}.workers.devの形式で表示されます。このホスト名を使用してサイトマップを取得できますが、コンテンツがあるのと同じドメインでサイトマップをホストすることがベストプラクティスです。

URLをWorkerにルーティングする

このステップでは、Cloudflareの組み込み機能を使用して新しいサブドメインでWorkerを利用可能にします。

サブドメインを使用する利点の1つは、このサイトマップがルートドメインのサイトマップと競合する心配がないことです。両方ともおそらく/sitemap.xmlパスを使用しています。

  1. Cloudflareダッシュボードにログインし、アカウントを選択します。

  2. アカウントホームで、Workers & Pagesを選択し、次にWorkerを選択します。

  3. 設定 > トリガー > カスタムドメイン > カスタムドメインを追加に移動します。

  4. Workerに設定したいドメインまたはサブドメインを入力します。

    このチュートリアルでは、サイトマップにあるドメインのサブドメインを使用します。たとえば、サイトマップがwww.example.comのようなURLを出力する場合、適切なサブドメインはcms.example.comです。

  5. カスタムドメインを追加を選択します。

    サブドメインを追加すると、Cloudflareは自動的にWorkerをサブドメインにバインドするための適切なDNSレコードを追加します。

  6. 設定を確認するには、新しいサブドメインに移動し、/sitemap.xmlを追加します。たとえば:

    cms.example.com/sitemap.xml

ブラウザには、ローカルでテストしたときと同様にサイトマップが表示されるはずです。

これで、ヘッドレスCMS用のサイトマップが、高度にメンテナンス可能でサーバーレスなセットアップを使用して作成されました。