🐕

RemixでMarkdownからメタタグを生成する

5 min read

こんな感じのメタタグを動的に生成する。

説明すると長くなるので、日付関連の部分はfront-matterからすべて省略した。

環境

  • Remix v1.0.4
  • @ryanflorence/md v7.0.1
  • gray-matter v4.0.3

参考

https://github.com/kentcdodds/kentcdodds.com

Kent C. DoddsさんのWebサイトは、Remixがクローズドベータだった頃にリニューアルされ、完全にRemixで書き直された。現状、Remixを使った完成品の貴重な例なので、めちゃくちゃ参考になる。

MDの準備

/data/entries/にMarkdownファイルを書く。

/data/entries/hoge.md
---
title: タイトル
summary: 概要
---

あああ

/app/models/md.d.ts
interface MdFrontMatter {
  title: string | null;
  slug: string | null;
  fileName: string;
  summary: string | null;
  tags: string[] | null;
  draft: boolean | null;
  image: string | null;
  shareText: string | null;
}
export interface Md {
  rawHTML: string | null;
  editUrl: string | null;
  frontMatter: MdFrontMatter;
}

frontmatterを前提にした型を作る。

MDのパース

fsを使う関係で、 .server.ts というファイル名にする。

/app/utils/md/getMdBySlug.server.ts
import { Md } from '~/models/md';
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
import { processMarkdown } from '@ryanflorence/md';

/**
 * Markdownをパース
 * @see https://github.com/gyarudev/eloper/blob/main/src/components/md/Renderer.tsx
 * @see https://github.com/timlrx/tailwind-nextjs-starter-blog/blob/master/lib/mdx.js
 * @param slug
 * @returns
 */
export default async function getMdBySlug(slug: string): Promise<Md> {
  const root = process.cwd();
  const mdPath = path.join(root, 'entries', `${slug}.md`);
  const source = fs.readFileSync(mdPath, 'utf8');
  const fileName = `${slug}.md`;
  const gitHubLink = `https://github.com/ユーザー名/レポジトリ/edit/main/data/entries/${fileName}`;

  const { data, content } = matter(source);
  const rawHTML = await processMarkdown(content);
  return {
    rawHTML,
    editUrl: gitHubLink ?? null,
    frontMatter: {
      slug: slug || null,
      fileName,
      title: data.title ?? 'タイトル取得失敗',
      tags: data.tags ?? null,
      summary: data.summary ?? null,
      draft: data.draft ?? null,
      image: data.image ?? null,
      shareText: data.shareText ?? null,
      ...data,
    },
  };
}

メタ情報の生成

/app/utils/seo.ts
import meta from '~/config/meta';

interface Props {
  keywords?: string | null;
  title?: string | null;
  ogImage?: string | null;
  description?: string | null;
  createdAt?: string | null;
  lastUpdateAt?: string | null;
}


export function generateMetaInfo(props: Props) {
  const title = props.title ? `${props.title} - サイト名` : 'サイト名';
  const description = props.description ?? 'サイトの説明';
  return {
    title,
    description: props.description ?? '',
    keywords: props.keywords ?? '',
    'og:title': title,
    'og:description': description,
    'og:image': props.ogImage,
    'twitter:card': props.ogImage ? 'summary_large_image' : 'summary',
    'twitter:creator': '@ユーザー名',
    'twitter:site': '@ユーザー名',
    'twitter:title': title,
    'twitter:description': description,
    'twitter:image': ogImage,
    'twitter:alt': title,
    'article:published_time': props.createdAt ?? '',
    'article:modified_time': props.lastUpdateAt ?? '',
  };
}

Remixでは、key:valueのオブジェクトをmetaとしてexportするだけでメタタグを生成してくれる。

MDからメタタグを生成する

/app/utils/md/meta.ts
import { Md } from '~/models/md';
import { generateMetaInfo } from '~/utils/seo';

/**
 * Generate meta from md
 * @param param0
 * @see https://github.com/kentcdodds/kentcdodds.com/blob/main/app/utils/mdx.tsx
 * @returns
 */
export const mdPageMeta = ({ data }: { data: { post: Md | null } | null }) => {
  if (data?.post) {
    const { frontMatter } = data.post;
    return {
      ...generateMetaInfo({
        title: frontMatter.title,
        description: frontMatter.summary,
      }),
    };
  }
};

dataがページのloaderで読むデータで、parentsDataは親ページから継承されるデータである。

ページで使う

/app/routes/entries/$slug.tsx
import { useLoaderData, MetaFunction } from 'remix';
import type { LoaderFunction } from 'remix';
import getMdBySlug from '~/utils/md/getMdBySlug.server';
+ import { mdPageMeta } from '~/utils/md/meta';

export let loader: LoaderFunction = async ({ params }) => {
  const post = await getMdBySlug(params.slug ?? '');
  return { post };
};

+ export const meta = mdPageMeta;

export default function PostSlug() {
  let { post } = useLoaderData();
  if (post && post.frontMatter.draft == true) return <div>🚧 下書きだよ</div>;
  if (post) return <div>{post.title}</div>;
  return null;
}

あとはmetaとしてexportする。

postがロードできていれば、メタタグが生成される。

http://localhost:3000/entries/hoge にアクセスして確認しよう。

Discussion

ログインするとコメントできます