Cloudflareの無料プランでキャッシュキーをカスタマイズする方法

![cloudflare-plan.png](https://img.searchemoji.app/acticle-image/cloudflare-plan.png)

概要:私はユーザーの言語を検出して対応する言語のページに自動的にリダイレクトする多言語の絵文字検索エンジンを作成しました。 そのため、キャッシュキーをカスタマイズする必要がありましたが、Cloudflareではキャッシュキーをカスタマイズするにはエンタープライズプランにアップグレードする必要があり、それは私の予算を超えていました。 しかし、ドキュメントを徹底的に研究した結果、変換ルールの設定を使用して、カスタマイズされたキャッシュキーと同等の効果を実現することに成功しました。 この記事では、設定方法を共有します。キャッシュキーをカスタマイズする必要がない場合でも、この記事の前半からCDNとキャッシュに関する知識を学ぶことができます。

始める前に、コンテキストをよりよく理解していただけるよう、私の Web サイトをご紹介します。

- 🧐 **SearchEmoji** ([https://searchemoji.app](https://searchemoji.app/)): 30の言語をサポートする絵文字検索エンジンです。 豊かな絵文字が記事やSNSの投稿をより生き生きとさせてくれます。

- ✊ **Yesicon** ([https://yesicon.app](https://yesicon.app/)): 8つの言語をサポートするベクターアイコン検索エンジンです。 20万以上の高品質なアイコンが登録されており、開発者とデザイナーのための⌘CVヘルパーです。

では、始めましょう!まず、なぜキャッシュキーをカスタマイズする必要があるのかを説明します。

多言語のWebサイトは通常、ユーザーの言語を検出し、対応する言語のページを返す機能を備えています。 検出ロジックは、ユーザーが言語を選択した場合、選択した言語をエンコードして `lang` クッキーに保存します。 サーバー側では、まず `lang` クッキーをチェックし、空の場合は `Accept-Language` ヘッダーにフォールバックしてユーザーの言語を判断します。

ユーザーがオリジンサーバーに直接接続する場合は問題ありませんが、世界中のアクセス速度を改善するため、通常 CDN を使用してコンテンツをキャッシュおよび配信します。 次のような状況を想像してみてください: 中国語のユーザーがホームページを訪問したため、中国語のページがエッジサーバーに "/", キーワードでキャッシュされ、後で英語のユーザーがホームページを訪問します。 "/" のキャッシュされたページがあるため、エッジサーバーは英語バージョンをオリジンから取得する代わりに、直接中国語のページを返します。これは望ましくないユーザーエクスペリエンスです。 

解決策は簡単です。すべてのCDNプロバイダーがキャッシュキーをカスタマイズする機能を提供しているため、`lang` クッキーと `Accept-Language` ヘッダーを追加するだけです。 例えば、キー "/" の代わりに、"/?lang=en&acceptLanguage=en-US,en" を使用します。 これにより、言語ごとに個別のキャッシュエントリが作成され、言語の混在を避けることができます。

問題は、Cloudflare ではカスタマイズキャッシュキーにはエンタープライズプランへのアップグレードが必要だということです。 これが Cloudflare の初めての利用で、別のサイト Yesicon は AWS Cloudfront CDN を使用しています。Cloudfront にはキャッシュキーの制限がなく、簡単にカスタムキーを設定できました。 一時は SearchEmoji も Cloudfront に移行することも考えましたが、Yesicon の人気もあり、Cloudfront の無料プランでも十分ではないことに気づきました。 なので、Cloudflareでアップグレードせずにカスタマイズキャッシュキーを実現する方法を徹底的に研究することにしました!

Cloudflareのドキュメントを3晩遅くまで読み漁り、実験し続けた結果、ついに方法を見つけ出しました! ポイントは、このフローを理解することです:

![Untitled](https://img.searchemoji.app/acticle-image/Untitled.png)

リクエストがエッジサーバーに入ると、最初にURLリライトルールに従ってURLが書き換えられ、その後でキャッシュの検索が行われます。 つまり、URLを`lang` クッキーと `Accept-Language` ヘッダーの値を含むように書き換えるだけで済みます。 カスタムキャッシュキーをシミュレートする方法は次のとおりです。

[https://searchemoji.app/](https://searchemoji.app/) にアクセスするユーザーの場合を追ってみましょう。 エッジサーバーに到達すると、URLは `https://searchemoji.app/?acceptLanguage=en-US,en` に書き換えられます。 最初の英語ユーザーがこの URL をキーとして英語ページをキャッシュすると、他の英語ユーザーもこのキャッシュされたページを取得できるようになります。一方、中国語のユーザーの場合は `https://searchemoji.app/?acceptLanguage=zh-CN,zh,en` に書き換えられるため、英語ページが表示されることはありません。 また、ユーザーが言語を明示的に変更した場合、クエリ文字列に `lang` クッキーを追加するだけです。

しかし、URL を動的に書き換えるのは簡単ではありません。 [Cloudflareのルール言語](https://developers.cloudflare.com/ruleset-engine/rules-language/)を理解する必要があります。 ルール追加ページで、上部がURL書き換え条件ですが、複雑なので `Edit expression` を選択します。

![Untitled](https://img.searchemoji.app/acticle-image/Untitled%201.png)

このロジックはホームページだけに適用したいので、まずホームページであることを確認します。 次に、ユーザーの言語がサポート言語リストにある場合にのみURLを書き換えるようにします。 サポート言語の場合、適切な言語のページと一致します。 英語やその他のサポート外の言語の場合は書き換えません。英語はデフォルトで、サポート外の言語も英語にフォールバックするためです。 完全な条件チェックのロジックは次のとおりです。

```bash
# ホームページである  
http.request.uri.path eq "/" and
# ユーザーが英語を選択していない  
not (http.cookie contains "lang=en") and
(
	# ユーザーがサポート言語を選択 
	http.cookie contains "lang=" or
	# ブラウザの言語がサポート言語
	substring(http.request.accepted_languages[0], 0, 2) in  
		{ "zh" "es" "de" "ja" "fr" "ko" "pt" "ru" "tr" "ar" "it" "hi" "pl" "bn" "nl" "uk" "id" "ms" "vi" "th" "sv" "el" "he" "fi" "no" "da" "ro" "hu" }
) 
```

読みやすくするために改行とコメントを入れていますが、コードをコピーする場合は削除してください。

次に、実際に動的変数を使用してクエリ文字列を書き換える必要があります。

![Untitled](https://img.searchemoji.app/acticle-image/Untitled%202.png)

`lang` 値をクッキーから抽出するには、正規表現を使用できます。

```bash
regex_replace(http.cookie, "^.*lang=([^;]+).*$|^.*$", "${1}")
```

後の処理を簡単にするために、`lang` と `Accept-Language` を1つのパラメータに連結します。 こちらが完全な書き換えルールです。

```bash
concat(  
	# クエリキー	
	"cfcache=",
	# lang
	regex_replace(http.cookie, "^.*lang=([^;]+).*$|^.*$", "${1}"), 
	# Accept-Language
	http.request.accepted_languages[0]
)
```

これにより、スペイン語のユーザーは `https://searchemoji.app/?cfcache=es-ES` に書き換えられ、言語を明示的に英語に切り替えた場合は `https://searchemoji.app/?cfcache=enes-ES` になります。言語間に区切り文字を入れることで可読性が向上します。

これでパラメータを追加できますが、2つの問題があります。

- 既存のパラメータが上書きされ、パラメータが失われる。 例えば、`https://searchemoji.app/?v=1` にアクセスすると、v=1パラメータが失われて `https://searchemoji.app/?cfcache=es-ES` に書き換えられる。

- 追加されたクエリパラメータがリダイレクト後も残る。 例えば、`https://searchemoji.app/` にアクセスすると `https://searchemoji.app/?cfcache=es-ES` にリダイレクトされ、ユーザーに醜いパラメータが表示される。

最初の問題の解決策はすぐに思いつくと思います。既存のパラメータも保持する必要があります。

```bash  
concat(
	# クエリキー
	"cfcache=",
	# lang
	regex_replace(http.cookie, "^.*lang=([^;]+).*$|^.*$", "${1}"),
	# Accept-Language
	http.request.accepted_languages[0], 
	# オリジナルクエリ
	"&",
	http.request.uri.query
)
```

しかし、これでも完璧とは言えません。オリジナルのURLにクエリパラメータがない場合、末尾に & 記号が付加されます(`https://searchemoji.app/?cfcache=es-ES&`)。これと2つ目の問題を一緒に解決しましょう。

エッジサーバーが追加する `?cfcache=xxx` クエリパラメータはキャッシュのためだけのものですが、エッジサーバーはこのパラメータを付けたままオリジンサーバーにリクエストを転送します。 オリジンサーバーは多言語リダイレクトロジックの実行時にもこのパラメータを付加します。 オリジンサーバーは302ステータスコードを返してリダイレクトを実装しているため、レスポンスヘッダーの `Location` でこのパラメータを削除する必要があります。 私は `Nuxt 3` を使用しています。処理ロジックは次のとおりです。

```js
nitroApp.hooks.hook('render:response', (response, { event }) => {
  if (response.headers?.location?.includes('cfcache=')) {
    const host = 'https://searchemoji.app'
    const url = new URL(response.headers.location, host)
	// パラメータの末尾に & があると、 { cfcache: 'es-ES&' } のように処理される 
    const params = new URLSearchParams(url.search)
	// そのため、このパラメータを削除し、&も削除される
    params.delete('cfcache') 
    url.search = params.toString()
    response.headers.location = url.toString().replace(host, '')
	// 302ステータスをエッジサーバーにキャッシュさせる
    response.headers['Cache-Control'] = 'max-age=86400, must-revalidate'
  }
})
```

これで、余分な手間はかかりますが、カスタマイズキャッシュキーを完璧にシミュレートすることができます。 設定は次のとおりです。

![Untitled](https://img.searchemoji.app/acticle-image/Untitled%203.png)

Cloudflare は追加料金があっても、すべてのプランでカスタムキャッシュキーを提供するべきだと思います。

最後に、私の2つのサイト(記事の先頭にリンクがあります)をぜひお試しください。デザイナー、開発者、クリエイターを問わず、アイコンと絵文字は皆さんのお仕事に役立つはずです。年の瀬ですので、年末レビューのPowerPointにアイコンを加えるだけでも視覚効果が上がります! フィードバックはサイト右上のフィードバックフォーム、またはこの記事へのコメントから受け付けています。