💭

TanStack Start + Effect-TS​で、個人ブログを構築した

に公開

周回遅れ感がありますが、個人ブログを作りました。元々2017年からHugoのブログがあり、主にEmacsについて英語で書いていたのですが、2020年を最後に新しい記事を投稿しておらず(ネタ不足で自分でも記事の内容に不満があった)、その後、DNSをCloudflareに移行した際に設定の誤りで閲覧できない状態になっていたのを放置していました。関係者にはご迷惑をお掛けしました(日本語話者に関係者はいませんが)。

Hugoには、Go Templatesや後方互換性のないバージョンアップ(当時)等色々と不満がありましたので、今回はTypeScriptでスクラッチからブログシステムを構築しました。以下のようなスタックで構成されており、「今さらブログ?」という気持ちをこめて​LATE Stack​と名付けました。

  • L​ightning CSS
  • React A​ria
  • T​anStack Start
  • E​ffect

意図や技術選定の背景等については以下の記事で説明していますので、本記事では、日本人エンジニアの関心が比較的高そうな内容(実装)について少し掘り下げて書きたいと思います。

https://jingsi.space/posts/en/start-blog-late

※デザインセンスがないので、とりあえずlight modeとdark modeの背景色を火鍋のカーブで仕切ったものをデフォルトのOGP画像にしました。

Effect​でパイプラインを構築

今回はAstro等の静的フレームワークを使わず、TanStack Startでブログを構築しました。​GitHub Pages​や​Cloudflare Pages​のような静的ホスティングではなく、​Cloudflare Workers​のようなサーバレス環境(今回は​Deno Deploy​を使いました)で動かすことを想定しています。

TanStack StartはNext.jsと同様のアプリケーションフレームワークです。Astroと違ってMarkdownをHTMLに変換する仕組みはデフォルトでは備わっていません。そこで、​Astro Collections​のような仕組みを、​Effect-TS​で実装しました。初期アイデアは無料版​ChatGPT​と壁打ちしました。

Markdownからの変換には、​unified​を使っています。hast (HTML)のsyntax treeをJSONファイルに保存しサーバにデプロイ、実行時に​hast-util-to-jsx-runtime​を使ってReactでレンダリングしています。変換処理全体はEffectのlayer(サービス)として実装されており、Viteのプラグインに統合されています。Markdownを編集すると​vite dev​のプレビューにも反映されます。

コンテンツからリンクされている外部リソースのOGPメタデータも、ビルド時に取得することでUXを向上させています。独自実装のmdastプラグインにEffectのruntimeを渡して、プラグインからEffectのサービスを呼び出す仕組みになっており、複数のウェブページを並行で取得できます。私はこれをeffectful pluginと呼んでいます。以下のようにmdastのdirective形式でリンク先定義を挿入するか、

