markdoc + Next.js 製のブログで Iframely の外部リンクを埋め込むタグを作る

Blog
August 26, 2022

最近、このブログを Gatsby.js から Stripe が開発している markdoc と Netx.js 使って書き換えた。
それに伴って、外部リンクを見栄え良く表示できる Iframely を埋め込む仕組みを変更する必要があったため、markdoc の独自タグを作成した。

markdoc の独自タグ

markdoc では、Markdown ファイル内で {% TAG %} のような形式で書くことでMarkdown では表現できない HTML 要素を記述できる。
これを実現するために、独自タグを実装する。
Gatsby.js の Component や HUGO のショートコードのようなものである。

この仕組みを使って、Markdown ファイル内に次のように記述すれば、Iframely を使って外部サイトの情報を埋め込みできるタグを実装する。

{% iframely href="URL" id="DATA-ID" /%}

なお、記事におけるファイル構成は、Schema customization に従うものとする。

タグの実装

今回は「iframely」という名前のタグを実装する。

STEP1:タグを定義する

「iframely.markdoc.ts」という名前のファイルを作る。

render プロパティにはレンダリングするコンポーネント(後述)を指定し、attributes にはそのコンポーネントに渡す引数を定義する。
Iframely を使って外部リンクに埋め込むときは、次の 2 箇所が可変となるので、これらの値を引数に取るようにする。

  • a 要素に指定する href 属性
  • data-iframely-url 属性内の「//iframely.net/この部分」、
markdoc/tags/iframely.markdoc.ts
import { Iframely } from "../../components/Iframely";

// eslint-disable-next-line import/no-anonymous-default-export
export default {
  render: Iframely,
  description: "iframely",
  attributes: {
    href: {
      type: String,
      description: "The link url",
    },
    id: {
      type: String,
      description: "The data-framely ID",
    }
  },
};

STEP2:コンポーネントを実装する

「iframely.markdoc.ts」に指定するコンポーネントを実装する。
レンダリングする HTML は Iframely が生成するタグと同じ構造にして、可変となる hrefid の値をコンポーネントに渡す。
実際の Iframely が生成するタグには class 属性がついているが、自分のブログでは表示が壊れたためそれらの class 属性は付与していない。 また、Iframely を表示するためのスクリプトタグは、<head> に適用するので削除した。

components/Iframely.tsx
export function Iframely({ href, id }) {
  return (
    <>
      <div>
        <a
          href={href}
          data-iframely-url={`//iframely.net/${id}?card=small`}
        ></a>
      </div>
    </>
  );
}

実装したタグを tags のルートファイルにエクスポートする。

markdoc/tags/index.ts
export { default as iframely }  from "./iframely.markdoc";

STEP3:Iframely のスクリプトタグを追加する

記事を表示するコンポーネントに、Iframely のスクリプトタグを入れる。
今回は react-helmet を使って <head> タグ内に差し込む。

_app.tsx
import React from "react";
+ import Helmet from "react-helmet";
// 省略
export function Home({ options }) {
+  React.useEffect(() => {
+    if (window.iframely) {
+      window.iframely.load();
+    }
+  });
  // 省略
  const { ast, content, config, errors } = useMarkdocCode(code);
  const children = content.children;
  return (
    <>
+      <Helmet>
+        <script async src="https://cdn.iframe.ly/embed.js" />
+      </Helmet>
      <!-- 省略 -->
      <div>
        {Markdoc.renderers.react(children, React, {
          components: config.components,
        })}
      </div>
    </>
  ):
}

windowiframely がないと怒られるので、型定義ファイルに宣言を追加する。

types/global.d.ts
declare var iframely: any;

STEP4:実装したタグを markdown parser のスキーマに渡す

独自タグを markdoc の schema に渡す。
parse した markdown がレンダリングされる前にタグを解釈する必要があるので、記事を表示するコンポーネント内で schema を指定する。
Create a custom tag

次の例は、トップページで Markdown ファイルのコンテンツを表示するコードである。

pages/_app.tsx
import React from "react";
+ import { getSchema } from '@markdoc/next.js/runtime';
// 省略
+ import * as tags from "../markdoc/tags";
+ const schema = {
+   tags,
+ };
// 省略
export function useMarkdocCode(code) {
  const ast = React.useMemo(() => Markdoc.parse(code), [code]);
  const config = React.useMemo(() => {
+    const { components, ...rest } = getSchema(schema);
    return {
      ...rest,
      variables: {
        markdoc: {
          frontmatter
        },
      },
      components,
    };
  }, [ast]);
}

export function Home({ options }) {
  const { ast, content, config, errors } = useMarkdocCode(code);
  const children = content.children;
  return (
    <>
      <Helmet>
        <script async src="https://cdn.iframe.ly/embed.js" />
      </Helmet>
      <div>
        {Markdoc.renderers.react(children, React, {
          components: config.components,
        })}
      </div>
    </>
  ):
}

動作確認

  1. Iframely にアクセスする。

  2. 埋め込みたいページの URL を入力して、タグを生成する。

  3. 生成されたタグにあるdata-iframely-url 属性内の「//iframely.net/この部分」(ID)を確認する。

  4. ブログにMarkdown ファイルを追加し、次のように記載する。

    {% iframely href="埋め込む記事のURL" id="ID" /%}
    
  5. プレビューして、リンクが埋め込まれて表示されていれば成功となる。 スクリーンショット:記事の埋め込みリンクが表示されている