AstroでCocoonのブログカードと商品紹介ボックスを作り直す

約9分で読めます
AstroでCocoonのブログカードと商品紹介ボックスを作り直す
目次全5項目
  1. 01ブログカードは、URLだけの行から作る
  2. 02外のサイトのタイトルや画像は、コミットのときに取って保存する
  3. 03商品紹介のボックスは、IDと名前だけで書く
  4. 04パンくずリストは、JSON-LDで出し直す
  5. 05まとめ

WordPressからAstroに移行するにあたって、気になったのがCocoonで提供されていたブログカードや商品紹介のボックスでした。記事を書く手間を減らすように、Astroでも手軽に扱えるコンポーネントを作ってみました。

Astroへの移行の記事の3本目です。前の2本はこちらです。

関連記事PHPの脆弱性をきっかけに、WordPressをAstroベースの静的サイトに置き換えた 関連記事WordPressブログからAstroへの移行に必要なリダイレクトとコメント対応

01 / 05ブログカードは、URLだけの行から作る#

Cocoonでは、URLだけを1行に書くと、その行がブログカードになりました。この手軽さを残したかったので、新しいサイトでも、URLだけの行がブログカードになるように作りました。MDXをHTMLにする処理にプラグインを足し、URLだけの段落を、カードのコンポーネントに置き換えるようにしました。カードの中身は、このブログの記事なら、その記事の今のタイトルと画像です。外のサイトなら、記事をコミットするときに保存した、相手のサイトのタイトルや画像です(次の節)。

プラグインは、Astro 7の標準のMarkdownの処理であるSätteriのプラグインとして書き、astro.config.mjsのmarkdown.processorに渡しました。MDXの記事も、この処理を通ります。URLだけの段落を置き換える部分は次のとおりです(画像だけの段落の扱いなどは省略)。

TypeScript
import type { HastPluginEntry } from 'satteri';
import type { ElementContent, Root, RootContent } from 'hast';
import tweetUrl from '@astro-community/astro-embed-twitter/matcher';
import youtubeId from '@astro-community/astro-embed-youtube/matcher';

type Node = Root | RootContent | ElementContent;
const NICOVIDEO = /^https:\/\/www\.nicovideo\.jp\/watch\/sm\d+$/;
const blank = (n: Node) => n.type === 'text' && !n.value.trim();
const textOf = (n: Node): string => (n.type === 'text' ? n.value : 'children' in n ? n.children.map(textOf).join('') : '');

export const blocks: HastPluginEntry = {
  name: 'blocks',
  element: [
    {
      filter: ['p'],
      visit(node) {
        const kids = node.children.filter((c) => !blank(c));
        const a = kids[0];
        if (kids.length === 1 && a.type === 'element' && a.tagName === 'a') {
          // URL に日本語があると、リンク先だけが %エンコードされる(文字はそのまま)。文字のほうの URL を使う
          const href = textOf(a);
          if (/^https?:\/\//.test(href) && (href === a.properties.href || encodeURI(href) === a.properties.href)) {
            // X の投稿と動画は埋め込み(判定は astro-embed のもの。ニコニコ動画は astro-embed にないので、ここで見る)。
            // ほかはブログカード
            const name = tweetUrl(href) ? 'Tweet' : youtubeId(href) || NICOVIDEO.test(href) ? 'Video' : 'Card';
            return { type: 'mdxJsxFlowElement', name, attributes: [{ type: 'mdxJsxAttribute', name: 'href', value: href }], children: [] };
          }
        }
      },
    },
  ],
};

置き換えた先のCard、Tweet、Videoは、記事のページが<Content components={components} />でMDXに渡すコンポーネントです。componentsは、{ Card, Tweet, Video, … }のように、名前とコンポーネントを並べたオブジェクトです。

URLだけの行かどうかは、リンク先(href)と表示の文字が同じかどうかで見分けています。[記事](URL)のように文字を付けたリンクを、カードにしないためです。ただ、URLに日本語が入っていると、リンク先だけが%エンコードされ、表示の文字はそのまま残ります。そのまま比べると一致せず、カードになりません。そこで、表示の文字をencodeURIしてから比べるようにしました。

Xの投稿と動画のURLは、カードでなく埋め込みにしました。YouTubeは、astro-embedのコンポーネントで出します。

02 / 05外のサイトのタイトルや画像は、コミットのときに取って保存する#

