方針
記事を単に見られなくするだけであれば、noindex nofollow
を付与したうえで一覧表示ページに追加しないなどの対応をとればよいでしょうが、今回はさらにURL文字列をシリアルナンバーに置き換えることでURLを推測しにくくする工夫を施します。
CMSやMarkdownフロントマターのフィールドにチェックボックスを追加します。これが true
である記事のみ、ビルド時にランダムなシリアルナンバーを発行し、データストアにキャッシュします。データストアには Vercel Edge Config を用いています。
Edge Configを利用することで、アプリケーションのインスタンスが変わっても公開設定を変えない限りは同一のシリアルナンバーを利用できます。
Vercel Edge Configの設定
プロジェクトにEdge Configストアを作成する
Edge Configはアカウントレベルもしくはプロジェクトレベルで導入することができます。ストアの作成はプロジェクトページの Storage タブから行います。
SDKの導入
Edge Config を扱いやすくするため、SDK @vercel/edge-config
を導入します。これは Edge Config へのシームレスなGETリクエストを実行するためのヘルパー関数を提供します。
$ npm i @vercel/edge-config
クライアントを初期化するために、Edge ConfigのRead access tokenを取得し、これを環境変数として保存します。SDKはデフォルトで EDGE_CONFIG
という変数を読み取ります。
// Vite では import.meta.env で環境変数にアクセスします
const client = createClient(import.meta.env.EDGE_CONFIG);
注意Edge Config への値の書き込み(POST, PATCH)を行うためのヘルパーはSDKに付属していません。これらはJSのFetch APIで行います(参考)。
記事ごとにランダムなシリアルナンバーを生成する
Edge Configは { key: value }
のようなJSON形式でデータを管理します。今回は記事に設定してあるスラグ文字列をkeyとし、ランダムなシリアルナンバーを生成してvalueとして保存します。
SDKには key を引数にとる以下のメソッドが存在します。
get()
-key
に対応するvalue
を取得するhas()
-key
がストアに存在するかどうかをbooleanで返す
const getValue = async (key) => {
const value = await client.get(key);
if (!value) return "";
return value;
}
const getIsExistKey = async (key) => {
return await client.has(key);
}
これらを利用し、後述するPATCHリクエストで使用するpayloadを生成するメソッドを以下のように実装しました。
const getPayload = (articles) => {
const payload = await Promise.all(articles.map(async (article) => {
const isExistKey = await getIsExistKey(article.slug);
// 限定公開の記事:keyがないなら登録、そうでないならスルー
if (article.isLimited) {
const id = await getSerializedNumber(); // シリアルナンバーを生成
const data = { operation: "create", key: article.slug, value: id; }
return isExistKey ? null : data;
// 限定公開でない記事:keyがあるなら削除、そうでないならスルー
} else {
const value = await getValue(article.slug); // Edge Configから値を取得
const data = { operation: "delete", key: article.slug, value: value; }
return isExistKey ? data : null;
}
// nullをクリーンアップ
return payload.filter((data) => data !== null);
}));
}
// ビルド時にシリアルナンバーを登録する
PATCHリクエスト
Edge ConfigにPATCHリクエストを行う関数をFetch APIで実装します。
const update = async (payload) => {
// 環境変数 EDGE_CONFIG_ID にEdge ConfigのIDを格納します
const endpoint = `https://api.vercel.com/v1/edge-config/${import.meta.env.EDGE_CONFIG_ID}/items`;
try {
await fetch(endpoint, {
method: "PATCH",
headers: {
// 環境変数 VERCEL_ACCESS_TOKEN にREST APIトークンを格納します
Authorization: `Bearer ${import.meta.env.VERCEL_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ payload });
});
} catch (error) {
console.error(error);
}
}
ビルド時にシリアルナンバーを更新する
以上の処理を getStaticPaths
内に含めればよいでしょう。
---
export const getStaticPaths = async () => {
// 限定公開の記事のみを抽出
const articles = (await getArticles).filter((article) => {
return article.isLimited
});
// 更新処理を実行
if (import.meta.env.PROD) {
const payload = await getPayload(articles);
await update(payload);
}
// slug の代わりにシリアルナンバーを渡す
const arr = await Promise.all(articles.map(async (article) => {
const id = await getValue(article.slug);
return {
params: { id: id },
props: { article: article },
}
}));
return arr;
}
---
こうすることで、limited/
にシリアルナンバーをパス文字列とした記事が生成されます。