JSON-LDに対応してみた

created_at

なんとなく最近、ブログのデザインをいじり始めました。

元々超ミニマル?なデザインにしていたのですが、他のブログやってる方のサイトなどを巡回してて少し影響されてしまいました。また少しずついじっていこうと思います。

今回は、JSON-LDという検索エンジン向けの構造化データにこのブログを対応させてみましたという話です。

JSON-LD とは

JSON-LDは軽量なリンクデータ形式です。人間が読み書きしやすい特徴を持ち、既に広く普及しているJSON形式を基盤としています。これにより、JSONデータがウェブ規模で相互運用可能になる手段を提供します。JSON-LDはプログラミング環境、REST Webサービス、Apache CouchDBやMongoDBなどの非構造化データベースに最適なデータ形式です。

https://json-ld.org/ より日本語に翻訳したものを引用)

検索エンジンに対して明示的にコンテンツの情報を提供することで、例えばGoogleであればリッチリザルトという形で、検索結果が正確になるうえに様々な情報が表示できるようになります。

ECサイトなどの商品がより検索に引っ掛かりやすくなるように、このような情報を定義して渡すという事例はよく見かけます。SEO対策にもなるみたいですね。

やってみた

これをやろうと思った動機は、検索に引っ掛かりやすくするためみたいなものではなく、単純に興味です。それ以上はありません。明示的にこちらから情報を提供することになんとなく面白さを感じました。

弊ブログはAstro製なので、今回は以下の記事を参考にさせていただきました。

その中で紹介されているようなやり方とサンプルを参考に、以下の関数を用意しました。

import { SITE_TITLE, SITE_DESCRIPTION, SITE_ADMIN } from '../consts'
import type { WithContext, Article, Person, WebSite } from 'schema-dts'
import type { CollectionEntry } from 'astro:content'

const SITE_URL = 'https://blog.snowsphere.net/'

const person: Person = {
  '@type': 'Person',
  '@id': `${SITE_URL}#author`,
  name: SITE_ADMIN,
  url: SITE_URL,
  sameAs: [
    'https://moemoe.dev/@snosph'
  ]
}

export const webSiteSchema: WebSite = {
  '@type': 'WebSite',
  '@id': SITE_URL,
  name: SITE_TITLE,
  description: SITE_DESCRIPTION,
  url: SITE_URL,
  inLanguage: 'ja',
  author: person
}

export const articleSchema = (blog: CollectionEntry<'blog'>): WithContext<Article> => {
  const url = `${SITE_URL}article/${blog.slug}/`
  return {
    '@context': 'https://schema.org',
    '@type': 'Article',
    '@id': `${url}#article`,
    url,
    mainEntityOfPage: {
      '@type': 'WebPage',
      '@id': url
    },
    headline: blog.data.title,
    description: blog.data.description,
    keywords: blog.data.tags?.join(', '),
    author: person,
    datePublished: blog.data.pubDate.toISOString(),
    dateModified: (blog.data.updatedDate ?? blog.data.pubDate).toISOString(),
    image: `${SITE_URL}snowsphere.jpg`,
    isPartOf: webSiteSchema
  }
}

まず schema-dts というJSON-LD向けの型定義が公開されているので、こちらをimportして適度に型を使いながら実装していきます。そこまで難しいこともなく、単純に必要なプロパティを列挙していく感じですね。どのぐらい細かくプロパティを用意した方が良いのかがちょっとわからなかったのですが、埋めれるところは埋めてみました。

少し気を付ける必要があるのが、URLを扱うプロパティは、中途半端にパスを書くと相対パス扱いをされてしまいます。 なので絶対パス(フルパス)で基本書くのがいいと思います。

あとはhead内のscriptタグに対してpropsで渡しました。

<!-- 記事のレイアウトから渡す -->
<head>
  <BaseHead title={post.data.title} description={post.data.description} jsonLdSchema={articleSchema(post)} />
</head>
---
import '../styles/global.css'
import type { WebSite, WithContext, Article } from 'schema-dts'

interface Props {
  title: string
  description: string
  image?: string
  jsonLdSchema?: WebSite | WithContext<Article>
}

const canonicalURL = new URL(Astro.url.pathname, Astro.site)

const { title, description, image = '/snowsphere.jpg', jsonLdSchema } = Astro.props
---

{jsonLdSchema && (
  <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLdSchema)}></script>
)}

これらの実装が終わったら、Google Search Consoleのリッチリザルトテストで各プロパティが認識されているか試してみるのがいいと思います。

https://search.google.com/test/rich-results?hl=ja

さいごに

このサイトでの実装例を置いておきます。

https://github.com/prismistim/blog

結局、こんなことに対応したところで中身が一番大事ですよね!