# Blog Posts

## [XMLHttpRequestとFetch APIの機能差および移行における制約の整理](/blog/2026/05/31/xmlhttprequest-vs-fetch-api-comparison.md)

Web標準における非同期通信の主流はXMLHttpRequest (以下、XHR) からFetch APIへと移行しましたが、ユースケースによっては現在でもXHRを選択せざるを得ない領域が存在します。本稿では主要な3つの機能差 (キャンセル、ダウンロード進捗、アップロード進捗) について、最新の仕様および実用上の制約を踏まえて比較と解説します。

### 各機能の詳細と実装比較

#### リクエストのキャンセル

Fetch APIの登場当初はリクエストの中断手段がありませんでしたが、現在は**`AbortController`**の導入により、XHRの`xhr.abort()`と同等以上の柔軟な制御が可能です。

##### XHRによる実装

```javascript
const xhr = new XMLHttpRequest();  
xhr.open('GET', '/path/to/data');  
xhr.send();  
  
// 任意のタイミングで中断  
xhr.abort();
```

##### Fetch APIによる実装

```javascript
const controller = new AbortController();  
  
try {  
  const response = await fetch('/path/to/data', { signal: controller.signal });  
  const data = await response.json();  
} catch (error) {  
  if (error.name === 'AbortError') {  
    console.log('リクエストがキャンセルされました。');  
  }  
}  
  
// 任意のタイミングで中断  
controller.abort();
```

- **現状**: `AbortController`は主要ブラウザおよびNode.jsなどのサーバーサイド環境にも広く普及しており、完全にFetch APIへ移行可能です。

#### ダウンロード進捗の取得

レスポンスの受信進捗を表示する機能です。XHRの手軽さに比べ、Fetch APIはストリームを扱うため記述量が増えますが、非同期反復 (Async Iterable) のサポートにより実用的なコードで実装可能です。

##### XHRによる実装

```javascript
const xhr = new XMLHttpRequest();  
xhr.open('GET', '/path/to/large-file.json');  
xhr.onprogress = (event) => {  
  if (event.lengthComputable) {  
    const percent = (event.loaded / event.total) * 100;  
    console.log(`ダウンロード進捗: ${percent}%`);  
  }  
};  
xhr.send();
```

##### Fetch APIによる実装

```javascript
const response = await fetch('/path/to/large-file.json');  
const total = parseInt(response.headers.get('content-length') || '0', 10);  
let loaded = 0;  
  
const progressStream = new ReadableStream({  
  async start(controller) {  
    // response.body の非同期反復（for await...of）を利用してスマートに記述可能  
    for await (const chunk of response.body) {  
      loaded += chunk.byteLength;  
      const percent = total ? ((loaded / total) * 100).toFixed(1) : 0;  
      console.log(`ダウンロード進捗: ${percent}%`);  
        
      controller.enqueue(chunk);  
    }  
    controller.close();  
  }  
});  
  
const data = await new Response(progressStream).json();
```

- **現状**: 記述の簡潔さではXHRに劣りますが、Fetch APIの**`ReadableStream`**を用いることでサードパーティ製ライブラリを頼らずに完全な代替が可能です。

#### アップロード進捗の取得

大容量ファイルの送信時などに進捗バーを表示する機能です。Chromium系ブラウザを中心に**Fetch Upload Streaming (ボディにストリームを渡す仕様) **が追加されましたが、実務においては現在でもXHRから状況が変わっていない (XHRを使わざるを得ない) 最大の要因がここにあります。

##### XHRによる実装 (FormDataによる複数ファイル送信)

XHRでは`FormData`を使って複数ファイルやテキストパラメータを同時に送信しても、それらを含めたリクエスト全体の送信物理量をたった1つのイベントで正確に自動計測してくれます。

```javascript
const formData = new FormData();  
formData.append('userId', '12345');  
formData.append('files', fileA);  
formData.append('files', fileB);  
  
const xhr = new XMLHttpRequest();  
xhr.open('POST', '/upload');  
  
xhr.upload.onprogress = (event) => {  
  if (event.lengthComputable) {  
    const percent = (event.loaded / event.total) * 100;  
    console.log(`全体のアップロード進捗: ${percent.toFixed(1)}%`);  
  }  
};  
xhr.send(formData);
```

##### Fetch APIによる実装 (Fetch Upload Streaming)

Fetch APIで進捗を取得するには`body`に`ReadableStream`を直接渡し、JavaScript側でチャンクの消費量をカウントする必要があります。

```javascript
const total = file.size;  
let uploaded = 0;  
  
const uploadStream = new ReadableStream({  
  async start(controller) {  
    // file.stream() から for await...of でチャンクを取り出して監視  
    for await (const chunk of file.stream()) {  
      uploaded += chunk.byteLength;  
      console.log(`アップロード進捗: ${((uploaded / total) * 100).toFixed(1)}%`);  
        
      controller.enqueue(chunk);  
    }  
    controller.close();  
  }  
});  
  
await fetch('/upload', { method: 'POST', body: uploadStream, duplex: 'half' });
```

### Fetch APIによるアップロード進捗取得が「実用解」にならない4つの重大な制約

上記の通りFetch APIでも実装自体は可能となりましたが、プロダクション環境においてXHRを置き換えるのは極めて困難です。その理由は以下の通りです。

#### 1. FormData (複数ファイルやパラメータの同時送信) が一切使えない

Fetchで進捗を取るためには `body` に `ReadableStream` を直接指定しなければなりません。これは単一のバイナリを流すことを意味するため、ブラウザが境界線を自動生成してくれる `FormData` オブジェクトを組み合わせることが不可能です。  
もし複数のファイルやメタデータを同時にストリーミング送信したい場合、自前でマルチパート（`multipart/form-data`）の仕様に沿ったバイト配列（バウンダリ文字列やヘッダー文字列のバイナリ結合）を組み立てるストリームを自作するという、極めて非現実的な実装が必要になります。

#### 2. サーバー側とインフラ側の高い要求要件

