ブログを作り直しました。
1年ほど前にGatsby.jsで作ったブログがあったのですが、それを今回Next.jsで作り直しました。理由は、見た目とコードがショボくていじる気が失せてたためです。
一年前はまだ仕事でReact/TypeScriptを書いた経験がなく、今見返すと良いコードとは言えないものでした。
転職もして、仕事で毎日React/TypeScriptを書いてる今ならもっとまともなものが作れるなと思い、勢いで作り替えました。

主な技術スタック

  • Next.js
  • Chakra UI
  • microCMS

前回のブログとは変えた点

  • Zennのフィードも表示するようにした
  • 記事一覧のページネーションをやめた
  • 動的OGPをimgixのAPIを使って生成するようにした
  • 記事検索機能を追加した
  • プロフィールページをNotionで管理するようにした

などです。

作るにあたって工夫した点はあまりなくて、ひたすら楽をして作りたいという気持ちで作りました。
見た目に関してはChakra UIのコンポーネントをそのまま使って色を変えてるだけですし、ページネーションについては実装がめんどくさいので1ページに全記事表示するようにしました。
というかJAMStackなんだからビルド時に全記事のデータ取得するしページネーション作ってページを分けて表示する必要ってあるんでしょうか?
何千記事とかあるなら話は変わってくると思いますが(サーバーから取得するHTMLのサイズが膨れ上がるので)、100記事にも満たないこのブログではページネーションは不要だと判断しました。
使う側としても1ページに全記事載ってる方が嬉しくないでしょうか?InstagramとかTwitterもそうですけど、ひたすら縦にスクロールしていく無限スクロール/ローディングのUIが今では当たり前な感じがします。
Zennのフィード表示はRSSを使って実現しています。
動的OGPについて、以前作ったブログでは、node-ogp-creatorという自作のパッケージを使ってビルド時にOGP画像を生成してたのですが、Next.jsと相性が良くない気がして(public配下にwriteFileするのってあんま見かけないし気が進まなかった)使うのをやめました。
その代わりにmicroCMSのブログで紹介されていたimgixのAPIを使ってOGP画像を生成する方法に変えました。使い方はmicroCMSのブログ記事を見ていただければなんとなくわかると思います。はっきりいってimgixは動的OGP生成の救世主です。めちゃくちゃ便利だし、今後も使いたいと思いました。APIがちょっと理解しづらい気がするのが難点ですが。
あと、記事検索機能についてですが、Algoliaのような外部サービスは使いませんでした。
Next.jsのstatic propsで全記事のタイトルを渡すようにしておいて、それに対して入力された文字列で部分一致で検索をかけるようにしています。

import { SearchIcon } from '@chakra-ui/icons';
import {
  Box,
  Heading,
  Input,
  InputGroup,
  InputRightElement,
} from '@chakra-ui/react';
import { GetStaticProps, InferGetStaticPropsType, NextPage } from 'next';
import { NextSeo } from 'next-seo';
import React, { useState } from 'react';
import FeedCard from '../components/FeedCard';
import Layout from '../components/Layout';
import PostCard from '../components/PostCard';
import { getAllContents, isMicrocmsPost } from '../lib/microcms';
import { getZennFeedItems } from '../lib/zenn';
import type { PostListResponse, PostResponse, ZennFeedItem } from '../types';
import { sortByDate } from '../utils/arrayHelpers';


type StaticProps = {
  postOrFeedList: (PostResponse | ZennFeedItem)[];
};
type PageProps = InferGetStaticPropsType<typeof getStaticProps>;
const Search: NextPage<PageProps> = ({ postOrFeedList }) => {
  const [value, setValue] = useState('');


  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setValue(e.target.value);
  };


  const hitList =
    value === ''
      ? []
      : postOrFeedList.filter((v) => {
          const searchTarget = v.title.toLowerCase();
          return searchTarget.includes(value.toLowerCase());
        });


  return (
    <>
      <NextSeo title="記事検索" description="記事検索ページです" />
      <Layout>
        <InputGroup>
          <Input placeholder="記事タイトルを検索" onChange={handleChange} />
          <InputRightElement>
            <SearchIcon />
          </InputRightElement>
        </InputGroup>
        <Box marginTop="10">
          {hitList.length !== 0 ? (
            <Heading as="h1" color="blue.700">
              検索結果
            </Heading>
          ) : value !== '' ? (
            <Box>記事が見つかりませんでした。</Box>
          ) : null}
          <Box marginTop="5">
            {hitList.map((postOrFeed, i) =>
              isMicrocmsPost(postOrFeed) ? (
                <PostCard key={postOrFeed.id} post={postOrFeed} />
              ) : (
                <FeedCard key={i} feedItem={postOrFeed} />
              )
            )}
          </Box>
        </Box>
      </Layout>
    </>
  );
};


export const getStaticProps: GetStaticProps<StaticProps> = async () => {
  const allPostList = await getAllContents<PostListResponse>('post');
  const postList = allPostList.flatMap((postList) => postList.contents);
  const zennFeed = await getZennFeedItems();
  const postOrFeedList = [...postList, ...zennFeed];
  postOrFeedList.sort(sortByDate);


  return {
    props: {
      postOrFeedList,
    },
    revalidate: 60,
  };
};


export default Search;



最後に、プロフィールページについてはmicro CMSでプロフィールのコンテンツを管理するのをやめました。プロフィールって割と頻繁に更新する可能性のあるコンテンツだと思うので、気軽に編集できることが望ましいです。
microCMSでもスマホから編集できないことはないですが、UIとか操作性、ネイティブアプリの有無などを考えて、Notionでプロフィールを管理することにしました。ブログにはNotionの公開ページのリンクを貼るだけにしています。
これからはプロフィールを編集したくなってもスマホのNotionのアプリからお手軽に編集できるのでちゃんと内容を更新できそうです。前のブログでは転職したのに半年近く放置してしまってました。
以下が今回新しく作成したNotionのページです。
https://hallowed-axolotl-164.notion.site/093e624f4786477f94ac2d2f28de5988

こんな感じで新しいブログは大体3〜4日くらいでリプレイスできました。
普段から使い慣れてるNext.jsとUIで楽するためのChakra UIを使ったことでかなり早く実装できた気がします。
見た目に関しても自分の好きなインディゴっぽいカラーで雰囲気を統一することができて満足です。
ドメインもdev.eringiv3.comからeringiv3.devに変更しました。やっぱdevドメインの方がエンジニアのブログ感出ていいですよね。

ソースコードも公開してるので気になる方は見ていただければと思います。
https://github.com/EringiV3/devBlog_V2

古い記事の中で他の記事を参照してるリンクがリンク切れしてしまっていますが、今は直す気力がないので多分しばらくこのままです…。気が向いたら直します。