::link[https://react-spectrum.adobe.com/react-aria/components.html]

または以下のようにMarkdown標準のリンク先定義からもOGPのリンクカードに変換されるようになっています。

[nitro]: https://nitro.build/

図(diagrams)も、以下のようにソースコードをdirectiveで囲むことで、独自のmdastプラグインがEffectのサービスを呼び出してSVGに変換するようになっています。変換にはCLIが使えるので、Nixと組み合わせれば割とどんなフォーマットにも対応可能ですし(Mermaidが人気ですがここでは​d2​のCLIを使っています)、構文エラーもビルド時に検出できます。

:::diagram{.fit}

```d2
# This is an example of a diagram.
direction: right

Concept_A -> Is_Connected_To -> Concept_B
```

:::

AIコーディングの実践

システム側の実装については​Claude Code (Pro)​と、Geminiチャット(Gemini 2.5 Flash)を多用しました(開発時期の大部分が、Gemini CLIやAmazon Kiroがリリースされる前だったのもあります)。

Claude Codeはターミナルベースのアプリケーションですが、Emacsのターミナルエミュレータeat.elで動かしていました。私のEmacsの独自統合でorg-modeからプロンプトを送るか、短い場合は直接プロンプトを入力して使っています。これについては後ほど、今回構築したブログに書こうと思います(むしろこのためにブログを作った)。claude以外のモデルにやらせたいときはaider等も使いました。

まだ安全に運用するためのノウハウを蓄積できていないので、MCPサーバは最小限しか使わず、YOLOモードも使いませんでした。今のところは開発効率の向上よりも、成果物の品質について「高みに到達する」ためにAIが役立つという印象です。

CSS

今までブログの構築を検討するにあたって悩んでいたのはデザインでした。​3年前の伏線​で、当時のフロントエンド技術についてのトレンドは大雑把に把握していましたが、その後実務での(2Cプロダクトの)フロントエンド開発はしておらず、実践経験は欠如していました。

2025年、v0.dev等のプロトタイピングツールから始まって「AIによる開発タスクの遂行」が本格的に盛り上がっています。今回は、Claude Codeにvanilla CSSを書かせることで、個人的に満足の行くデザインのものができました。

具体的な流れとしては、まず最初に「​anthropic.com​っぽい背景色と​antfu.me​のようなレイアウトのCSS」をClaude Codeに生成させてから、微調整や機能追加をClaude Codeにやらせるという流れです。Claude Codeが最初に生成したdark modeの色がいまいちで、WCAG的に大丈夫かどうかGeminiに聞いてみたところ駄目だとのことだったので、配色についてはGeminiと相談しながら決めました。

Vanilla CSS + Lightning CSSを組み合わせることで最新のCSS機能も使えるし、Tailwind CSS等よりもeasyです。わざわざCSS Modulesを追加するほどでもない微調整については、Tailwindと同一のユーティリティクラスをAIに生成させてメインのCSSに追加したりしています。

mdastプラグイン

Markdownのsyntax treeを変換するmdastプラグインの実装でも、Claude Codeが活躍しました。プラグインでやりたいことを詳細に指示すると、かなり意図に近いものを出してくれます。mdast (unist)のプラグインAPIは、あまりわかりやすい印象ではないですし、人が全部書くよりはAIにやらせるほうが間違いなく効率がいいでしょう。

AIで英語を推敲

今回は5年ぶりに英語でブログ記事を書きました。私は正規の教育としては日本の学校しか卒業しておらず、卒業した大学も国際基督教大学(ICU)や国際教養大学のような英語教育が充実したところではなかったので※、英語運用力としては低いレベルにあります。どのくらいのレベル(作文力)かというと、2010年代の時点で、Grammarlyの無料版だと間違いが殆ど見つからないが、ネイティブにはあちこち文法的な誤りを指摘されるレベルでした。その後雑な英語を書くようになったので、現在はコロナ禍前よりも不正確になっていると思います。

※ICU卒の人でさえも、英語圏で現地就職するには英語力の壁を経験するようです。

https://ryoppippi.com/blog/2025-07-06-how-to-get-job-in-the-uk-ja

今回は自然言語が得意なLLMが使えるようになったので、以下のブログ記事を公開前にAIに推敲させました。

https://jingsi.space/posts/en/start-blog-late

最初に私が英語で下書きした後、Gemini CLIでGeminiに文法的な誤りやトーン等を直させましたが、文章が長すぎて失敗しました。aiderでGemini-2.5 Proに同じように遂行させましたが、英語のトーンは意図通りであるものの、内容をレビューすると最初の意図と違うことを言っているようです。

私の英語力不足でGeminiに意図が伝わっていないというだけではなく、以下のような用語の書き換えが発生しました。さすがにこれは致命的です。

  • Gemini 2.5 Flash → Gemini 1.5 Flash (?)
  • (OpenAI) o3 → GPT-4.1

Geminiは文章の意図を勝手に読み替えてしまいます。Gitのコミットをsquashしてしまったので元の文章が残っておらず、後悔しています。

https://x.com/fromdusktildawn/status/1943520226735497568

https://querie.me/answer/fy91QuiQpp8Wfev8Ql3e?timestamp=1753517761

Geminiに破壊されてしまった文章をレビューして、気になったところはChatGPTに相談しながら書き直しました。「別のAIに書き直させたこの文章、元々の意図は〜だったんだけど、ちょっと違うよね?」みたいな確認を繰り返して修正しました。改めて、噂に聞いていたOpenAIのモデルの理解力の高さ、およびChatGPT (GPT-4o)のパートナーとしての信頼度の高さを認識しました。以下のプロフィールも、ChatGPTに相談して言葉のニュアンスを調整しながら完成させました。

https://jingsi.space/about/en

https://x.com/super_bonochin/status/1949805270961635718

ChatGPTに毎回相談しながら文章を書いていくのは効率が悪いので、文章推敲ワークフローの改善とともに、人間が最初から正確な自然言語を出力できるような言語学習プログラムの導入・開発が課題として挙げられます。

※ChatGPTはGPT-5のリリースからkeep4oムーブメントによる反対を経てGPT-4oが復活しましたが、現在では「安全性重視」に振っているそうです。商用AIモデルも一期一会です。AIモデルとの出会いを大切にしましょう(?)。

https://x.com/ddtwin_0728/status/1955928827475189820

その他

開発の終盤で見つけて気が付きましたが、今回実装した内容(Deno Deploy、unified、OGPカード等)は、国内Nixで一躍、時の人(ときのひと)となったasa1984さんが2〜3年前にやったことの(意図せず結果として)後追いになっているようです。さすがasa1984さんです(?)。

https://asa1984.dev/blog/old-fresh

https://asa1984.dev/blog/first

今回構築したブログはまだ、記事単位のOGPイメージは入っていません。AIで画像生成したいと思いましたが、とりあえずウェブサイト自体をリリースすることを優先したため、画像生成は今後の課題です。サイト全体のfallback画像は、Claude CodeにSVGを生成させてからInkscapeで編集・エクスポートしました。以下のbell curve memeをOGPにしようかと思いましたが、縮小すると文字が読めないし、ふざけすぎなのでやめました。

org-publishは、EmacsのOrg modeからウェブサイトを生成する古い仕組みであり、たとえば​toyboot4eさんのブログ​などで使われています。記事を公開するだけならこれでも十分であり、それどころかPageSpeed Insightsでも(パフォーマンス改善の余地を残しているとはいえ)私のほうが負けています。

では今回のプロジェクトは無意味かというと、フロントエンド技術を理解するために必要な過程だという認識です。IQ 55からIQ 145に飛躍することはできず、一度はIQ 100を経由しなければいけません。下のモデルにおいて左を目指す過程で、IQ 100があるのでしょう。一度は左を目指しながらも、最終的に右に落ち着くことができた人は、幸せかもしれません。

ソースコード

https://github.com/akirak/late-stack

Discussion