LoginSignup
32
31

Reactで作ったレジュメにRechartsを組み込んでスタイリッシュにデータを魅せる

Last updated at Posted at 2024-04-24

きっかけ

いつものようにクラスメソッドさんのブログを見ていた時、何気なく執筆者さんのリンクを押下しました。

↑のページにある、各執筆者様たちのリンクをクリックします。するとこのように投稿数やシェア数と…

image.png

投稿した記事の一覧が簡単なプロフィールと共に表示されています。

image.png

これを見たワイ。

「かっけぇぇぇぇぇぇぇぇぇぇぇ〜〜〜〜!!自分のポートフォリオでもやりてぇぇぇぇぇぇぇ〜〜〜🤯」

冷静になって仕様を決める

「さてどういう仕様にするか?」を考え始めたところで、自分が技術記事を書いている媒体はZennとQiitaであることを思い出します。

これまでそれぞれへの導線はポートフォリオのトップに置いていました。

image.png

が、さっきのクラスメソッドさんのページをヒントにこう思い始めます。

「そもそも、記事の特性によって投稿する媒体を変えているけど、書いてるものは"技術記事"なんだから、プラットフォーム関係なく一覧に出しておきたいなぁ🤔」

できたもの

というわけでいきなりですが完成したブツです。完全に自画自賛ですがめちゃくちゃ気に入っています。

Rechartsを使って年間の投稿数を出したり、媒体別の月別記事数を出したり…

image.png

年間のいいね数を出したり、媒体別の月別いいね数を出したりしていて…

image.png

ZennとQiitaの両方の記事を投稿日時の降順にページングしながら表示しています。

image.png

ちなみに実際にポートフォリオを見ていただけるとわかると思いますが、2年近いデータを取得しているにもかかわらず非常に高速です。一切のラグがなく表示されているはずです。

そして気づかれた方もいらっしゃるかもしれません。開発者ツールからネットワーク状況を確認すると、データ取得のためのfetchが走っていません。

では、ここからはお待ちかね?の「どう作ったか」の話です。

要件

ゴールとしては先に話した通り、「グラフを使って投稿数やいいね数が見えること」「ZennとQiitaの記事を一括で一覧表示できること」でした。

が、非機能的な要件として以下を考えていました。

とにかくお金をかけない

まず、そもそもポートフォリオのホスティング先はFirebaseでReact×Viteでクライアントサイドで動いています。

つまり、APIを実行するために必要なトークンを秘匿して保持することができません。よって、アクセストークンが必要なQiita APIを実行できません。

トークンを秘匿しながらAPI実行するにはCloud Functionsなどを利用する必要がありますが、1円たりともお金は払いたくありません。

最新の情報を取得していきたい

当たり前ですが、最新の情報を表示してほしいです。

が、ポートフォリオにアクセスがある度に数年単位の記事情報を取得して表示するのはナンセンスでしょう。APIサーバーへ余計な負荷をかけることにもなるため、避けたいところです。

そのために、キャッシュを作りたいところです。

実装

以上の要件を満たすべく実装を進めていきます。

Recharts

まずは機能要件から。本筋の目的であるグラフ表示をするために、ライブラリの導入を検討しました。

今回は表題の通り、Rechartsを使うことにしました。理由は特にないのですが、強いて言えば割と頻繁にリリースがされていることと、実装が簡単そうだったからです。

実際、実装はとてもシンプルです。以下のようにimportするだけで簡単に使い始めることができます。

import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, BarChart, Bar } from 'recharts';
import { TechArticleData } from '../data/TechArticleData';

export const TechArticlesGraph = () => {
  return (
      <div className="grid grid-cols-1 items-center justify-center gap-4">
          <div className="flex justify-center items-center gap-4">
              <h1 className="text-center text-3xl font-extrabold text-gray-600 underline">Article Posts</h1>
          </div>
          <div className="flex justify-center items-center gap-4 mb-4">
            <ResponsiveContainer width="100%" height={300}>
              <BarChart
                width={500}
                height={300}
                data={TechArticleData.yearArticleCounts}
                margin={{
                  top: 20,
                  right: 30,
                  left: 20,
                  bottom: 5,
                }}
              >
                <CartesianGrid strokeDasharray="3 3" />
                <XAxis dataKey="year" />
                <YAxis />
                <Tooltip />
                <Legend />
                <Bar dataKey="articles" stackId="a" fill="#FBBC05" />
              </BarChart>
            </ResponsiveContainer>
          </div>
      </div>
  )
}

Rechartsのグラフに渡すデータ形式は以下のようになっている必要があります。

const data = [
  {
    name: 'Page A',
    uv: 4000,
    pv: 2400,
    amt: 2400,
  },
  {
    name: 'Page B',
    uv: 3000,
    pv: 1398,
    amt: 2210,
  },
  {
    name: 'Page C',
    uv: 2000,
    pv: 9800,
    amt: 2290,
  },
];

ということでZennとQiitaのデータを取得して、このオブジェクトのようなデータを作成する必要があります。

データ取得

記事情報のデータ取得には、Zenn・QiitaともにAPIを使うことができます。

Zennは簡単で、ただのGETリクエストを送るだけで記事一覧の取得が可能です。エンドポイントは以下の通りです。

https://zenn.dev/api/articles?username=ユーザー名&order=latest&count=取得件数

一方で一手間必要なのがQiitaです。リクエストを送信するためにアクセストークンの取得が必要になります。

