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 が返ってくれば成功である。