フロントエンドだけでなく、バックエンド側にも以下の特殊な対応が必須となります。

- **HTTP/2 または HTTP/3 の接続維持**: HTTP/1.1環境では、ブラウザがFetchによるストリーム送信を拒否（制限）するため、インフラ（Nginx、ロードバランサー等）およびサーバーがHTTP/2以降に対応している必要があります。
- **アプリケーション側のストリーミング受信実装**: サーバー側のプログラムが、流れてくるチャンクを随時パースして消費するストリーム処理で書かれている必要があります。従来の「リクエストボディを一括でメモリや一時ファイルに読み込む」実装のままだと、ストリームが途中で詰まるか送信エラーになります。

#### 3. ブラウザ間の互換性問題

Chromium系 (Chrome, Edgeなど) 以外、特にSafariやFirefoxにおいては依然としてデフォルトでこのストリーミング機能が利用できないか実装に制限があり、クロスブラウザ環境での安定動作が保証されません。

#### 4. 計測される進捗の「信頼性」の乖離

- **XHR**: ブラウザのネットワーク層が、**実際にサーバーへ送信し終えた物理量** (TCPのACK受信ベース) を検知します。
- **Fetch**: JavaScriptが、**ブラウザの送信バッファにデータを引き渡した量**しか検知できません。そのため回線が細い環境では「JS上の表示は100% (バッファへの引き渡し完了) なのに、実際の送信はまだ終わっていない」という時間差のズレが生じます。

### 総合結論

- **リクエストのキャンセル・ダウンロード進捗**:Fetch API (`AbortController` / `ReadableStream`) で完全に実用的な代替が可能です。
- **アップロード進捗**:  
フロント・バックエンド双方に高度な技術スタックを要求し、かつ`FormData`も使えなくなるFetch Upload Streamingに比べ、XMLHttpRequestは「HTTP/1.1でも動作し」「サーバー側は従来通りの一括受信でよく」「FormDataで複雑なデータを詰め込んでも、あらゆるブラウザで実際の物理送信量を正確に測れる」という圧倒的な実用性を保ち続けています。

したがって「ファイルアップロードの進捗表示 (特に複数ファイルやテキストパラメータが混在するケース)」が必要なシナリオにおいては、現在でもXMLHttpRequest (あるいは内部でXHRをラップしているAxios等のライブラリ) を選択するのが技術的に正しい判断となります。

## [静電容量無接点方式キーボードを使い続ける理由と変遷](/blog/2026/03/25/why-i-keep-using-capacitive-keyboards.md)

静電容量無接点方式キーボードを使い続ける理由と変遷

日々のソフトウェア開発においてキーボードは最も触れる時間の長いインターフェースです。わたしは2000年代後半にHHKB Professional 2を購入して以来、2010年代にRealforce 87UB、そして2020年代にRealforce R3と、長年にわたり静電容量無接点方式のキーボードを使い続けています。

同じキースイッチの系譜を辿りながらも要求するレイアウトや接続方式は作業環境の変化とともに変わってきました。今回は実用性の観点からキーボードをどのように選択し、なぜ買い替えるに至ったのかという変遷についてまとめます。

### 60%キーボードの限界とテンキーレスへの移行

わたしが個人の入力デバイスとして静電容量無接点方式のスイッチを明確に意識して導入したのは、PFUのHHKB Professional 2でした。このキーボードには東プレからOEM供給されたスイッチが採用されています。東プレ製のスイッチ自体はATMなどのキーパッドにも広く採用されていて、実は多くの人が無意識のうちに日常的に触れている馴染み深いものです。

最近でこそゲーミングデバイス市場への逆輸入によって60%キーボードという呼称が一般化しましたが、当時はUNIX向けのコンパクトな特殊配列キーボードという限られた立ち位置でした。

HHKBの打鍵感自体は非常に優れていたものの、日常的なソフトウェア開発において使い勝手の悪さが次第に目立つようになりました。最大の課題は物理的な方向キーやファンクションキーが存在しない点です。

コードの記述中にコンテキストを行き来したりIDEのショートカットを多用したりする際、Fnキーとの同時押しによるレイヤー操作を強要されることは無視できない認知負荷となります。道具に人間の操作を最適化させる徹底した思想は理解できるものの、日々の生産性を最大化するという実用的な観点から見切りをつけ、方向キーと独立した機能キーを備えたテンキーレスモデルである東プレのRealforce 87UBへと移行しました。

### 接続規格の変遷と互換性の問題

Realforce 87UBは非常に堅牢なキーボードであり、10年近く使い続けても静電容量無接点方式の要であるキースイッチ自体には物理的な問題は一切発生していませんでした。しかしPCの環境が新しくなるにつれて、接続周拠で不都合が生じるようになりました。

具体的には新しめのWindows環境において、マザーボードのUSBポートに直結するとデバイスとして正常に認識されなくなってしまった点です。

87UBなどの当時のモデルは内部のコントローラーがUSB 1.1仕様で設計されています。近年のマザーボードはレガシーなEHCIコントローラーを廃止し、すべてのポートをxHCIコントローラーで統括しています。Windows標準のxHCIドライバ (usbxhci.sys) は起動時やスリープ復帰時に古いUSB 1.1デバイスの初期化タイミングを見失いやすく、結果としてOSがキーボードを認識できなくなるというハードウェアレベルの相性問題が生じていました。

間にUSBハブを挟むとハブ内部のTransaction Translatorが通信を仲介するため正常に認識されますが、周辺機器の接続において余計なレイヤーを挟まなければならない状態は精神衛生的にも運用上も好ましくありません。

### 複数デバイス環境への適応とRealforce R3の実運用

インターフェースの互換性に加えて接続方式の限界も買い替えを後押しする要因となりました。現在わたしのデスクにはMacやWindows PCなど複数の端末が混在していて、これらをシームレスに切り替えて操作するにはマルチペアリングに対応したBluetooth接続が必須となっていました。

