私はこれまでに何度ブログのコードを書き直したかは覚えていませんが、これが(短期間内で)最後の機会であることは確信しています。とにかく、カスタマイズ性、メンテナンス性、コストの三者の間でバランスを見つけられない完璧主義に一時的な区切りをつける時が来ました。
書き直しの理由#
古いブログのページデザインがますます気に入らなくなったため、私は直接覆して再デザインすることに決めました。しかし、そうする前に、私はしばらくブログを更新していないことに気づきました。最近の更新は、理解しがたい小説のいくつかであり、ほとんどは別のサイトからの作品をそのまま移したものでした。要するに、私は長い間、じっくりとブログを書くことができていませんでした。時折、自分の探求の過程を長文で記録することは非常に重要だと思います。これは、私が手帳を作成する際の日常的な記録とは大きく異なり、物事そのものに焦点を当てた記述は、思考を整理しながら記憶を深めるのに役立ちます。
私はブログを書く習慣をうっかり失ってしまいましたが、その大きな理由は静的ブログを更新する際の煩雑な操作プロセスです。前回のブログコードを書き直したとき、私は純粋な Next.js または Svelte フレームワークを使用しており、いわば「サーバーレス」アプリケーションでした。データベースがないため、ブログのすべての記事は Markdown ファイルとして、ブログのソースコードと一緒に保存されていました。これにより、ブログを更新する必要があるとき、私はまず Markdown ファイルに記事の内容を書き、ファイルの冒頭に記事のタイトル、作成日、タグなどのメタ情報を示すための必須のフロントマターを書く必要がありました(しかも、毎回フォーマットや属性の命名を忘れてしまい、以前のファイルを見つけて新しいファイルにコピーする必要がありました)。ファイルが準備できたら、それを Git リポジトリの対応するディレクトリに置き、ローカルで npm run dev
を実行して問題がないかテストし、問題がなければ GitHub にプッシュして Vercel が新しいブログバージョンを本番環境にデプロイするのを待ちます。その後、記事内のいくつかのエラーを修正したり、追加の更新を行ったりする必要がある場合、私はモバイルデバイスでこれらの操作を行うことができず、コンピュータを開いて GitHub Desktop と VS Code を開き、内容を編集し、テストし、再度プッシュし、デプロイを待つ必要があります。
静的ブログに初めて触れる開発者は、これが面白いと感じるかもしれませんが、私はすぐに飽きてしまいました。なぜなら、時には単に考えを伝えるための記事を書いたり、純粋に記録したりしたいだけなのに、バグを書く準備をしているかのようなインターフェースを開いて、一連の非常に「ハッカー」な操作を行わなければならないからです。これはまさに反人間的です。
ブログを書く良い習慣を保ちながら、自分を妥協させないために、私は自分がより快適に感じるブログシステムを書き直すことに決めました。
私の考え#
私は機能が充実したグラフィカルなブログ管理バックエンドが必要ですが、自分で車輪を再発明したくはなく、市場にある派手な CMS を「大砲で蚊を撃つ」ようなこともしたくありません。私はただ、シンプルで使いやすい、個人ブログに適したコンテンツ管理プログラムが欲しいのです。
この説明に合う答えはもちろん ——Typechoです。
しかし問題は、Typecho は PHP で書かれたフロントエンドとバックエンドが一体となったブログプログラムであることです。JavaScript でフロントエンドを書くことを楽しんできた私にとって、PHP 時代に戻ることは、現代人が山の中の岩穴に住むようなものです。私は PHP に再適応し、ブログテーマを再度書き直さなければなりません。これは私には受け入れがたいことです。
私は伝統的なブログの簡単な操作による便利さを享受したい一方で、現代的なフロントエンド開発の優雅さと効率を手放したくありません。したがって、解決策は明らかです —— ヘッドレス CMS を使用し、同時にブログのフロントエンドを再設計することです。しかし再び問題が発生しました。市場にあるほとんどのヘッドレス CMS は少し重く、言い換えれば、私が解決しようとしている問題に対して、必要以上の機能を備えています。しかし、Typecho はヘッドレス CMS として使用できませんが、その規模は私のニーズをちょうど満たしています。
したがって、私は Typecho をヘッドレス CMS に変える方法を考えれば、すべての問題が解決するのです。
実践を始める#
私は既存のプラグインを簡単に見つけました。これにより、Typecho は RESTful API を提供できます。これにより、Typecho は純粋なバックエンドとして私が設計したフロントエンドにデータを提供でき、私は Typecho のコンソールでブログの内容を更新するだけで済みます。
次に、私はフロントエンドのデザインに重点を置くだけで済みます。
ツールの選択#
私は、フロントエンドを Vercel にホスティングすることに決めたので、私が慣れている Next.js を使用することにしました。Vercel の Next.js のサポートは明らかに優れています。
CSS-in-JS に関しては、最近人気のある Tailwind.css を選びました。自分で SCSS でクラスを手書きする代わりに、次のようにしました。一方で、新しいバージョンの Next.js はデフォルトで Tailwind.css をサポートしており、設定にかかる時間を省けます。もう一方で、React のモジュール開発のサポートにより、同じまたは類似の要素はコンポーネントとして書くことができ、CSS の観点から意味的にすることは少し不必要になります。この時、より便利で迅速な方法があるのは当然です。
ちなみに、私はしばらく Next.js を使用していませんでした。前のブログ(Isla)は Svelte を使用していました。新しいバージョンの Next.js では、新しいページルーティング方法である App Router が追加され、従来の Page Router と区別されています。本来、App Router を使用する方が良いのですが、再び取り組む私にはまだ反応がありませんでしたので、Page Router を使用してブログを書き続けました。しかし、動けばそれで良いのです。
React Icons のような追加ツールについては、特に言及する必要はありません。
記事の取得#
Next.js Page Router が提供する getStaticProps()
関数を使用して、ページが読み込まれる前にヘッドレス CMS からデータを取得できます。fetch()
を使用して API の内容を取得し、await
キーワードを使用することを忘れないでください。
プラグインが提供する RESTful スタイルの API は直接 json で解析できます。解析時にも await
キーワードを追加することを忘れないでください。
export async function getStaticProps() {
const res = await fetch('https://blog.guhub.cn/api/posts')
const posts = await res.json()
return { props: { posts } }
}
完了したら、記事リストデータを Props
としてメイン関数に返すだけです。
しかし、ここで私はバックエンドの問題に直面しました。このコードが正常に数十回実行された後、フロントエンドが最初の 5 つの記事しか表示しないことに気づきました。理由は、プラグインが API にページネーション機能を提供しており、デフォルトで 1 ページに 5 つの記事が必要で、URL クエリで ?page=
を使用して現在のページを指定する必要があるからです。しかし、私の現在のデザインではページネーション機能は必要ないため、API が提供する別の方法を使用して、1 ページあたりの表示記事数を増やしました。これは比較的愚かな解決策です。
const res = await fetch('https://blog.guhub.cn/api/posts?pageSize=9999')
記事の表示#
バックエンドから得たデータの中で、重要なデータは data.dataSet の下にあり、記事のタイトル、作成タイムスタンプ、CID、カテゴリ、スラッグなどが含まれています。特に注目すべきは digest
という属性で、これは Typecho の設定に関連しています。もし $this->content()
をホームページに完全に表示するように設定した場合、digest
には要約だけでなく、全文の HTML 文字列が含まれます。このプラグインは記事リストの API で全文の属性を特別に出力していないため、digest
が要約のみを出力する場合、スラッグや CID などのユニークな属性を使用して別のパスから詳細な記事情報を取得する必要があります。
これは明らかに面倒なので、私は Typecho の設定を変更せずに digest
を全文として使用することに決めました。しかし、私は依然として記事リストで本当の要約を出力する必要があるため、フロントエンドで要約の一部を切り取る必要があります。
私は次のように実装しました:
function stripDigest(digest) {
// 空行と空白を削除
digest = digest.replace(/\ +/g,"").replace(/[ ]/g,"").replace(/[\r\n]/g,"")
// タイトルを削除
digest = digest.replace( /<h.*?>(\S|\s)*?<\/h.*?>/g,"")
// 記事内容の中で <!--more--> タグを探す
// 存在する場合は、<!--more--> の前の内容を切り取る
// 存在しない場合は、最初の150文字を切り取る
var moreTag = digest.search(/<!--more-->/)
var sliceEnd = (moreTag>0) ? moreTag+2 : 150
// HTML タグを削除し、テキスト内容のみを保持し、切り取り操作を実行
digest = digest.slice(0,sliceEnd).replace(/<\/?[^>]+(>|$)/g, "") + "......"
return digest
}
要約は連続したテキストであるべきで、改行や空白がないため、まずこれらの空白を削除する必要があります。タイトルも削除するのが望ましいです。そして、よく知られている <!--more-->
タグを使って、要約を手動で切り取るためのものです。<!--more-->
タグがあれば、それを境界線として前のテキストを要約として切り取ります。もしなければ、最初の 150 文字を切り取ります。次に、HTML 文字列からタグを削除し、純粋なテキスト内容のみを保持する必要があります。
もしあなたが上記のコードを注意深く見ているなら、次のコードに疑問を抱くかもしれません:
var moreTag = digest.search(/<!--more-->/)
var sliceEnd = (moreTag>0) ? moreTag+2 : 150
ここで、変数 moreTag
は <!--more-->
の位置のインデックスを示します。存在する場合、インデックスは 0 より大きく、理論的にはそのインデックスを直接 slice()
メソッドに使用すべきですが、ここでは 2 を加えています。理由は —— この 2 を加えないと、切り取り位置が正しくないからです。
非常に古典的な問題で、なぜこのコードを書く必要があるのかはわかりませんが、書かないとプログラムが正常に動作しません。
加えた後も完全に正しいわけではありませんが、加えないと問題が大きくなります。私はなぜそうなのか理解できず、放置しました。今考えると、最良の処理方法は RESTful API の設計ロジックに従って、サーバーが提供する要約を直接取得することです。この問題は後で時間があるときに修正しましょう。
ページデザイン#
記事データを取得し、フロントエンドに表示できれば、ブログの基本機能は完成です。次はページデザインの番です。
以前のブログデザインのバージョンでは、私は意図的にシンプルさを追求していました(すでに使い古されたデザインスタイルです)。当時のページ構成は白い背景に黒い文字、同じくシンプルな黒い線のアイコン、淡い灰色の色塊で簡単に領域を区切ったものでした。
このようなデザインは、派手なウェブサイトやアプリの中で一筋の爽やかさを見つけることができましたが、問題は、この過度にシンプルなデザインは「キッチュ」に陥りやすいことです。より正確には、多くの装ったブロガーに広く認められているデザインスタイルに対してキッチュになってしまうのです。このスタイルは新しさと個性に欠けており、今思うと、私がブログのフロントエンドを覆して書き直したいと思った主な理由でもあります。
私は何が私にインスピレーションを与えたのか忘れてしまいましたが、数日間悩んだ後、新しいブログの外観デザインに新たな構想を持ちました。私はシンプルで洗練されているが、同時に特徴が際立ち、色彩が明確で、タイポグラフィが新しいデザインを望んでいます。いくつかの新聞の見出しやコラージュの要素を取り入れた後、まず Figma でコンセプト図を作成しました。
その後の実装プロセスでいくつかの調整を行い、グリッド手帳のページのような背景を追加し、徐々に現在の形になりました。
RSS 订阅#
このブログを読む人が少なく、ブログの更新も頻繁に行われない時代に、私に関心を持ってくれる読者に購読の手段を提供し、私が更新したときに読者に知らせることは重要です。
最初はこれが難しくないと思いました。なぜなら、Typecho 自体が RSS フィードを提供しているからです。しかし、問題が再び発生しました。私はバックエンド部分(Typecho)を blog.guhub.cn
というドメインに置き、フロントエンドは www.guhub.cn
に置いていますが、Typecho 自体はフロントエンドとバックエンドを分離するために設計されていないため、Typecho が提供するフィードのすべての記事リンクは blog.guhub.cn
というドメインを指し、現在使用している www.guhub.cn
ではありません。
私はフロントエンドで RSS の XML を取得し、すべての blog.guhub.cn
を www.guhub.cn
に置き換えればいいと思っていました。しかし、Next.js の設計者は、道を回り道する愚かな人がこのようなことをしようと考えていなかったのでしょう。XML データを直接処理することができず、ページコンテンツを直接取得する方法も見つかりませんでした。理論的には可能ですが、このステップで時間をかけたくなかったので……。
npm i rss
私は RSS ライブラリをインストールし、API から取得した記事データを使用して新しいフィードを生成しました。
export default async function generateRssFeed({ posts }) {
const feedOptions = {
//...
}
const feed = new RSS(feedOptions);
posts.map((item) => {
let post = parseBlogPost(item);
feed.item({
title: post.title,
description: post.content,
url: `${site_url}/blog/${post.slug}`,
date: post.date,
});
});
fs.writeFileSync('./public/feed/index.xml', feed.xml({ indent: true }));
}
これにより、Next.js は必要に応じて XML ファイルを生成し、RSS フィードとして提供します。今、もしあなたが望むなら、このリンクを使って私のブログを購読できます。
その他#
細かい実装手順についてはここでは詳しく述べません。私はカテゴリやタグページなどの機能をまだ作成しておらず、記事リストのデザインも少し簡素すぎます。これらは徐々に後で追加していく予定です。これらの機能を作るだけでも、しばらく忙しくなりそうですので、すぐに退屈してブログ全体を削除することはないでしょう。
また、このブログのフロントエンドが不快に感じる場合、特に現在はダークモードを実装していないため、Typecho 側で記事を読むことができます。そちらでは Matcha テーマを使用しており、私が作成したもので、機能が充実しており、読書体験は現在のブログよりもはるかに良いはずです。
ああ、そうだ、私はこのプロジェクトを Taco と呼んでいます。これは Typecho の中間の音素を一部取り除いた後に得られた単語です。
备案とウェブサイトの加速#
Typecho 側のサーバーは Tencent Cloud の国内サーバーを使用しているため、私はついに guhub.cn
というドメインを登録しました。しかし、主な理由は、CDN やオブジェクトストレージのようなサービスを使用してブログのアクセス速度を向上させるためです。
ICP 登録と公安登録の具体的な手順については詳しく述べません。CDN とオブジェクトストレージサービスには又拍云を使用しており、独立ブログのような低アクセス量のウェブサイトにはコストパフォーマンスが非常に高い選択肢です。半月以上使用しても、料金は 1 元未満です。
全国が緑色で心地よい感じです。
最近のブログの成果はおおよそこれです。
余談ですが、注意深い方は現在ブログにリンク集ページがないことに気づくかもしれません。これはできるだけ早く追加するつもりです。友達リンクの申請を再開し、あまり交流のない友達リンクを削除するつもりです。ブログの今後の発展についても初歩的な構想があります。これらの内容については、別の記事で詳しく書くかもしれませんので、ここでは詳しく説明しません。
さて、ここまで読んでいただきありがとうございます。あなたが楽しく過ごしていることを願っています。