isirmtブログ
タグシリーズ投稿
お気に入りのタグ

ブログを書いている人

isirmt

ホームページ

GitHub
github_user_icon
isirmt
Software & Web
@isirmt

(管理者用)ダッシュボード

© isirmtBuild with NextjsBlogWithGitPAT  (535e811)
  1. 【Next.js】Markdownブログで個別にカスタムサムネイルを設定する
  2. はじめに
  3. 作り方
  4. 画像を取得する
  5. Base64からデコードしてAPIとしてレスポンスする
  6. 呼び出し方
  7. おわりに
共有
2024
10
1

【Next.js】Markdownブログで個別にカスタムサムネイルを設定する

コメント: 0件
Next.js
GitHub API
Route Handlers
ブログ

はじめに

みなさんは自身のMarkdownブログで「今回は特別にサムネイルを設定したい!」な時がありませんか?ありますよね!

Markdownブログの管理方法としてGitHubのリポジトリがあると思います.今回はそのリポジトリの画像フォルダにサムネイルを入れている場合を想定して実装し,実際にこの記事のサムネイルとして適用しました!

是非,投稿一覧やOGPが得られるサイト/サービスから確認してみてください!

↓ 実際のサムネイル

↓ 何も設定されていない場合に表示される動的生成のOGP画像

作り方

もし,何らかの方法でGitHubのリポジトリから記事内に画像を貼る仕様があるなら簡単に実装可能です!

画像を取得する

記事内へ画像を貼る/貼らないにしてもGitHub APIを通じてファイル内容を取得する必要があります.以下のソースコードのgitContentPathがapiへアクセスするためのURLです.今回は.env(.local)ファイルに以下の値を登録しています.

  • GIT_USERNAME=GitHubのユーザー名
  • GIT_REPO=GitHubでのリポジトリ名
const gitContentPath = `https://api.github.com/repos/${process.env.GIT_USERNAME!}/${process.env.GIT_REPO!}/contents`;

export const getImage = cache(async (path: string) => {
  const fileJson = await fetch(`${gitContentPath}${path}`, {
    ...getHeaders(),
    ...getNext(3600 * 24 * 30),
  })
    .then((res) => res.json())
    .catch((err) => console.error(err));

  const imageJson = await fetch(fileJson.git_url, {
    ...getHeaders(),
    ...getNext(3600 * 24),
  })
    .then((res) => res.json())
    .catch((err) => console.error(err));
  return imageJson.content as string;
});

ここで,fetch関数には2つのJSON形式の値を入れています.

export function getHeaders() {
  return {
    headers: {
      Authorization: `token ${process.env.GIT_TOKEN!}`,
      'Content-Type': 'application/json',
    },
  };
}
  • GIT_TOKEN=GitHub PATのトークン

このheaderオプションと,

export function getNext(revalidate: number): FetchOptions {
  if (revalidate === 0) {
    return { cache: 'no-store' };
  } else {
    return { next: { revalidate } };
  }
}

このcache/nextオプションを入れます.特にgetNextはrevalidateの指定でキャッシュ期間(再びfetchするまでの時間)を秒数で設定できます.画像そのものが変化することは中々ないので 3600*24*30(秒)=30(日) のキャッシュ期間を設けました.

取得したJSON形式の値からgit_urlを参照します.ここに画像の全容へアクセスできるためのリンクがあります.取得してもう一度fetchします.すると,JSONのcontentにBase64エンコーディングの画像が得られます!

これを返り値として次のステップに進みます.

Base64からデコードしてAPIとしてレスポンスする

ここからはBase64エンコーディングで取得する方法ができたので実際にリクエストに対してレスポンスを行います.新しくsrc/app/api/get-thumbnail/route.tsとして作ってみます.

URLパラメータからpathの値として受け取り,文字列が/GIT_IMAGES_DIR/から始まるか確認します.

  • GIT_IMAGES_DIR=リポジトリの画像を格納するディレクトリ

したがって,上述が満たされない場合は400エラーを返します.

import { getImage } from '@/lib/getPosts';
import { getImageMimeType } from '@/lib/mime-getter';
import { NextRequest, NextResponse } from 'next/server';

export async function GET(req: NextRequest) {
  const targetPath = new URL(req.url).searchParams.get('path');
  if (!targetPath) return NextResponse.json({ message: 'Fatal Error: no "path" query' }, { status: 400 });

  // 任意のディレクトリ内かチェック
  if (!targetPath.startsWith(`/${process.env.GIT_IMAGES_DIR}/`)) {
    return NextResponse.json({ message: 'Fatal Error: don\'t match "path" query' }, { status: 400 });
  }

  try {
    // Base64で取得
    const base64Image = await getImage(targetPath);

    const mimeType = getImageMimeType(targetPath);
    const imageBuffer = Buffer.from(base64Image, 'base64');

    return new NextResponse(imageBuffer, {
      status: 200,
      headers: {
        'Content-Type': mimeType,
        'Content-Length': imageBuffer.length.toString(),
      },
    });
  } catch (error) {
    console.error(error);
    return NextResponse.json({ message: 'Error fetching image' }, { status: 500 });
  }
}

imageBufferにてBase64エンコーディングから画像のバッファを作成します.あとは,Content-Typeをimage/サブタイプにして返すだけです.

getImageMimeType関数は渡されたpathから拡張子を切り取り判別し,対応したMIMEタイプを返します.

export function getImageMimeType(path: string) {
  const ext = path.split('.').pop()?.toLowerCase();
  switch (ext) {
    case 'jpg':
    case 'jpeg':
      return 'image/jpeg';
    case 'png':
      return 'image/png';
    case 'gif':
      return 'image/gif';
    case 'webp':
      return 'image/webp';
    case 'bmp':
      return 'image/bmp';
    case 'svg':
      return 'image/svg+xml';
    default:
      return 'application/octet-stream';
  }
}

他のMIMEタイプは以下より確認してください.

呼び出し方

既に動的OGPを呼んでいた引数の部分に下のように書けば実装完了です.

// dataはYAMLヘッダ部のJSON形式
data.thumbnail
    ? `/api/get-thumbnail?path=${encodeURIComponent(data.thumbnail)}`
    : `/api/ogp-posts/${slug}` /* 本来の動的OGP画像を生成するエンドポイント */

おわりに

今回は外部からはサムネイルを参照しないようにし,自身のリポジトリだけで完結させ問題が起こりにくいようにしています.ブログサイトにサムネイル表示機能があると,カスタムサムネイルがあるだけで全体的な印象が変わるのではないかと思います.

↓ 実際に変更したコミット内容

コメント

自動更新
コメントはまだありません
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.
LINKadd: custom thumbnai...Markdown blog u...https://github.com/isirmt/NextjsBlog...thumb
サムネイル
デフォルトサムネイル
LINKよくある MIME タイプ - HTTP...これは文書の種類に関連付けられ...https://developer.mozilla.org/ja/doc...thumb