結果として有線と無線のハイブリッド接続に対応したRealforce R3の導入に至りました。現在は専用のソフトウェアを用いてハードウェアレベルでキー割り当てを変更し、WindowsキーとAltキーをMacのOptionキーとCommandキーの並びにして運用しています。

ただしR3は無線キーボードであるものの、本体にかなりの重量があるため頻繁に動かすような使い方はしていなくて、無線の強みである取り回しの良さは実のところあまり活かせていません。単三乾電池の交換も煩わしいため、基本的にはUSBケーブルで給電しつつ接続先の切り替え機能としてのみBluetoothを利用するという運用に落ち着いています。

### 実運用における不満点

現状の運用で大きな支障はありませんが不満点がないわけではありません。

複数OSのデバイスを切り替えて使用する際、Bluetoothの接続先変更に連動してキーマップのプリセットも自動で切り替わってほしいという願望があります。現状は接続先のOSに合わせて手動でプリセットを変更するか、妥協したキーマップで運用する必要があるため、この点はファームウェアのアップデートや次期モデルでの改善を期待したいところです。

### 環境の変化に適応するインターフェース

キーボードのスイッチ自体は数十年使える寿命を持っていたとしても、それを接続するプロトコルや扱う側の作業環境は常に変化し続けています。

HHKB Professional 2から始まり配列の実用性を求めて87UBへ、そして接続の柔軟性を求めてRealforce R3へと移行してきた流れは、単なるガジェットの買い替えではなく、その時々の開発環境における課題を解消するための合理的な選択の結果です。現在のマルチデバイス環境において、Realforce R3は打鍵感と接続の利便性のバランスを満たす実用的な入力インターフェースとして機能してくれています。

## [プログラミング未経験者によるVibe Codingは現実的か](/blog/2026/03/16/vibe-coding-for-beginners-reality-check.md)

最近ある動画サイトで2つの異なる生成AIにブラウザ上で動く3Dゲームを作らせて比較するという検証動画を見かけました。

プロンプトだけでコードを書かせるいわゆるVibe Codingの能力を比較する試み自体は興味深かったのですが、その結論に至るまでのプロセスを見ていてプログラミングやWeb開発の知識を持たない層がAIを用いてコーディングを行うことの現実的な壁について考えさせられました。

### 実行環境による制約とフォールバック処理

その動画では最終的に片方のAIが出力したコードは動作が遅く使い物にならずもう一方のAIの方が優れていると結論づけていました。

しかし動作が遅いとされたAIのゲーム画面にはWebGLが利用できない旨を示す短いステータスが表示されていました。これはAIが出力したコードの質というよりも実行環境の問題に起因している可能性が高いです。

動画の投稿者はAIが出力したHTMLやJavaScriptのファイルを保存し、ローカルサーバーを立ち上げずにブラウザから直接ファイルを開いて実行していました。現代のWebブラウザはセキュリティモデルが厳しくローカルファイルを直接開いた場合、CORSの制限により外部モジュールの読み込みやWebGLを用いた描画処理がブラウザの仕様としてブロックされます。

画面にWebGLが使えない状態であることが表示されていたということは、そのAIが生成したコードは単にクラッシュしたわけではなく環境を検知してCanvas APIなどを用いた簡易的な描画処理にフォールバックしていたと考えられます。3Dの空間をソフトウェアレンダリングのような形で処理すれば当然ながら極端に動作は重くなります。

もう一方のAIはThree.jsなどのライブラリを利用したコードを出力していました。成熟した描画ライブラリは実行環境の制約を吸収したり致命的なエラーを回避して適切に処理を継続する仕組みを備えています。片方が明確にハードウェアアクセラレーションが効かないフォールバック状態で動いている状況でこれを単に生成されたコードのパフォーマンスの優劣として比較するのは早計に思えます。

### コード中のコメントやAIの補足文は読まれていたか

ここで疑問に思うのは動作が遅かったAIが本当にただ使い物にならないコードを出力しただけなのかという点です。

複雑なWebアプリケーションのコードを出力させた場合、生成AIは単にコードのブロックだけを返すわけではありません。多くの場合出力結果には「ローカルサーバーを立ち上げて実行してください」といったAIによる補足文が添えられていたりコード内に実行方法を示すコメントが記述されていたりします。

しかし動画の投稿者はそうしたコード中のコメントや生成AIによる補足文を読んでいないように見受けられました。Web開発の経験がないと出力されたテキストの意味を読み解こうとせず単にコードブロックだけを安直にコピーしてダブルクリックで開くという手軽な方法をとってしまいがちです。

実際の出力コードを確認していないため断定はできませんが、ブラウザのセキュリティ制約によって本来のパフォーマンスが出せていなかった可能性が高い状態のまま「そのAIは遅くて使い物にならない」という評価が下されてしまったように見えました。

ちなみにAIの出力したコードや補足説明を理解せずにそのまま使ってしまうというのは必ずしも未経験者だけに限った話ではありません。ソフトウェアエンジニアであってもコードの挙動を自身で説明できないままオープンソースプロジェクトにPull Requestを提出してしまうケースが増加していて問題視されるようになっています。

### Vibe Codingに求められる基礎リテラシー

プログラミングの知識がなくてもAIに指示を伝えるだけでアプリケーションを作れるVibe Codingが近年もてはやされており、ゼロからコードを書くハードルが劇的に下がったのは事実です。

しかし今回の動画の事例が示しているようにAIが出力したコードを実行し意図した動作にならない際にその原因を切り分ける能力は依然として求められます。

パフォーマンス低下の原因がAIの書いたロジックにあるのかそれとも自分の実行環境にあるのか。これを切り分けるWeb標準やブラウザの仕様に関する基礎知識がないとAIからの忠告を見落とし間違った結論に飛びついてしまいます。ツールがどれだけ進化しても出力された結果を正しく評価し運用するためのソフトウェア開発における基礎的なリテラシーはむしろ重要性を増しているのではないかと感じました。

## [Manael v3.0.0をリリースしました](/blog/2026/03/08/manael-v3-release.md)

