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

Blog

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

markdocの独自タグ

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

Learn how to integrate Markdoc into a Next.js project.

この仕組みを使って、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. プレビューして、リンクが埋め込まれて表示されていれば成功となる。 スクリーンショット:記事の埋め込みリンクが表示されている