トークンの取得方法・リクエストの送信方法はこちらをご参照ください。

これでデータの取得ができるようになりました。しかしQiitaAPIのトークンを保存する先が問題になります。

このポートフォリオはクライアントサイドで動くため、素直にトークンを隠匿することができません。

キャッシュの作成と保存先

先の課題を解決するために、GitHub Actionsで作成したプログラムを実行しデータを取得します。トークンなどはリポジトリのSecretsに持たせることで、外部に晒されることはありません。

今回はnode.jsでJavaScriptを実行し、取得したそれぞれの媒体の情報を整形したうえでRechartsやページング表示がしやすい形でTSファイルとして出力することでした。

const chartDataJson = JSON.stringify(chartData, null, 2);
const articleListJson = JSON.stringify(pagingArticles, null, 2);
const jsonContent = `export const TechArticleData = ${chartDataJson};\nexport const TechArticleList = ${articleListJson};`
fs.writeFile(FILE_PATH, jsonContent, 'utf8', (err) => {
  if (err) {
    throw err;
  }
  console.log('#### Print Succeeded!! ####');
});

その結果以下のようなTSファイルを作成し、このオブジェクトをグラフやページング表示で利用しています。

export const TechArticleData = {
  "articlesCounts": [
    {
      "yearMonth": "2023/04",
      "zenn": 8,
      "qiita": 0
    },
    {
      "yearMonth": "2023/05",
      "zenn": 10,
      "qiita": 0
    },
    {
      "yearMonth": "2023/06",
      "zenn": 2,
      "qiita": 0
    },
  ],
  "yearArticleCounts": [
    {
      "year": "2023",
      "articles": 54
    },
    {
      "year": "2024",
      "articles": 13
    }
  ],
  "favoritesCounts": [
    {
      "yearMonth": "2023/04",
      "zenn": 315,
      "qiita": 0
    },
    {
      "yearMonth": "2023/05",
      "zenn": 1152,
      "qiita": 0
    },
    {
      "yearMonth": "2023/06",
      "zenn": 79,
      "qiita": 0
    },
  ],
  "yearFavoritesCounts": [
    {
      "year": "2023",
      "favorites": 2265
    },
    {
      "year": "2024",
      "favorites": 443
    }
  ]
};
export const TechArticleList = [
  [
    {
      "treeType": "🖋",
      "img": "qiita",
      "year": "2024/04/20",
      "title": "Postmanを使い始めた時に知っておきたかった地味に便利な機能10選",
      "url": "https://qiita.com/ysknsid25/items/86fa54eca58edefe156d",
      "content": "❤️ 152"
    },
    {
      "treeType": "🖋",
      "img": "qiita",
      "year": "2024/04/18",
      "title": "[TIPS]MutableSetのaddメソッドを使って、先勝ちの処理をスリムに書く",
      "url": "https://qiita.com/ysknsid25/items/fa3c1d43c77f3a164a42",
      "content": "❤️ 6"
    },
    {
      "treeType": "🖋",
      "img": "zenn",
      "year": "2024/04/17",
      "title": "テックカンファレンスに「なんとなく」や「ただ楽しいから」で参加してない?",
      "url": "https://zenn.dev/bs_kansai/articles/4a8d9afc534d18",
      "content": "❤️ 50"
    },
  ],
  [
    {
      "treeType": "🖋",
      "img": "zenn",
      "year": "2024/04/03",
      "title": "テストコード品質を高めるためにJS向けMutation Testingライブラリ・Strykerを実戦導入してみた",
      "url": "https://zenn.dev/hitocolor/articles/3b6792cc9887df",
      "content": "❤️ 25"
    },
    {
      "treeType": "🖋",
      "img": "zenn",
      "year": "2024/03/04",
      "title": "Laravel(Pest)でInfectionを利用したMutation Testingを試してみる",
      "url": "https://zenn.dev/bs_kansai/articles/3a198f77e60d40",
      "content": "❤️ 5"
    },
    {
      "treeType": "🖋",
      "img": "zenn",
      "year": "2024/02/24",
      "title": "Re: WebサーバーアーキテクチャとPHP実行方式の理解から始めるphp-fpmとはなにか?",
      "url": "https://zenn.dev/bs_kansai/articles/3706c12408160c",
      "content": "❤️ 107"
    },
  ],
];

キャッシュの更新

キャッシュの更新はGitHub Actionsの定期実行で1日に一度行います。これで毎日最新の記事投稿状態でいいね数なども表示されます。

そこまで件数がないため更新時には先ほどのTSファイルをまるっと置き換えます。ちなみにファイル更新にかかっている時間は5秒でした。

image.png

今後の方針としては、あまり昔のデータを出力しても仕方がないと思っているので、直近5年くらいのデータを保持しようと考えています。

1年で50記事程度の増加具合なので、5年でも250件ほど。大した件数にはならなそうです。

そしてキャッシュを更新したタイミングで、ポートフォリオもデプロイを行います。

GitHub Actionsからのデプロイ方法は、これらの記事が参考になりました。

おわりに

ZennとQiitaの記事をいい感じにポートフォリオに出したいという人は、結構いらっしゃいそうです。

結構ポストに、いいねで反響があったり、「真似したい」という声も。

ということで、やってみたい方の参考になれば幸いです!めちゃ簡単にカッコ良くできますよ!!

32
31
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
31