Netlify Edge FunctionsでWebページのOGPデータを取得する関数を作る

Posts

このブログでは、外部のWebサイトの埋め込みパーツをiFramelyを使って実装していた。
ただ表示されるまでに少し遅延があったり、気軽にスタイルを変更したりしたいので、自分で実装することにする。
Webページから直接HTMLを取得するとCORS制約にかかってしまう。
そのためHTMLをパーズしてOGPのメタ情報を返すAPIを、Netlify Edge Functionで実装する。

このページでは、次のEdge Functionを作る。 クエリ文字列にOGPを取得したいWebページのURLを指定してGETリクエストを送信すると、WebサイトのOGPをJSON形式で取得するEdge Functionを作る。
この記事で作成するEdge Functionは、chick-p/netlify-edge-function-ogpに置いている。

Netlify Edge Functionsとは

Netlify Edge Functionsは、Netlifyが提供する、ユーザーのロケーションに近いエッジ環境でサーバーレス環境である。
ほかの同様のサービスには、AWSのCloudFront FunctionsやCloudflareのCloudflare Workersがある。

Netlify Edge Functionsの特徴は、次のとおり。

  • Deno Deployを採用しているため、TypeScriptやJavaScriptで書くことができる
  • メモリは512MB、CPU時間は50ミリ秒以内という制約がある
  • Netlifyのフリープランの場合、3分/月まで実行できる

Edge Functionは次の3ステップで作成できる。

  1. netlify/edge-functionsというディレクトリの中に実行するスクリプトを置く
  2. netlify.tomlに呼び出すスクリプトとルーティングの設定を書く
  3. Netlify CLIを使ってデプロイする

いくつかEdge Function のサンプルがGitHubに公開されているので、書き方がわからない場合にはこれらのサンプルを参考にするとよさそう。

WebページのOGPデータを取得するNetlify Edge Functionsを作る

動作を確認した環境

Netlify CLI v11.5.1

STEP0:事前準備

プロジェクトのディレクトリに、次の構成でディレクトリとファイルを作成する。
ファイルはまだ空のままで良い。

.
├── netlify
   └── edge-functions
       └── get-meta.ts
└─── netlify.toml

STEP1:スクリプトを作る

netlify/edge-functions/get-meta.tsを開き、次の内容を記載する。
Netlify Edge Functionsでは、export defaultするハンドラーの第一引数でリクエストを受け取り、Responseオブジェクトを返却する。

netlify/edge-functions/get-meta.ts
import { Context } from "https://edge.netlify.com";
import { DOMParser } from "https://deno.land/x/deno_dom/deno-dom-wasm.ts";

type Meta = Record<string, string>;

function extractOgp(metaElements: HTMLMetaElement[]): Meta {
  return metaElements
    .filter((element: HTMLMetaElement) => element.hasAttribute("property"))
    .reduce<Meta>((prev, element) => {
      const property = element.getAttribute("property")?.trim();
      if (!property) return { ...prev };
      const content = element.getAttribute("content") || "";
      return {
        ...prev,
        [property]: content,
      };
    }, {});
}

// eslint-disable-next-line import/no-anonymous-default-export
export default async (
  request: Request,
  context: Context
): Promise<Response> => {
  const url = new URL(request.url).searchParams.get("url"); // クエリ文字列を受け取る
  if (!url) {
    return context.json({ error: "The query parameter of url is required." });
  }
  const resp = await fetch(url);
  const html = await resp.text();
  const document = new DOMParser().parseFromString(html, "text/html");
  const meta = document?.querySelectorAll("meta");
  if (!meta) {
    return context.json({ error: "Meta element are not found." });
  }
  const ogp = extractOgp([...meta]);
  const response = await context.json(ogp);
  // CORS 対策
  response.headers.set("Access-Control-Allow-Origin", "*");
  response.headers.set("Access-Control-Allow-Methods", "GET");
  return response;
};

STEP2:呼び出すスクリプトとルーティングの設定を書く

netlify.toml内の[[edge_function]]というセクションで、スクリプトとルーティングの関係を定義する。
この関数では、クエリ文字列を渡せるようにしたいので末尾に*というワイルドカードをつける。

netlify.toml
[build]
  command = "echo No build for this site, we are living on the edge"
  publish = "."

[[edge_functions]]
  function = "get-meta"
  path = "/get-meta*"

STEP3:デプロイする

デプロイには、Netlify CLIを使う。
グローバルインストールすると、netlifyというコマンドが使えるようになる。

$ npm install netlify-cli -g

まずはローカル環境で動作を確認する。プロジェクトのディレクトリで次のコマンドを実行する。

$ netlify dev

 Netlify Dev
 Ignored general context env var: LANG (defined in process)
 No app server detected. Using simple static server
 Running static server from "netlify-edge-function-ogp"
 Setting up local development server

────────────────────────────────────────────────────────────────
  Netlify Build
────────────────────────────────────────────────────────────────

 Version
  @netlify/build 27.16.1

 Flags
  {}

 Current directory
  /Users/chick-p/works/netlify-edge-function-ogp

 Config file
  /Users/chick-p/works/netlify-edge-function-ogp/netlify.toml

 Context
  dev

────────────────────────────────────────────────────────────────
  1. Run command for local development
────────────────────────────────────────────────────────────────


 Static server listening to 3999

(dev.command completed in 8ms)

   ┌─────────────────────────────────────────────────┐

 Server now ready on http://localhost:8888

   └─────────────────────────────────────────────────┘

 Loaded edge function get-metas

http://localhost:8888/get-meta?url=https://blog.chick-p.workにアクセスして、OGP情報に関するJSONが返ってくれば成功となる。

Netlifyにデプロイするには、プロジェクトのディレクトリで次のコマンドを実行する。

# プレビュー環境の場合
$ netlify deploy --build

# プロダクション環境の場合
$ netlify deploy --build --prod

コマンドを実行するとNetlifyにログインすることを求められ、アプリを新しく作るかどうかを尋ねられる。 新しくアプリを作ることを選択すると、デプロイが始まる。

$ netlify deploy --build --prod

Packaging Edge Functions from netlify/edge-functions directory:
 - get-meta

(Edge Functions bundling completed in 2.4s)

────────────────────────────────────────────────────────────────
  Netlify Build Complete
────────────────────────────────────────────────────────────────

(Netlify Build completed in 2.5s)
Deploy path:        /Users/chick-p/works/netlify-edge-function-ogp
Configuration path: /Users/chick-p/works/netlify-edge-function-ogp/netlify.toml
Deploying to main site URL...
 Finished hashing 5 files and edge functions
 CDN requesting 0 files
 Finished uploading 0 assets
 Deploy is live!

Logs:              https://app.netlify.com/sites/foo/deploys/63145a19063a093f65a88c86
Unique Deploy URL: https://63145a19063a093f65a88c86--foo.netlify.app
Website URL:       https://foo.netlify.app

実行結果の最後の「Website URL」がEdge FunctionをデプロイしたアプリのURLになる。
URLの末尾に/get-meta?url=OGP情報を取得したいURLを実行して、ローカル環境で確認結果と同様に、OPG情報のJSONが返ってくれば成功である。