外のサイトのカードには、相手のサイトのタイトル、説明、画像、ファビコンを出します。これらはビルドのたびに取りに行くのではなく、記事をコミットするときに一度だけ取ってきて、リポジトリに保存するようにしました。WordPressにはなかった手順なので、手で動かさなくて済むように、取ってくるスクリプト(npm run cards)を、Gitのpre-commitフック(コミットの前に動くプログラム)から自動で動かすようにしました。

スクリプトは、コミットに記事が入っているときだけ動き、記事の中のURLだけの行のうち、まだ保存していないものを探して取ってきます。画像はOGPの画像を使い、OGPの画像がないサイトは、Chromeでページを撮ります。タイトルと説明はsrc/data/cards.jsonに、画像とファビコンはsrc/assets/cards/に保存し、フックが同じコミットに加えます。

取れなかったURLが残ると、コミットを止めるようにしました。フックを通さずにコミットしても、保存していないURLがあるとビルドが止まるので、取り忘れたまま公開されることはありません。

保存したものを使い続けるので、相手のサイトがタイトルや説明を変えても、カードは変わりません。取り直すときは、スクリプトに--refreshを付けて、URLを渡します。

ターミナルsh
npm run cards -- --refresh https://example.com/

03 / 05商品紹介のボックスは、IDと名前だけで書く#

商品紹介のボックスも、Cocoonの[amazon]のショートコードと同じくらい手軽に書けるようにしました。記事に書くのは、商品のID(AmazonはASIN、楽天は商品ページのURL)、ボックスに出す名前、検索の語だけです。

mdx
<Product asin="B08MDTMK5M" title="パナソニック 情報モジュラジャック CAT6A 埋込型 ホワイト" search="WZ7519W" />

ボタンのリンクは、ビルドのときにコンポーネントが作るようにしました。アフィリエイトのIDは設定のファイルの1か所にだけ書き、記事には書きません。リンクの形を変えるときも、1か所を直せば全記事に効きます。

ボタンは、Amazon、楽天市場、Yahoo!の3つです。IDを書いた店のボタンは商品のページを、ほかの店のボタンは検索の結果を開くようにしました。Amazonの商品は、Creators APIを使って、価格とサムネイルのURLを取得できるようにしました。

枠の手前には「広告」と出すようにしました。景品表示法のステルスマーケティングの規制に合わせ、アフィリエイトのリンクだと分かるようにするためです。

04 / 05パンくずリストは、JSON-LDで出し直す#

Cocoonのパンくずリストには、検索エンジン向けの構造化データが付いていました。新しいサイトでは画面のパンくずリストだけを作り直していて、構造化データが消えていました。Googleが新しいページをクロールするにつれて、Search Consoleの「パンくずリスト」の件数が減っていきました。

パンくずリストが有効なページは、PCのGoogleの検索結果で、URLの部分にドメインとパンくずリストが出ます。URLのパスの代わりに、どのカテゴリーの記事かが見えます。

そこで、パンくずリストをJSON-LD(BreadcrumbList)で出すようにしました。パンくずリストは、Astro向けのパッケージ(astro-breadcrumbs)を使わずに作りました。項目はURLでなく記事のカテゴリーから作るので、パッケージを使っても、項目の一覧は自分で渡すことになるためです。画面のパンくずリストとJSON-LDは、その一覧から作るので、食い違うことはありません。

11月2日にクロールされたページは、Search Consoleでパンくずリストとして有効になっています。

05 / 05まとめ#

Cocoonのブログカード、商品紹介のボックス、パンくずリストを、Astroで作り直しました。記事に書くのは、WordPressのときと同じく、URLだけの行と、商品のIDと名前だけです。

変わったのは、それぞれを用意するタイミングです。Cocoonは、ページを表示するときに、サーバの上でカードやボタンを作っていました。新しいサイトでは、外のサイトのタイトルや画像は記事をコミットするときに取って保存し、カードとボタンはビルドのときに作ります。WordPressにはなかった手順も、フックで自動で動くので、記事を書く手間はほとんど変わりません。

一方で、画面に見えないものは、作り直すときに抜けやすいところです。パンくずリストは画面には出ていたので、構造化データが消えていることに、Search Consoleの件数が減るまで気付きませんでした。Cocoonから移るときは、旧サイトのページの構造化データも比べておく必要があります。

コメント

コメントを書く

送ったコメントは、著者が確かめてから載ります。メールアドレスは聞きません。プライバシーポリシー

この記事を書いた人Hiroaki

注文住宅のスマートホーム化と、iPhoneアプリやFM音源のソフトウェアづくりを、自分で使いながら書いています。

hiroaki0923@hiroaki0923このサイトについて