プログラミング
プログラミング
  • 2024/09/22
  • 2024/09/22

Astro + Newt + Vercel Edge Configでブログに限定公開の機能をつけました

当ブログには記事を限定公開する機能がなかったので、自前で実装してみました。

方針


記事を単に見られなくするだけであれば、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/ にシリアルナンバーをパス文字列とした記事が生成されます。

関連記事


    記事がありません

Shota Inoue
Shota Inoue

大学生 | 化学・Webプログラミング・統計学など