表題の通り、画像変換プロキシサーバーである[Manael](https://manael.org/ja/) v3.0.0をリリースしました。Graceful Shutdownのサポートなど、モダンな運用に耐えうるアーキテクチャへの刷新をはじめ様々な変更が含まれていますが、一番大きな変更としては画像処理バックエンドのlibvipsへの移行でしょう。

### libvipsの導入によるパフォーマンスの向上

これまでのManaelではlibwebpやlibaomなどのCライブラリを個別にバインディングして画像処理を行っていましたが、v3.0からは高速で省メモリな画像処理ライブラリであるlibvipsに基盤を統合する形へと書き直しました。

単なるフォーマット変換だけでなく、後述するリサイズなどの処理を行う際にもピクセルデータをメモリ上で効率的に扱うことができるため、ピーク時のメモリ使用量が削減されて変換速度自体も向上しています。

### 画像のリサイズと画質の動的制御

新しい機能としてクエリパラメータを用いた画像のリサイズと画質の動的制御に対応しました。

URLに`?w=300&h=300`といったパラメータを付与することで任意のサイズに縮小できるほか、`fit`パラメータを用いてアスペクト比の維持やクロップの挙動を制御することが可能です。

また、圧縮品質については`?q=80`のように全体的な数値を指定できるだけでなく、`?q=avif:60,webp:80`のように配信されるフォーマットごとに最適な品質を細かくコントロールできる設計としています。

ただしプロキシサーバーにおける画像のリサイズは展開時にCPUとメモリを激しく消費する処理でもあります。悪意のあるユーザーが巨大なサイズ指定を大量に送りつけてきた場合、すぐにメモリを食いつぶしてOOMを引き起こす懸念があります。

プロキシとしての安全性と軽量さを保つため、このリサイズ機能はデフォルトで無効化するオプトイン方式を採用しました。環境変数`MANAEL_ENABLE_RESIZE=true`を設定した場合のみ有効化され、変換可能な最大ピクセル数にも上限を設けるセーフガードを実装することで、DoS攻撃のリスクを排して安全に運用できるようにしています。

### 複数のAIを活用した開発プロセスへの移行

今回のv3.0.0アップデートでは、複数のAIを適材適所で組み合わせた開発プロセスを導入しています。具体的にはIssueの作成にGemini、実装にCopilot Coding Agent、そしてコードレビューにCodeRabbitを利用するという役割分担を行いました。

Issueの作成にGeminiを採用したのはコンテキストを解釈してビジネスライクな文章やドキュメント、要件定義を書き起こす能力に長けていると感じたためです。他方で純粋なコード生成においては不向きな側面があるように見受けられたため、実装フェーズについてはCopilot Coding Agentへと委譲しています。

Copilot Coding AgentはGitHubのUI上から容易に実行をトリガーできることに加え、実行環境の定義もGitHub Actionsと同様の記法で簡潔に記述できるという、インフラストラクチャおよびワークフロー上の明確なメリットがありました。こうしたツール群を開発フローに組み込むことによって生じるプロセス上の変化についても、非常に興味深い知見が得られたと感じています。

### おわりに

v3.0.0への移行に伴う環境変数の設定や新機能の具体的な使い方については新しく階層化して見やすくなった[ドキュメント](https://manael.org/ja/)および[リリース告知記事](https://manael.org/ja/blog/2026-03-07-manael-v3-release/)に改めて整理し、備忘録として残しておこうと思います。

より実用的で堅牢なプロキシサーバーへと進化したManaelをぜひプロジェクトで活用してみてください。引き続き改善を続けていこうと思います。

## [RØDECaster Pro IIとKVMモニターで作る複数PCのオーディオ環境](/blog/2026/03/05/rodecaster-pro-ii-multi-pc-audio-setup.md)

私のデスクには用途の異なる複数の端末が混在しています。日常のソフトウェア開発やブラウジングに使うMac miniやゲーム用のWindows PC、仕事用のMacBook Pro、そして動画などを流すためのiPad Airです。

在宅で業務を行うソフトウェアエンジニアという性質上、日々のミーティングでマイクやカメラの利用は必須です。複数のデバイスを使っていると直面するのが音声の入出力やデバイス切り替えをどうするかという問題です。

このルーティングの課題を解決するためにさまざまなオーディオインターフェースやミキサーを検討しました。配信やDTMを行っているわけではありませんが自分のニッチな要件を満たす機材を探した結果、ポッドキャスト向けのミキサーであるRØDECaster Pro IIに行き着き、導入から1年ほどが経過しました。今回は複数PC環境におけるオーディオ周りの構成と非配信者目線でのRØDECaster Pro IIの所感についてまとめます。

### デバイス構成と接続ルーティング

現在の接続を図にすると以下のようになります。EIZOのモニターであるFlexScan EV2740XのKVM機能をハブにしてRØDECaster Pro IIへ音声を流す構成です。

![RØDECaster Pro IIを中心とした配線図](https://muxluenhrgvecahpnbre.supabase.co/storage/v1/object/public/images/a0c762de-f86d-40a1-8fd3-72ae2e80ef5f/82bf6e35-7c61-48cf-b0df-a27456456c70.png)

KVM機能を持つEV2740Xを挟むことでモニターの入力を切り替えると、それに紐づいてRØDECaster Pro IIのUSB 1とWebカメラのElgato Facecamの接続先が仕事用のMacBook ProとMac miniの間で自動的に切り替わります。仕事用のMacBook Proに関してはUSB-Cケーブル1本の接続で全てが完結するため着脱が非常に容易になっています。

なおカメラはWindows PCからは使えない割り切った構成にしています。

### この構成のメリット

配信機材をあえて日常用途に使うメリットは以下の点に集約されます。

1. **複数ソースの音声を同時にミックスできる**  
KVM経由で繋がっているMac miniや仕事用のMacBook Pro、直接繋がっているWindows PC、そしてBluetooth接続のiPad Airといったすべての音声を1組のスピーカーやヘッドフォンから同時に出力できます。作業しながら別端末で動画を流す際などに便利です。
2. **物理フェーダーでの直感的な音量調整**  
各デバイスの音量バランスをPCの画面を操作することなく手元のフェーダーで瞬時に調整できます。
3. **SM7Bをプリアンプなしで直接駆動できる**  
マイクにはSHUREのSM7Bを使用しています。このマイクは出力レベルが低く本来であればCloudlifterなどのインラインマイクプリアンプを別途用意する必要があります。しかしRØDECaster Pro IIは強力なプリアンプを内蔵しているためそのまま直接接続して十分なゲインを得ることができています。これはハイエンド機ならではの恩恵です。

### 改善してほしい点・不満点

とはいえ約10万円と決して安い機材ではなく用途に対してオーバースペックであることは否めません。デュアルUSB-C入力を備えた機器は他にも存在するため手放しで万人に勧められるわけではありません。個人的にも以下のような不満や改善要望があります。

- **USB-C入力がもっと欲しい**  
現状の市場にあるミキサーは他社製品を含めてもUSB-C入力が2系統で頭打ちになっています。私の環境ではMac miniや仕事用のMacBook ProとWindows PCに加えてiPad Airもあるため、理想を言えばUSB-C入力が4つ以上ある機器が欲しいところです。現状は入力端子が足りないためKVMを挟んでMac 2台を1系統にまとめ、iPad Airは有線を諦めてBluetoothで接続するという妥協をしています。すべてのデバイスを直接有線で接続できればよりシンプルで安定したルーティングが可能になるはずです。
- **Bluetoothの入出力仕様**  
Bluetoothの入出力が排他仕様になっており取り回しに制限があります。

### おわりに

RØDECaster Pro IIはフェーダー操作やBluetooth入力、そして何よりSM7Bを単体で鳴らせるプリアンプといった特定の条件を満たしたため採用に至りましたが不満点がないわけではありません。ただ現状の複数PCをKVMで切り替えつつ別PCやタブレットの音も同時にミックスし、高音質なマイクでミーティングをこなすというニッチな要件においてはうまくシステムにハマってくれています。

## [GitHub CodespacesとAIエージェントで作る快適なクラウド開発環境](/blog/2026/03/02/github-codespaces-ai-agent.md)

日々の開発において複数のプロジェクトでコンテナ (Docker) を立ち上げたり、重いプロセスをローカルで動かしたりすることはマシンのリソースを圧迫して環境を汚す原因になりがちです。

そこでここ数年の開発では、ローカル環境を極力汚さず、負荷もかけないようにGitHub Codespaces (Dev Containers) をフル活用するようになりました。各リポジトリに`.devcontainer/devcontainer.json`を用意してクラウド上で開発を行うスタイルです。

最初は単なるマシンスペックの補完や環境のクリーン化として始めたこの運用ですが「AIエージェント」を開発フローに組み込むようになった現在これが思わぬ相乗効果を生んでいるので実際の構成やメリットとデメリットについて少し書いてみようと思います。

### Dev Containersのシンプルな構成と具体例

リポジトリ直下に`.devcontainer/devcontainer.json`を配置するだけで、どこからでも同一の開発環境が立ち上がるのは非常に強力です。

Docker Composeを用いて複雑なコンテナ群を立ち上げるような使い方も可能ですが、基本的には公式の軽量なイメージをベースにしつつ、シンプルな構成に留めておくのが起動も早く扱いやすいと感じています。

例えばTypeScript (Node.js) 環境でAIエージェント (GitHub Copilot等) を併用する場合、以下のような最小限の`devcontainer.json`を用意しています。

```json
{  
  "name": "Node.js & TypeScript Environment",  
  "image": "mcr.microsoft.com/devcontainers/typescript-node:1-24-bullseye",  
    
  "customizations": {  
    "vscode": {  
      "extensions": [  
        "dbaeumer.vscode-eslint",  
        "esbenp.prettier-vscode",  
        "github.copilot",  
        "github.copilot-chat"  
      ]  
    }  
  },  
  
  // コンテナ作成後に自動で依存関係をインストール  
  "postCreateCommand": "npm install",  
    
  // 開発サーバー用によく使うポートをフォワード  
  "forwardPorts": [3000]  
}
```

このように記述しておけば`postCreateCommand`によって依存関係のインストールが自動化されるだけでなく、VS Codeの拡張機能 (リンターやAIアシスタント) や設定も強制的に統一されます。新しい環境を立ち上げた瞬間から普段通りのエディタ体験が保証されるわけです。

### AIエージェントを動かす安全なサンドボックスとして

最近はIssueの要件定義から実装、Pull Requestの作成までをAIエージェント (CodeRabbitやGitHub Copilotなど) に委ねる開発プロセスを取り入れています。

エージェントに自律的にコードを書かせたり、コマンドを実行させたりする際、ローカル環境で直接それを行わせるのは環境の破壊や意図しないファイルの変更といったセキュリティや運用上のリスクを伴います。

その点、Dev Containersで構築されたCodespacesは完全に独立した使い捨てのコンテナ環境です。万が一AIが破壊的な操作を行ってしまってもコンテナを破棄して新しく立ち上げ直すだけで済むため、非常に安全なサンドボックスとして機能してくれます。

### 複数環境の並行稼働とポートフォワーディングの柔軟性

Codespacesを使うもう一つの大きなメリットは「使い捨ての環境を気軽に複数立ち上げられること」です。

AIエージェントに重い実装やデバッグを任せている間、人間はただ待っている必要はありません。AIが特定のブランチで作業 (思考と生成) を行っている間に、別のCodespaceセッションを立ち上げて別タスクのレビューや実装を並行して進めることができます。

またそれぞれの環境で開発サーバーを立ち上げた際もポートフォワーディングの設定を柔軟に切り替えることで、動作確認 (プレビュー) のコンテキストが混ざることなく容易に行えます。

- **環境A:** AIエージェントが機能追加の実装とテストを実行中
- **環境B:** 自分が並行して別のIssueを修正・プレビュー中

といったマルチタスクがローカルマシンのリソース (メモリやCPU) を一切消費せずに実現できるわけです。

### リージョンに起因するレイテンシ

ここまで利点を挙げてきましたが運用する上で明確なデメリットも存在します。それはレイテンシ (通信遅延) です。

現在のところ、一般的なGitHub Codespacesの環境には日本のリージョンが存在しません。日本から接続する場合、一番近いリージョンでも東南アジア (シンガポール) に割り当てられるため、どうしても一定のネットワークレイテンシが発生してしまいます。

タイピングの反映やターミナルの操作において、ネイティブのローカル環境と比べるとわずかな遅延を感じることがあり、ネットワーク環境が不安定な場所ではストレスに繋がる可能性があります。

### おわりに

多少のレイテンシというトレードオフはあるものの、この構成において手元のマシンは「クラウド上の開発環境とAIエージェントにアクセスするための、非常に快適なシンクライアント」として機能してくれています。

AIがコードを読み書きする時代において、人間の開発者が用意すべきなのは「強力なローカルマシン」ではなく、「AIが安全かつ効率的に働けるクラウド上のワークスペース」と「共通のコンテキストを定義するドキュメント (`AGENTS.md`など)」へと変化しつつあるのかもしれません。

ローカル環境の運用に煩わしさを感じている方やAIエージェントの導入に踏み切れないでいる方は一度Dev Containersベースのクラウド開発環境を試してみてはいかがでしょうか。

## [Next.jsのApp Routerとv16におけるキャッシュ戦略](/blog/2026/02/27/nextjs-app-router-cache-strategy-v16.md)

Next.jsについてApp Routerが導入された必然性からv16におけるキャッシュ戦略の転換、そしてインフラストラクチャにおけるVercel依存 (ロックイン) の誤解について改めて整理し、備忘録として残しておこうと思います。

### Next.jsの進化の変遷とApp Routerのコア概念

2016年の初期リリース時から今日に至るまで、Next.jsは単なるSSR (Server Side Rendering) のラッパーツールから「WebのOS」とも呼べるような包括的なフレームワークへと進化を遂げています。

初期 (v1〜) はファイルを配置すればHTMLが生成されるシンプルなものでしたが、動的ルーティングやAPI連携を行うには前段にExpressなどのカスタムサーバーを配置する必要がありました。その後、中期 (v9〜) において`getStaticProps`など導入によるSSG (静的サイト生成) やAPI Routesがサポートされ、BFF (Backend For Frontend) としての機能を内包するようになります。

そして現在 (v13以降) のApp RouterではReact Server Components (RSC) を前提としたアーキテクチャへと移行しました。

- **React Server Components (RSC)**: デフォルトでコンポーネントはサーバー側でレンダリングされ、クライアントにJavaScriptを送信しません。ブラウザでの実行が必要な場合のみ`'use client'`ディレクティブを付与します。
- **Server Actions**: 従来のAPIエンドポイントを用意する方式とは異なり、フォームから直接サーバー側の関数を呼び出すことが可能になりました。

```typescript
// フォームから直接DBを叩く関数を呼べる  
async function create(formData: FormData) {  
  'use server'  
  
  await db.user.create({ name: formData.get('name') })  
}
```

### キャッシュ戦略の転換: v14の暗黙的マジックからv16の明示的制御へ

Next.jsにおける課題の一つとして、キャッシュの制御の難しさが挙げられていました。v14以前では、`fetch` リクエストはデフォルトで自動的にキャッシュされる（`force-cache`）仕様となっており、これが「データが更新されない」といったバグの温床になりやすいという問題がありました。バックエンドエンジニアの視点からも、どこでキャッシュが保持されているのかが不明瞭になりがちでした。

v15で`fetch`のデフォルト挙動が`no-store` (毎リクエスト再取得) に変更され、暗黙的なキャッシュによる混乱は一定の軽減を見せました。しかしルート設定との組み合わせは依然として複雑でした。

そこからv16で`'use cache'`ディレクティブを用いた明示的な制御へと回帰しています。これはRuby on Railsにおけるフラグメントキャッシュに近いアプローチであり、ページ全体ではなく関数やコンポーネント単位でキャッシュしたい箇所だけを明示的に指定する設計です。

#### v16で導入された3層のキャッシュディレクティブ

v16では以下の3つのディレクティブを使い分けることでデータの性質に応じた柔軟なキャッシュ戦略を実現しています。

**1. 静的キャッシュ (`'use cache'`)**  
全ユーザーで共有され、主にビルド時に生成される静的データ (商品情報、マスターデータなど) に用います。

```typescript
async function getProduct(id) {  
  'use cache'  
  
  cacheTag(`product-${id}`)  
  
  return db.products.find(id)  
}
```

**2. リモートキャッシュ (`'use cache: remote'`)**  
全ユーザーで共有されますが、ランタイムで外部ストア (Redisなど) に保持される変動データ (価格情報、在庫など) に用います。`cacheLife`を用いてTTL (有効期間) を設定します。

```typescript
async function getPrice(id) {  
  'use cache: remote'  
  
  cacheTag(`price-${id}`)  
  cacheLife({ expire: 300 }) // 5分保持  
  
  return db.prices.get(id)  
}
```

**3. プライベートキャッシュ (`'use cache: private'`)**  
ユーザー固有のデータ (推奨商品、個人設定など) に用います。このディレクティブ内でのみ `cookies()` の読み込みが許可されます。

```typescript
async function getRecommendations(id) {  
  'use cache: private'  
  
  cacheLife({ expire: 60 })  
  
  const sessionId = (await cookies()).get('session-id')?.value  
  return db.recommendations.find({ productId: id, sessionId })  
}
```

これらのディレクティブに加え、`cacheTag()`によるタグ付けと`revalidateTag()`によるオンデマンドな無効化を組み合わせることで開発者が意図した通りのキャッシュライフサイクルを構築できるようになっています。

### Turbopackによる開発体験の向上

開発ツールチェーンの面ではWebpackの後継として開発されたRust製のバンドラー「Turbopack」がv16でデフォルト有効化されました (v15でdev環境がstable化、v16でbuild環境がstable化)。

並列処理とキャッシュの最適化により、開発サーバーの起動時間やHMR (Hot Module Replacement) による更新反映が大幅に高速化されています。大規模なアプリケーション開発においてもコンパイルの待ち時間による思考の断絶が減り、快適な開発体験が提供されています。

### インフラ非依存性（Vercelロックインの誤解）

Next.jsを採用するにあたり、「Vercelにロックインされるのではないか」という懸念を耳にすることがありますが、実際には標準的なNode.jsランタイムが動作する環境であればデプロイは可能です。

- **Docker Support**: `next.config.js`に`output: 'standalone'`を設定することで依存関係を内包した極小のイメージを作成でき、AWS ECSやCloud Runなど任意のコンテナ環境で稼働させることができます。
- **Custom Cache Handlers**: Next.js内部のキャッシュの保存先をデフォルトのファイルシステムからRedisやS3などの外部ストレージに変更するインターフェースが提供されています。これにより、Vercel以外のインフラ (ElastiCacheなど) を利用した場合でも、複数インスタンス間でのISR (Incremental Static Regeneration) の整合性を維持できます。
- **Custom Adapters**: Cloudflare WorkersやAWS Lambda等の異なるランタイムへの変換レイヤーをオープン化する取り組み (RFC段階) も進められています。

### まとめ

App Routerの導入によりバックエンドとフロントエンドの境界が再定義され、v16における`'use cache'`の導入によって、キャッシュはブラックボックスな魔法から「エンジニアが明確に制御可能な道具」へと洗練されました。

既存のインフラ資産 (DockerやAWS) を活かしながらこれらのアーキテクチャの恩恵を受けられる点は、Next.jsを技術選定のテーブルに乗せる十分な意義があると考えています。

## [生成AIを活用したソフトウェア開発における技術的課題と対策](/blog/2026/02/25/generative-ai-and-oss-licenses.md)

昨今のソフトウェア開発において、大規模言語モデル (LLM) を組み込んだコーディング支援技術や、自律的にタスクを処理するAIエージェントの導入が進行している。本稿では、これらの技術を開発フローに組み込むことによって生じるプロセス上の変化と、オープンソースソフトウェア (OSS) のライセンスに関わる技術的な課題について整理する。

### 開発プロセスの構造的変化

開発環境にAIエージェントを統合した場合、従来人間が行っていた工程の一部が自動化される。具体的には、自然言語による抽象的な要件を入力とし、そこから実装仕様の補完、ソースコードの生成、そして初期段階のコードレビューまでを一貫してシステム側に処理させる構成を取ることが可能となっている。

この構成においては、ソフトウェアエンジニアの主要な役割はコードの記述そのものから、出力されたコードの妥当性の検証、セキュリティ要件の確認、およびシステム全体のアーキテクチャ設計といった意思決定の領域へと移行する。実装の大部分を機械的に生成させることで開発速度の向上は見込めるものの、出力結果に対する最終的な品質保証のプロセスは依然として人間に依存している。

### OSSライセンスの形骸化リスク

生成AIを開発に導入する上で無視できない技術的課題として、OSSライセンスの取り扱いが挙げられる。

多くのLLMは、インターネット上に公開されている膨大なソースコードを学習データセットとして利用している。そのため、モデルの出力するコードの中に、GPLに代表されるコピーレフトライセンスのコードや、適切な著作権表示を要求するライセンスのコードが、出所が不明瞭な状態で混入する可能性が存在する。

こうした事象は、OSSコミュニティにおいてライセンス要件の形骸化として危惧されている。開発者が意図せずにライセンス違反を引き起こすリスクがあり、商用利用を前提とするソフトウェア開発においてはシステムに対する信頼性を損なう懸念事項となっている。

### リアルタイム照合による技術的対策

前述のライセンスリスクに対するシステム側の対策として、コード生成時に実行されるパブリックコードとの照合機能が考案され、一部の環境で実装されている。

これは、AIが提案を出力する直前に、あらかじめ構築された公開コードのインデックスとリアルタイムに比較を行う仕組みである。提案コードが既存の公開コードと一定の閾値を超えて一致した場合、システムはその出力を意図的にブロックするか、あるいは該当するコードの引用元URIおよび適用されているライセンス情報をメタデータとして付与して開発者に提示する。

ただし、この照合機能はすべてのコーディング支援環境に標準搭載されているわけではない。モデル自体の学習手法によって出力の安全性を担保しようとする設計思想のものや、リアルタイムの外部照合インターフェースを持たないものも存在する。

### まとめ

AIを用いたコード生成技術は開発効率を向上させる一方で、コードの出所管理やライセンスの遵守といった既存の課題を別の形で顕在化させている。開発環境にこれらの技術を導入する際は、利用するツールの内部仕様や制限事項を技術的に把握し、自動化される領域と人間が検証すべき領域を明確に切り分けた運用フローの設計が必要である。

## [開発プロセスの移行とサイト統合](/blog/2026/02/21/migration-to-nextjs.md)

これまでブログの運用に用いてきた[Docusaurus](https://docusaurus.io/)から、[Next.js](https://nextjs.org/)と[Supabase](https://supabase.com/)を組み合わせた構成へと刷新しました。これに併せてSEOの観点およびポートフォリオとブログの権威性を集約するためにドメインを`ykzts.com`配下へと統合しています。

### **Docusaurusからの移行**

従来のDocusaurusによる運用では以下の制約や管理上の懸念がありました。

- **執筆環境の制約**: 記事の執筆にMarkdownを用いる必要があること
- **運用上の制約**: 標準機能での予約投稿が困難であること
- **秘匿性の欠如**: ファイルベースで管理する性質上、公開前の記事であってもPull Requestなどのプロセスを通じて本文が露出してしまうこと

これらの課題を解消するため、データベースをバックエンドに持つ動的なシステム構成へと移行しました。

### **サイト構成とドメイン統合**

本プロジェクトでは複数のアプリケーションを一つのリポジトリで管理するモノレポ構成をとっていますが、今回の刷新に合わせてURLの統合も行いました。

複数のアプリケーションを同一のURL配下で扱うマイクロフロントエンド構成を採用したことで、ポートフォリオとブログを`ykzts.com`という一つの起点からシームレスに提供しています。これによってユーザー体験の向上とドメイン評価の集約を図っています。

### **AI エージェントを活用した開発フロー**

本プロジェクトでは、リポジトリ内に定義した`AGENTS.md`に基づき、人間とAIエージェントが共通のコンテキストを共有して開発を進める体制を構築しています。

具体的な開発手順は以下の通りです。

- **Issue の具体化**: 人間がタイトルのみのIssueを起票し、[CodeRabbit](https://www.coderabbit.ai/ja)のAgent Chat機能を用いて実装要件やタスクリストをIssue内に補完
- **実装の自動生成**: 補完されたIssueをソースとして、[GitHub Copilot](https://github.com/features/copilot)のCoding AgentがPull Requestを直接作成
- **多層的なレビュー**: 作成されたコードに対し、まずCodeRabbitが自動レビューを実施。その結果を踏まえ、最終的に人間がコードの妥当性やセキュリティ (機密情報の漏洩や脆弱性の侵入防止) を検証し、マージを判断

人間は「何を作るか」という意思決定と安全性の担保に注力し、実装およびその整合性の確認作業の多くをエージェントに委ねる形をとっています。

### **データベース設計**

バックエンドの構築においてはSupabaseをベースとしたスキーマ設計をすべてコードベースで管理しています。記事データは`posts`テーブルで管理していますが、直接の操作を制限してPL/pgSQLで定義した関数を介してのみ書き込みを行う設計としました。これによりpost_versionsテーブルへ変更履歴を不変に保存する仕組みを強制しています。

また`pgvector`を導入したデータベースレベルでの検索機能を実装しました。後述する[AI SDK](https://ai-sdk.dev/)を活用した関連記事の自動抽出などはこの設計によって支えられています。

### **ローカル開発環境の管理**

開発の効率化のため、`config.toml`や`seed.sql`を整備し、ローカル開発環境の再現性を確保しています。これにより、本番環境と同等のスキーマや初期データを手元の環境で即座に実行することができ、開発サイクルの高速化を図っています。

### **AI SDKとVercel AI Gatewayによる運用の自動化**

本システムではAI SDKを通じて[Vercel AI Gateway](https://vercel.com/ai-gateway)を活用し、各モデルを透過的に利用する構成をとっています。これは開発時のみならず、実際のサイト運用における各機能の自動化に寄与しています。

管理画面にはAI SDKを用いて日本語タイトルと本文から英語のslugを自動生成する機能を実装しました。ここでは既存データとの重複チェックといった処理もツールコールによって自動化されています。また関連記事抽出のトリガーとなるベクトルデータの生成・管理においても AI SDK を活用しています。

## [PlayStation 5を購入しました](/blog/2022/11/03/bought-ps5.md)

ゲームは基本的にWindows PCでプレイしているのに加えて購入しづらいという話を度々耳にしていたのでPlayStation 5 (PS5) は購入するつもりがあまりなかったのですが2023年夏に発売予定のFINAL FANTASY XVI (FF16) がPS5以外はサポートしないそうなのでPS5を購入しました。

前述の通りFF16が発売される予定の来年の夏までに手に入れられていれば良かったので時間がかかっても良いので招待販売という形を取っていて招待のリクエストを一度すれば抽選が開始される度に登録等の作業をしなくても済むAmazonを使わせていただきました。

ただ今年9月にリクエストをして翌10月に購入権が得られるとは思っておらず購入しづらいという噂との乖離におどろきました。ちょうど新しいモデルであるCFI-1200の提供が開始されたばかりでかつAmazonでも旧モデルであるCFI-1100も並行して招待のリクエストを受け付けていたタイミングだったので招待のリクエストをしていた人が分散されていたおかげなのではないかと思います。おそらく運が良かったのでしょうね。

PS5自体は10月に手に入れられていたのですが1週間も経たない内にレストモード中に行われたと思われる始めてのアップデートの後から起動しなくなってしまい修理に出していました。わたしのメールボックスを「PlayStation 5」で検索するとAmazonの発送通知メールの直上に修理受け付けのメールが並んでいてなかなかに趣きがあります。

わたしはこれまでの人生でもう何度も電子機器の購入をしているのですがこうした初期不良に遭遇したのが始めてなので少しテンションが上がりましたが修理の依頼をしたり送付をするのは少し手間でした。幸いにもあまり待つことなく数日で新品に交換されて返ってきたので何よりでした。

修理から返ってくるのを待っている間に少し物欲が湧いてしまったので加えて液晶TVとサウンドバーも購入しました。もともと購入を検討していたのに加えてせっかくPS5がUltra HD Blu-rayに対応しているのだから……と理由づけていますがただの言いわけでしかありませんね。

おおよそ10年振りにTVを買ったのですが大型のものでもかなり安くなっていてありがたい限りですね。またスピーカーもバーチャルサラウンドのサウンドバーが主流になりつつあるようで省スペースに配置できるようなっているのも良かったです。サブウーファーも無線で繋げられるようになっていて配線に苦労せずに済むのも助かりました。

とはいえPS5を購入したもののRTX 3080 Tiを載せたWindows PCを使っているということもあってPS5でゲームを遊ぶ機会はだいぶ稀になってしまうと考えています。今はPlaySation Plusのプレミアムプランに加入していると利用できるクラシックスカタログでPS3版のWHITE ALBUM2を遊んでいます。

PS3版のWHITE ALBUM2は発売された2012年に購入してトロフィーも翌2013年にコンプリートしているどころか2010年と2011年に発売されたオリジナル版も2018年に発売されたEXTENDED EDITIONも繰り返しプレイしているのですが好きな作品なので何度遊んでも楽しいですね。冬の間にクリアできるようにゆっくりと楽しむ予定です。

