<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Seu Dev Notes</title><description>雑多な備忘録の墓場</description><link>https://blog.c12o.net/</link><language>ja</language><item><title>f-string の書式指定まとめ（桁揃え・ゼロ埋め・日付）</title><link>https://blog.c12o.net/posts/fstring-format-spec/</link><guid isPermaLink="true">https://blog.c12o.net/posts/fstring-format-spec/</guid><description>f-string の : 以降に書く書式ミニ言語を、桁揃え・ゼロ埋め・カンマ区切り・小数の桁数・日付という実用 5 パターンに絞ってまとめました。並び順は埋め文字 → 寄せ → 幅 → 区切り → 精度 → 型で、これさえ覚えれば毎回調べ直す必要はなくなります。</description><pubDate>Mon, 03 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;f-string で値を埋め込むだけなら &lt;code&gt;f&quot;{name}&quot;&lt;/code&gt; で済みます。困るのは &lt;code&gt;:&lt;/code&gt; から先です。桁を揃えたい、ゼロ埋めしたい、カンマを入れたい。そのたびにドキュメントを開き直している気がしたので、実際に使う 5 パターンに絞って手元にまとめました。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;:&lt;/code&gt; の後ろは &lt;a href=&quot;https://docs.python.org/ja/3/library/string.html#format-specification-mini-language&quot;&gt;書式指定ミニ言語&lt;/a&gt; と呼ばれる独自の文法で、並びはこうなっています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{値:[fill][align][sign][#][0][width][,][.precision][type]}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;全部覚える必要はありません。実際に手が動くのは以下の 5 つです。&lt;/p&gt;
&lt;h2&gt;1. 桁揃え&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;&lt;/code&gt; が左寄せ、&lt;code&gt;&amp;gt;&lt;/code&gt; が右寄せ、&lt;code&gt;^&lt;/code&gt; が中央寄せ。数字は幅です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;print(f&quot;|{&apos;Leão&apos;:&amp;lt;10}|&quot;)
print(f&quot;|{&apos;Leão&apos;:&amp;gt;10}|&quot;)
print(f&quot;|{&apos;Leão&apos;:^10}|&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;|Leão      |
|      Leão|
|   Leão   |
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;寄せ方を省略したときの既定は型によって違い、&lt;strong&gt;文字列は左寄せ、数値は右寄せ&lt;/strong&gt;です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;print(f&quot;|{&apos;Leão&apos;:10}|&quot;)
print(f&quot;|{9:10}|&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;|Leão      |
|         9|
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;寄せ方の前に文字を 1 つ置くと、空白の代わりにその文字で埋めます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;print(f&quot;{&apos;Leão&apos;:.&amp;lt;10}&quot;)
print(f&quot;{42:*^9}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Leão......
***42****
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この埋め文字は&lt;strong&gt;寄せ方とセットでしか書けません&lt;/strong&gt;。&lt;code&gt;f&quot;{42:*9}&quot;&lt;/code&gt; のように寄せ方を省くと、&lt;code&gt;*&lt;/code&gt; が埋め文字だと解釈されず &lt;code&gt;ValueError: Invalid format specifier&lt;/code&gt; になります。&lt;code&gt;.&amp;lt;&lt;/code&gt; や &lt;code&gt;*^&lt;/code&gt; のように必ず 2 文字で書いてください。&lt;/p&gt;
&lt;h2&gt;2. ゼロ埋め&lt;/h2&gt;
&lt;p&gt;幅の前に &lt;code&gt;0&lt;/code&gt; を置くとゼロ埋めになります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;print(f&quot;{7:03d}&quot;)
print(f&quot;{-42:05d}&quot;)
print(f&quot;{3.5:06.2f}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;007
-0042
003.50
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;-42&lt;/code&gt; が &lt;code&gt;-0042&lt;/code&gt; になっているとおり、この &lt;code&gt;0&lt;/code&gt; は符号を残したまま桁だけ埋めます。前章の埋め文字は符号より外側に入る（&lt;code&gt;f&quot;{-42:.&amp;gt;5}&quot;&lt;/code&gt; は &lt;code&gt;..-42&lt;/code&gt;）ので、数値として桁を揃えたいときはこちらを使います。&lt;/p&gt;
&lt;h2&gt;3. カンマ区切り&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;,&lt;/code&gt; を入れると 3 桁区切り、&lt;code&gt;_&lt;/code&gt; ならアンダースコア区切りになります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;print(f&quot;{1234567:,}&quot;)
print(f&quot;{1234567:_}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;1,234,567
1_234_567
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;幅・区切り・精度は同時に指定できます。並び順は幅 → 区切り → 精度です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;print(f&quot;{1234567.891:&amp;gt;14,.2f}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;  1,234,567.89
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. 小数の桁数&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;.2f&lt;/code&gt; で小数第 2 位まで。&lt;code&gt;%&lt;/code&gt; を使うと 100 倍して &lt;code&gt;%&lt;/code&gt; を付けてくれるので、率の表示で便利です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;print(f&quot;{2/3:.2f}&quot;)
print(f&quot;{0.4567:.1%}&quot;)
print(f&quot;{123456:.3g}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;0.67
45.7%
1.23e+05
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;g&lt;/code&gt; は有効数字での指定で、大きすぎる/小さすぎる値は指数表記に切り替わります。&lt;/p&gt;
&lt;p&gt;:::caution
&lt;code&gt;.2f&lt;/code&gt; は四捨五入とは別の規則で丸めます。しかも直感から外れる理由が 2 つあります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;print(f&quot;{0.125:.2f}&quot;)
print(f&quot;{2.675:.2f}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;0.12
2.67
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;0.125&lt;/code&gt; は 1/8 なので 2 進数で正確に表せます。これはちょうど半分の値なので、偶数側に寄せる規則（偶数丸め）が働いて &lt;code&gt;0.12&lt;/code&gt; になります。&lt;/p&gt;
&lt;p&gt;一方 &lt;code&gt;2.675&lt;/code&gt; は 2 進数で正確に表せず、内部では &lt;code&gt;2.674999...&lt;/code&gt; を持っています。半分に届いていないので単に切り捨てられただけで、偶数丸めは関係ありません。「半分の値は偶数側」という規則だけを覚えていると、&lt;code&gt;2.68&lt;/code&gt; を期待して外します。&lt;/p&gt;
&lt;p&gt;金額を扱ったことはまだないので受け売りです。丸め方を厳密に決めたい場面では、表示側で調整せず &lt;code&gt;decimal.Decimal&lt;/code&gt; で丸めてから渡すのが一般的なやり方とされています。
:::&lt;/p&gt;
&lt;h2&gt;5. 日付&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;datetime&lt;/code&gt; を渡したときの &lt;code&gt;:&lt;/code&gt; 以降は、ここまでの書式ミニ言語ではなく &lt;a href=&quot;https://docs.python.org/ja/3/library/datetime.html#strftime-and-strptime-format-codes&quot;&gt;&lt;code&gt;strftime&lt;/code&gt; の書式&lt;/a&gt; がそのまま使えます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from datetime import datetime

d = datetime(2026, 8, 3, 21, 5)
print(f&quot;{d:%Y-%m-%d}&quot;)
print(f&quot;{d:%Y/%m/%d %H:%M}&quot;)
print(f&quot;{d:%m/%d(%a)}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;2026-08-03
2026/08/03 21:05
08/03(Mon)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;d.strftime(&quot;%Y-%m-%d&quot;)&lt;/code&gt; と結果は同じですが、f-string の中に直接書けるぶん読みやすくなります。オブジェクトごとに &lt;code&gt;:&lt;/code&gt; 以降の解釈を変えられる仕組みなので、自作クラスでも &lt;code&gt;__format__&lt;/code&gt; を定義すれば同じことができます。&lt;/p&gt;
&lt;h2&gt;組み合わせると表になる&lt;/h2&gt;
&lt;p&gt;5 つを覚えると、ログや確認用の出力を整形するのが一気に楽になります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rows = [(&quot;Rafael Leão&quot;, 9, 1.234), (&quot;Christian Pulisic&quot;, 8, 0.87), (&quot;Adrien Rabiot&quot;, 6, 12.5)]
for name, goals, xg in rows:
    print(f&quot;{name:&amp;lt;20}{goals:&amp;gt;3}{xg:&amp;gt;8.2f}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Rafael Leão           9    1.23
Christian Pulisic     8    0.87
Adrien Rabiot         6   12.50
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::caution
この方法は日本語を混ぜると崩れます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;print(f&quot;{&apos;レオン&apos;:&amp;lt;10}|&quot;)
print(f&quot;{&apos;Leão&apos;:&amp;lt;10}|&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;レオン       |
Leão      |
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;幅指定が数えているのは&lt;strong&gt;文字数であって表示幅ではない&lt;/strong&gt;ためです。&lt;code&gt;レオン&lt;/code&gt; は 3 文字なので、残り 7 個ぶんの空白が入ります。ただし全角は 1 文字で 2 列ぶんの見た目を取るため、等幅フォントで並べたときに端が揃いません。&lt;/p&gt;
&lt;p&gt;自力で揃えるなら &lt;code&gt;unicodedata.east_asian_width&lt;/code&gt; で 1 文字ずつ列数を数えてパディングします。表の整形が目的なら &lt;a href=&quot;https://pypi.org/project/tabulate/&quot;&gt;tabulate&lt;/a&gt; のようなライブラリに任せるほうが早いです。
:::&lt;/p&gt;
&lt;p&gt;幅を変数で渡したいときは、幅の部分をさらに &lt;code&gt;{}&lt;/code&gt; で囲めます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w = 8
print(f&quot;{3.14159:&amp;gt;{w}.3f}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;   3.142
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;おまけ: デバッグ用の &lt;code&gt;=&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Python 3.8 以降では、式の後ろに &lt;code&gt;=&lt;/code&gt; を置くと式そのものと値を一緒に出してくれます。書式指定と併用もできます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;xg = 1.2345
print(f&quot;{xg=}&quot;)
print(f&quot;{xg = :.2f}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;xg=1.2345
xg = 1.23
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;print(&quot;xg =&quot;, xg)&lt;/code&gt; と書く手間がなくなるので、値を覗きたいだけの場面はこれで足ります。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;覚えるのは並び順だけです。&lt;strong&gt;埋め文字 → 寄せ → 幅 → 区切り → 精度 → 型&lt;/strong&gt;。&lt;code&gt;{:&amp;gt;10,.2f}&lt;/code&gt; を「右寄せ・幅 10・カンマ区切り・小数 2 桁・浮動小数点」、&lt;code&gt;{:*^9}&lt;/code&gt; を「&lt;code&gt;*&lt;/code&gt; で埋めて・中央寄せ・幅 9」と読み下せるようになれば、あとは必要な部分を足し引きするだけで済みます。&lt;/p&gt;
</content:encoded><category>Tech</category><category>Python</category><category>f-string</category></item><item><title>Pandas で and が使えず &amp; が要る理由</title><link>https://blog.c12o.net/posts/pandas-and-vs-ampersand/</link><guid isPermaLink="true">https://blog.c12o.net/posts/pandas-and-vs-ampersand/</guid><description>Pandas で複数条件の絞り込みに and を使うと「The truth value of a Series is ambiguous」で落ちます。and が全体で 1 つの真偽値を求めるのに対し、&amp; は行ごとに判定する別の演算で、各条件は丸カッコで囲む必要があります。</description><pubDate>Sun, 02 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Understat から取ってきたセリエAの選手データで「ミランの選手」かつ「5 得点以上」を絞り込もうとして、素直にこう書きました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;df[df[&quot;team_title&quot;] == &quot;AC Milan&quot; and df[&quot;goals&quot;] &amp;gt;= 5]
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;ValueError: The truth value of a Series is ambiguous.
Use a.empty, a.bool(), a.item(), a.any() or a.all().
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;and は「全体で 1 つ」を求める&lt;/h2&gt;
&lt;p&gt;Python の &lt;code&gt;and&lt;/code&gt; は、左右のオブジェクトを真偽値 1 つに変換してから判定します。ところが &lt;code&gt;df[&quot;team_title&quot;] == &quot;AC Milan&quot;&lt;/code&gt; が返すのは &lt;strong&gt;行数と同じ長さの bool の Series&lt;/strong&gt; で、真偽値 1 つではありません。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;print(df[&quot;team_title&quot;] == &quot;AC Milan&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;0      False
1      False
2      False
       ...
585    False
Name: team_title, Length: 586, dtype: bool
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;586 行ぶんの True/False が並んだこれを、&lt;code&gt;and&lt;/code&gt; は「Yes か No のどちらか 1 つ」に決めようとします。1 つでも True があれば True なのか、全部 True でなければ False なのか。決めようがないので Pandas は判断を拒否し、&lt;code&gt;ambiguous&lt;/code&gt;（あいまい）だと言ってきます。エラーメッセージが &lt;code&gt;a.any()&lt;/code&gt; や &lt;code&gt;a.all()&lt;/code&gt; を勧めてくるのは、「全体で 1 つにまとめるならどちらの意味か明示しろ」ということです。&lt;/p&gt;
&lt;h2&gt;&amp;amp; は「行ごとに 1 つ」&lt;/h2&gt;
&lt;p&gt;一方、こちらが本当にやりたいのは &lt;strong&gt;1 行ずつ「両方の条件を満たすか」を判定する&lt;/strong&gt; ことで、全体を 1 つにまとめることではありません。それをやるのが &lt;code&gt;&amp;amp;&lt;/code&gt; です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;df[(df[&quot;team_title&quot;] == &quot;AC Milan&quot;) &amp;amp; (df[&quot;goals&quot;] &amp;gt;= 5)]
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;       player_name team_title position  goals
       Rafael Leão   AC Milan      F S      9
 Christian Pulisic   AC Milan      F S      8
Christopher Nkunku   AC Milan      F S      7
     Adrien Rabiot   AC Milan      M S      6
Strahinja Pavlovic   AC Milan      D S      5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&amp;amp;&lt;/code&gt; は 2 つの bool Series を要素ごとに比較し、結果もまた bool の Series で返します。&lt;code&gt;df[...]&lt;/code&gt; はそれを行の選択に使います。&lt;code&gt;or&lt;/code&gt; に対応するのが &lt;code&gt;|&lt;/code&gt;、&lt;code&gt;not&lt;/code&gt; に対応するのが &lt;code&gt;~&lt;/code&gt; で、対応関係は同じです。&lt;/p&gt;
&lt;p&gt;なお各条件を丸カッコで囲んでいるのは飾りではありません。&lt;code&gt;&amp;amp;&lt;/code&gt; は &lt;code&gt;==&lt;/code&gt; や &lt;code&gt;&amp;gt;=&lt;/code&gt; より結合が強いため、囲まないと意図と違う順序で評価されます。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;&amp;amp;&lt;/code&gt; は &lt;code&gt;and&lt;/code&gt; とはそもそも別の演算で、代替品ではありません。&lt;code&gt;and&lt;/code&gt; が「まとめて 1 つの答え」を出すのに対し、&lt;code&gt;&amp;amp;&lt;/code&gt; は「行ごとに答え」を出します。Pandas の絞り込みで欲しいのは常に後者です。&lt;/p&gt;
&lt;p&gt;データの取得元については &lt;a href=&quot;/posts/football-data-sources-2026&quot;&gt;Pythonでサッカー分析を学ぼうとしたら、無料のxGが消えていた&lt;/a&gt; に書きました。&lt;/p&gt;
</content:encoded><category>Tech</category><category>Python</category><category>pandas</category><category>データ分析</category></item><item><title>Pythonでサッカー分析を学ぼうとしたら、無料のxGが消えていた</title><link>https://blog.c12o.net/posts/football-data-sources-2026/</link><guid isPermaLink="true">https://blog.c12o.net/posts/football-data-sources-2026/</guid><description>サッカー分析の入門記事はほぼ FBref 前提ですが、その高度スタッツは 2026年1月に消え、半年後も戻っていません。Understat / StatsBomb Open Data / football-data.co.uk を実際に叩き、xG の有無と利用条件を確かめました。</description><pubDate>Sat, 01 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Python の学習として、好きなサッカークラブの試合データを触るところから始めようと思いました。&lt;br /&gt;
日本語・英語を問わず入門記事はたくさんあります。ところが、そのほとんどが前提にしているデータソースが、いま動きません。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://fbref.com/&quot;&gt;FBref&lt;/a&gt; の高度スタッツが 2026年1月に丸ごと消えたためです。xG（&lt;a href=&quot;https://theanalyst.com/articles/what-is-expected-goals-xg&quot;&gt;Expected Goals&lt;/a&gt;）をはじめとする、いわゆる「サッカー分析らしい指標」がまとめて無くなりました。半年後に学習を始めて入り口で足止めを食らった側から、いま何が使えるのかを実際に取得して確かめた記録です。&lt;/p&gt;
&lt;p&gt;:::tip
記事中のコードをまとめて実行できる &lt;strong&gt;ノートブック&lt;/strong&gt;を用意しました。Google Colabで動くため、追加インストールなしで上から流すだけで動きます。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://colab.research.google.com/github/c12o-dev/notebooks/blob/main/football-data-2026-getting-started.ipynb&quot;&gt;Google Colab で開く&lt;/a&gt;（ソース: &lt;a href=&quot;https://github.com/c12o-dev/notebooks&quot;&gt;c12o-dev/notebooks&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;リンクを開いてそのまま実行できます。所要 1〜2 分ほどです。
:::&lt;/p&gt;
&lt;h2&gt;2026年1月20日に何が起きたか&lt;/h2&gt;
&lt;p&gt;一次ソースは Sports Reference の公式ブログ &lt;a href=&quot;https://www.sports-reference.com/blog/2026/01/fbref-stathead-data-update/&quot;&gt;FBref &amp;amp; Stathead Data Update&lt;/a&gt;（2026年1月20日）です。要点はこの一文に尽きます。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;last week the provider of our advanced soccer data sent us a letter terminating our access to their data feeds and requiring the deletion of their data from the site immediately.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;データ提供元から契約終了とデータ削除を通告され、従うほかなかった、という説明です。注意したいのは、&lt;strong&gt;この公式ブログ記事が提供元を名指ししていない&lt;/strong&gt; ことです。コミュニティでは Opta（Stats Perform）だと受け止められていますが、公式声明にその名前は出てきません。&lt;/p&gt;
&lt;p&gt;確かなのは次の事実です。その 8 日前の 2026年1月12日に、Stats Perform が &lt;a href=&quot;https://inside.fifa.com/tournament-organisation/commercial/media-releases/stats-perform-official-worldwide-betting-data-streaming-rights-distributor-world-cup&quot;&gt;FIFA の公式ベッティングデータ・ストリーミング配信権の独占ディストリビューター&lt;/a&gt;に決まったと発表されています。因果関係が公表されているわけではないので、並んだ事実として置いておきます。&lt;/p&gt;
&lt;h2&gt;半年後の今も戻っていない&lt;/h2&gt;
&lt;p&gt;この記事のメインはここです。1月時点の記事は「消えた」までしか書いていません。2026年8月の今はどうなのかを確かめます。&lt;/p&gt;
&lt;p&gt;FBref は現在ボット対策が入っていて、&lt;code&gt;curl&lt;/code&gt; や自動取得ツールからは 403 が返ります。そこで &lt;a href=&quot;https://web.archive.org/&quot;&gt;Internet Archive&lt;/a&gt; に残っている 2026年7月31日のスナップショットで、欧州5大リーグのシュート系スタッツ一覧のテーブル列を確認しました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import re
import requests

URL = (&quot;https://web.archive.org/web/20260731082148/&quot;
       &quot;https://fbref.com/en/comps/Big5/shooting/players/Big-5-European-Leagues-Stats&quot;)
html = requests.get(URL, timeout=60).text
print(sorted(set(re.findall(r&apos;data-stat=&quot;([a-z_0-9]+)&quot;&apos;, html))))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返ってきた列は &lt;code&gt;goals&lt;/code&gt; / &lt;code&gt;shots&lt;/code&gt; / &lt;code&gt;shots_on_target&lt;/code&gt; / &lt;code&gt;shots_on_target_pct&lt;/code&gt; / &lt;code&gt;goals_per_shot&lt;/code&gt; / &lt;code&gt;pens_made&lt;/code&gt; といった基礎スタッツだけでした。&lt;code&gt;xg&lt;/code&gt;、&lt;code&gt;npxg&lt;/code&gt; に相当する列は 1 つも残っていません。&lt;strong&gt;半年経っても、シュート数は取れるが xG は取れない状態が続いている&lt;/strong&gt;ということです。&lt;/p&gt;
&lt;p&gt;どこまで消えたかは PK 関連を見るとはっきりします。PK の試投数・成功数（&lt;code&gt;pens_att&lt;/code&gt; / &lt;code&gt;pens_made&lt;/code&gt;）は残っている一方で、PK を除いた期待得点である &lt;code&gt;npxg&lt;/code&gt; は消えています。&lt;strong&gt;試合で数えられる事実は残り、モデルで推定した値だけが抜けた&lt;/strong&gt;という消え方です。&lt;/p&gt;
&lt;p&gt;公式ブログも、将来の高度データについてこう書いています。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;if we present advanced data in the future it will certainly have a dramatically smaller scale.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;戻るとしても以前の規模ではない、という前提で組んだほうがよさそうです。&lt;/p&gt;
&lt;p&gt;:::note
FBref そのものが使えなくなったわけではありません。100 以上の大会にわたる試合結果・出場時間・得点といった基礎データは今も健在で、歴史データの参照先としては引き続き有力です。消えたのは xG などの高度スタッツだけです。
:::&lt;/p&gt;
&lt;h2&gt;代替を実際に叩いて確かめた&lt;/h2&gt;
&lt;p&gt;「代替はこれ」と列挙している記事は増えましたが、リンクを並べただけのものも多いので、学習で使う前提の 3 つを実際に取得しました。以下はすべて 2026年8月上旬時点で動作を確認したものです。&lt;/p&gt;
&lt;h3&gt;Understat —— xG が要るならまずここ&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://understat.com/&quot;&gt;Understat&lt;/a&gt; は欧州5大リーグの xG を独自モデルで公開しているサイトで、選手単位の &lt;code&gt;xG&lt;/code&gt; / &lt;code&gt;npxG&lt;/code&gt; / &lt;code&gt;xA&lt;/code&gt; / &lt;code&gt;xGChain&lt;/code&gt; / &lt;code&gt;xGBuildup&lt;/code&gt; が取れます。FBref で取れなくなった指標を補う第一候補です。&lt;/p&gt;
&lt;p&gt;ただし、注意点があります。&lt;strong&gt;日本語の入門記事の多くが書いている「HTML に埋め込まれた &lt;code&gt;var playersData = JSON.parse(&apos;...&apos;)&lt;/code&gt; を正規表現で抜く」方法は、もう通りません。&lt;/strong&gt; 現在の Understat はページ読み込み後に XHR でデータを取りに行く作りに変わっていて、HTML 側には該当の変数が存在しないためです。&lt;/p&gt;
&lt;p&gt;代わりに、ページ自身が叩いているエンドポイントを使います。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import pandas as pd
import requests

LEAGUE, SEASON = &quot;Serie_A&quot;, &quot;2025&quot;
res = requests.get(
    f&quot;https://understat.com/getLeagueData/{LEAGUE}/{SEASON}&quot;,
    headers={
        &quot;User-Agent&quot;: &quot;Mozilla/5.0&quot;,
        &quot;X-Requested-With&quot;: &quot;XMLHttpRequest&quot;,
        &quot;Referer&quot;: f&quot;https://understat.com/league/{LEAGUE}/{SEASON}&quot;,
    },
    timeout=30,
)
df = pd.DataFrame(res.json()[&quot;players&quot;])
print(df.columns.tolist())
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;[&apos;id&apos;, &apos;player_name&apos;, &apos;games&apos;, &apos;time&apos;, &apos;goals&apos;, &apos;xG&apos;, &apos;assists&apos;, &apos;xA&apos;,
 &apos;shots&apos;, &apos;key_passes&apos;, &apos;yellow_cards&apos;, &apos;red_cards&apos;, &apos;position&apos;,
 &apos;team_title&apos;, &apos;npg&apos;, &apos;npxG&apos;, &apos;xGChain&apos;, &apos;xGBuildup&apos;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;列は揃っていますが、&lt;strong&gt;このエンドポイントは数値もすべて文字列で返します。&lt;/strong&gt; &lt;code&gt;goals&lt;/code&gt; が &lt;code&gt;&quot;17&quot;&lt;/code&gt;、&lt;code&gt;xG&lt;/code&gt; が &lt;code&gt;&quot;17.13883000984788&quot;&lt;/code&gt; という具合です。気づかずに &lt;code&gt;df[&quot;goals&quot;].sum()&lt;/code&gt; とすると足し算ではなく文字列連結になるので、&lt;code&gt;pd.to_numeric()&lt;/code&gt; を通してから使ってください。&lt;/p&gt;
&lt;p&gt;セリエA 2025/26 で 586 行。ミランの xG 上位はこうなりました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;       player_name games time goals    xG assists    xA
Christopher Nkunku    32 1343     7  9.94       3  1.61
       Rafael Leão    29 1874     9  9.60       3  4.98
 Christian Pulisic    30 1612     8  9.54       4  5.04
   Youssouf Fofana    33 2231     2  4.59       3  3.64
     Adrien Rabiot    29 2546     6  4.36       4  3.94
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::caution
このエンドポイントは公式に文書化されていないので、いつ変わってもおかしくありません。実際、HTML 埋め込み方式が消えたのが良い例です。学習用途でも取得間隔は空けて、一度取ったものはローカルに保存して使い回してください。&lt;/p&gt;
&lt;p&gt;なお Understat の &lt;code&gt;robots.txt&lt;/code&gt; は &lt;code&gt;User-agent: * / Disallow: /&lt;/code&gt; で、自動アクセスを許可していません。ライブラリ経由でも同じことなので、負荷をかけない範囲での利用にとどめてください。
:::&lt;/p&gt;
&lt;p&gt;自分でリクエストを組みたくない場合は、&lt;a href=&quot;https://github.com/probberechts/soccerdata&quot;&gt;soccerdata&lt;/a&gt; が Understat を含む複数サイトのスクレイパーをまとめてくれています。キャッシュも面倒を見てくれるので、学習目的ならこちらのほうが安全です。&lt;/p&gt;
&lt;h3&gt;StatsBomb Open Data —— イベント単位で学ぶなら&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/statsbomb/open-data&quot;&gt;StatsBomb Open Data&lt;/a&gt; は、パス・シュート・タックルといったイベント 1 件ずつが JSON で公開されている無料データセットです。GitHub の raw ファイルなので、ボット対策も認証もありません。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;BASE = &quot;https://raw.githubusercontent.com/statsbomb/open-data/master/data&quot;

events = requests.get(f&quot;{BASE}/events/3773386.json&quot;, timeout=60).json()
shots = [e for e in events if e[&quot;type&quot;][&quot;name&quot;] == &quot;Shot&quot;]
print(len(events), len(shots), shots[0][&quot;shot&quot;][&quot;statsbomb_xg&quot;])  # 3891 29 0.201
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1 試合が 3,891 イベント、うちシュート 29 本。各シュートに &lt;code&gt;statsbomb_xg&lt;/code&gt; が入っており、シュート時点の全選手位置（&lt;code&gt;freeze_frame&lt;/code&gt;）まで付いてきます。収録は 80 コンペティション。ただし&lt;strong&gt;最新シーズンを網羅的に追う用途には向きません&lt;/strong&gt;。ワールドカップやメッシのキャリア全試合など、公開範囲が限定されているためです。&lt;/p&gt;
&lt;p&gt;利用条件も明快で、&lt;a href=&quot;https://github.com/statsbomb/open-data#terms--conditions&quot;&gt;README&lt;/a&gt; は「分析や考察を公開するときは出典を StatsBomb と明示すること」だけを求めています。&lt;/p&gt;
&lt;h3&gt;football-data.co.uk —— xG はないが手軽&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.football-data.co.uk/&quot;&gt;football-data.co.uk&lt;/a&gt; はリーグ・シーズンごとの CSV を直接置いてくれています。&lt;code&gt;pandas&lt;/code&gt; の 1 行で読めるのが強みです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;df = pd.read_csv(&quot;https://www.football-data.co.uk/mmz4281/2526/I1.csv&quot;)
print(df.shape)                                        # (380, 131)
print([c for c in df.columns if &quot;xg&quot; in c.lower()])    # []
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;380 試合 × 131 列。ただし中身は結果・シュート数・カード数と、大量のブックメーカーオッズです。&lt;strong&gt;xG は入っていません。&lt;/strong&gt; 前処理や結合の練習台としては手軽ですが、xG を扱いたいなら別のソースが要ります。&lt;/p&gt;
&lt;h2&gt;学習目的ならどう組むか&lt;/h2&gt;
&lt;p&gt;3 つ触ったうえでの結論です。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;公開・再配布まで考えるなら StatsBomb Open Data と football-data.co.uk。&lt;/strong&gt;
この 2 つは利用条件がはっきりしています（StatsBomb は出典明示を求めるのみ、football-data.co.uk は &lt;code&gt;robots.txt&lt;/code&gt; で自動アクセスを許可）。分析結果を記事や成果物として出す前提なら、こちらを使うのが安全です。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;xG を使いたいなら Understat を主軸にする。&lt;/strong&gt;
シーズン単位・選手単位で xG が揃う無料ソースとして、現状これが最も実用的です。ただし上のとおり自動アクセスは許可されていないので、手元の学習にとどめ、エンドポイントの変更に備えて取得と分析のコードは分けておきます。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;取得したデータは必ずローカルに保存する。&lt;/strong&gt;
毎回リクエストを投げる書き方は、相手への負荷も自分の試行錯誤の速度も悪くします。一度 CSV か Parquet に落として、以降はそれを読みます。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;イベントデータの練習は StatsBomb Open Data で。&lt;/strong&gt;
「シュート 1 本ごとの位置と xG」を扱う経験は、集計済みのテーブルからは得られません。対象大会は限られますが、学ぶだけなら十分です。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;入門記事は 2026年1月より前かどうかを最初に見る。&lt;/strong&gt;
それ以前の記事は FBref に高度スタッツがある前提で書かれています。手順が動かないのは自分のせいではないので、データソースの節だけ読み替えて進めます。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;「FBref を叩けば全部ある」時代は終わりました。無料で xG が手に入る時代が終わったわけではありません。手順どおりにやって動かないときは、コードではなくデータソースのほうを確認してください。&lt;/p&gt;
&lt;p&gt;次は、ここで取ってきた Understat のデータを実際に &lt;code&gt;pandas&lt;/code&gt; で触っていきます。手始めに、絞り込みで最初につまずく &lt;a href=&quot;/posts/pandas-and-vs-ampersand&quot;&gt;Pandas で &lt;code&gt;and&lt;/code&gt; が使えず &lt;code&gt;&amp;amp;&lt;/code&gt; が要る理由&lt;/a&gt; を書きました。&lt;/p&gt;
</content:encoded><category>Tech</category><category>Python</category><category>サッカー</category><category>データ分析</category><category>xG</category><category>オープンデータ</category></item><item><title>指でなぞると凍る画像モザイク——端末内完結のプライバシー PWA を作った</title><link>https://blog.c12o.net/posts/frost-mosaic/</link><guid isPermaLink="true">https://blog.c12o.net/posts/frost-mosaic/</guid><description>写真やスクショの一部を隠したいが、画像を Web サービスにアップロードするのは不安です。全処理がブラウザ内で完結する画像モザイク PWA「Frost」を作り、送らない・キャッシュしない・再エンコードで EXIF を落とす作りで端末内完結を担保しました。</description><pubDate>Wed, 17 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SNS や記事に写真・スクリーンショットを載せるとき、顔や住所、名前や番号など「ここだけは隠したい」部分が出てきます。選択肢は、手元の標準機能で黒塗りするか、Web の目隠しツールに画像をアップロードするかです。ただし後者は、隠したい画像をわざわざ外部サーバーに送ることになります。&lt;/p&gt;
&lt;p&gt;それが気持ち悪かったので、&lt;strong&gt;全処理が端末内で完結する画像モザイク &lt;a href=&quot;https://developer.mozilla.org/ja/docs/Web/Progressive_web_apps&quot;&gt;PWA&lt;/a&gt;「Frost（frost-mosaic）」&lt;/strong&gt; を作って公開しました。指でなぞった場所が氷霜に覆われて見えなくなる、という小さなツールです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ライブ: &lt;a href=&quot;https://frost.c12o.net&quot;&gt;frost.c12o.net&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;すぐ試す: &lt;a href=&quot;https://frost.c12o.net/app?sample=1&quot;&gt;frost.c12o.net/app?sample=1&lt;/a&gt;（同梱のサンプル画像で挙動を確認できます）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;::github{repo=&quot;c12o-dev/frost-mosaic&quot;}&lt;/p&gt;
&lt;p&gt;:::photosm{title=&quot;なぞった場所から氷霜が広がる（架空のサンプル画像）&quot;}
&lt;img src=&quot;/frost-mosaic/frost.gif&quot; alt=&quot;Frost でサンプル会員証の個人情報を氷霜で隠していくアニメーション&quot; /&gt;
:::&lt;/p&gt;
&lt;h2&gt;なぜ作ったか&lt;/h2&gt;
&lt;p&gt;出発点は、個人的な不満と「作ってみたい」が半分ずつでした。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;既存の Web ツールのセキュリティが気になった: 隠したい情報を含む画像を、見ず知らずの Web サービスにアップロードするのが不安だった。&lt;/li&gt;
&lt;li&gt;アプリを入れるのも手間: そのためだけにネイティブアプリをインストールするのは面倒で、ブラウザだけで終わらせたい。&lt;/li&gt;
&lt;li&gt;すりガラスの質感を使ってみたかった: 確実に塗りつぶすだけでなく、すりガラス（フロストガラス）のような半透明の質感で隠す表現を一度作ってみたかった。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;事前に調べた範囲では、ブラウザ完結の画像 redaction（目隠し）ツールは矩形や楕円で領域を選ぶものが主流で、&lt;strong&gt;指でなぞるブラシ式&lt;/strong&gt;は見当たりませんでした。だったら作ろう、というのがきっかけです。&lt;/p&gt;
&lt;h2&gt;何ができるのか&lt;/h2&gt;
&lt;p&gt;やることは単純で、&lt;strong&gt;画像を読み込み、隠したい場所を指でなぞる&lt;/strong&gt;だけです。なぞった軌跡に沿って、その下の画像が加工面に置き換わります。&lt;/p&gt;
&lt;p&gt;加工の見せ方が「氷霜ガラス」です。なぞった所から霜が結晶化して広がり、対象を覆う見せ方です。黒塗りやモザイクのような無機質な遮蔽ではなく、冬の窓に降りる霜のような質感で隠します。隠蔽の実体はモザイクとぼかしが担い、氷霜はその上に乗るレイヤーなので、「凍らせる＝隠す」という比喩と機能の向きは一致しています。見た目の演出が目的を曖昧にしないよう、比喩と機能の向きを揃えました。&lt;/p&gt;
&lt;p&gt;実際に、架空のサンプル会員証で氏名・住所・電話番号を隠すとこうなります。&lt;/p&gt;
&lt;p&gt;:::photo{title=&quot;加工前：架空のサンプル会員証&quot;}
&lt;img src=&quot;./sample.png&quot; alt=&quot;個人情報が見えている加工前のサンプル会員証&quot; /&gt;
:::&lt;/p&gt;
&lt;p&gt;:::photo{title=&quot;加工後：氏名・住所・電話番号を氷霜で覆った状態&quot;}
&lt;img src=&quot;./sample-frost.png&quot; alt=&quot;氷霜で個人情報を隠したサンプル会員証&quot; /&gt;
:::&lt;/p&gt;
&lt;h2&gt;端末の外に画像を出さない&lt;/h2&gt;
&lt;p&gt;このツールの核は「&lt;strong&gt;画像が端末の外に出ない&lt;/strong&gt;」ことです。主張だけでなく、実装の構造でそうなるようにしています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;送らない: 読み込みは &lt;a href=&quot;https://developer.mozilla.org/ja/docs/Web/API/FileReader&quot;&gt;&lt;code&gt;FileReader&lt;/code&gt;&lt;/a&gt; で行い、加工は &lt;a href=&quot;https://developer.mozilla.org/ja/docs/Web/API/CanvasRenderingContext2D&quot;&gt;Canvas 2D&lt;/a&gt; 上で完結します。画像をサーバーへ送信する処理を持たない作りです。&lt;/li&gt;
&lt;li&gt;EXIF / GPS が落ちる: 保存は &lt;code&gt;canvas.toBlob(&apos;image/png&apos;)&lt;/code&gt; で再エンコードします。そのため元画像に含まれていた撮影日時や位置情報などのメタデータは出力に残りません。&lt;/li&gt;
&lt;li&gt;キャッシュしない: オフライン動作のために &lt;a href=&quot;https://developer.mozilla.org/ja/docs/Web/API/Service_Worker_API&quot;&gt;Service Worker&lt;/a&gt; でアプリ本体（HTML・JS・CSS など）はキャッシュします。ただし &lt;strong&gt;ユーザーが読み込んだ画像はキャッシュ対象に含めていません&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;「ローカルで処理します」と書くだけなら簡単です。送らない・キャッシュしない・再エンコードで EXIF を落とす、という動作を実装に織り込んで主張を裏打ちしています。この設計の詳細は別記事で掘り下げる予定です。&lt;/p&gt;
&lt;h2&gt;使い方は「読み込んで、なぞって、保存」&lt;/h2&gt;
&lt;p&gt;操作の流れはこれだけです。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;画像を読み込む（ファイル選択、または空状態の枠をタップ）&lt;/li&gt;
&lt;li&gt;隠したい場所を指でなぞる&lt;/li&gt;
&lt;li&gt;必要ならスライダーで調整する&lt;/li&gt;
&lt;li&gt;保存する&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;調整できるのは 4 つのスライダーです。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;スライダー&lt;/th&gt;
&lt;th&gt;範囲&lt;/th&gt;
&lt;th&gt;役割&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;粗さ&lt;/td&gt;
&lt;td&gt;6–48px&lt;/td&gt;
&lt;td&gt;モザイクのブロックの大きさ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ぼかし&lt;/td&gt;
&lt;td&gt;0–14px&lt;/td&gt;
&lt;td&gt;加工面のぼかし量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;氷霜&lt;/td&gt;
&lt;td&gt;0–100%&lt;/td&gt;
&lt;td&gt;霜レイヤーの濃さ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ブラシ&lt;/td&gt;
&lt;td&gt;10–90px&lt;/td&gt;
&lt;td&gt;なぞる円の太さ&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;直近のストロークの取消と全消去もできます。長い縦長スクショなどは、2 本指でズーム・パンして拡大すれば細部も塗れます（1 本指は描画、2 本指がズーム・パンです）。&lt;/p&gt;
&lt;p&gt;保存すると &lt;code&gt;&amp;lt;元画像名&amp;gt;_&amp;lt;モード&amp;gt;.png&lt;/code&gt;（例: &lt;code&gt;IMG_1234_frost.png&lt;/code&gt;）という名前の PNG が得られます。iOS では共有シート経由、Android やデスクトップでは直接ダウンロードと、環境に合わせて保存方法を出し分けています。&lt;/p&gt;
&lt;h2&gt;モザイクと氷霜ガラスの 2 モード&lt;/h2&gt;
&lt;p&gt;隠し方は 2 モードあります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;モザイク: 縮小→拡大によるブロック化。昔ながらの素直なモザイクです。&lt;/li&gt;
&lt;li&gt;氷霜ガラス: モザイクを下地に、ぼかしとガラス質感、霜レイヤーを重ねたモード。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;同じ会員証を、それぞれのモードで隠すと印象が変わります。&lt;/p&gt;
&lt;p&gt;::::photorow
:::photosm{title=&quot;モザイクモード：ブロック状に粗くする&quot;}
&lt;img src=&quot;./mode-mosaic.png&quot; alt=&quot;モザイクモードで会員証を隠した編集画面&quot; /&gt;
:::&lt;/p&gt;
&lt;p&gt;:::photosm{title=&quot;氷霜ガラスモード：霜の質感で覆う&quot;}
&lt;img src=&quot;./mode-frost.png&quot; alt=&quot;氷霜ガラスモードで会員証を隠した編集画面&quot; /&gt;
:::
::::&lt;/p&gt;
&lt;p&gt;氷霜スライダーを 0 にすると霜レイヤーは消え、モザイク＋ぼかしだけが残ります。きっちり隠したい場面と、雰囲気を保ったまま隠したい場面で使い分けられます。&lt;/p&gt;
&lt;h2&gt;インストール不要、オフラインでも動く&lt;/h2&gt;
&lt;p&gt;Frost は PWA（プログレッシブ Web アプリ）です。ブラウザで開けばそのまま使えますし、ホーム画面に追加すればアプリのように起動し、2 回目以降はオフラインでも動きます。アプリ本体はランタイム依存ライブラリゼロの素の TypeScript で書いていて、ツール本体のバンドルは gzip で約 5KB と軽量です。&lt;/p&gt;
&lt;h2&gt;まずサンプルで試す&lt;/h2&gt;
&lt;p&gt;自分の画像を選ばなくても、&lt;a href=&quot;https://frost.c12o.net/app?sample=1&quot;&gt;frost.c12o.net/app?sample=1&lt;/a&gt; を開くと同梱のサンプル画像が端末内で読み込まれ、なぞる・調整する・保存するの一連をそのまま試せます。&lt;/p&gt;
&lt;p&gt;ソースは MIT ライセンスで公開しています。実装の詳しい話はそれぞれ別記事にまとめていきます。Canvas を 4 枚合成してマスク描画する仕組み、保存の方法を iOS と Android / デスクトップで出し分けた話、ズーム時に座標がずれない工夫などです。&lt;/p&gt;
</content:encoded><category>Tech</category><category>PWA</category><category>Canvas</category><category>プライバシー</category><category>画像処理</category><category>Web標準</category></item><item><title>Go の RE2 でストリーミングフィルタを安全に書く</title><link>https://blog.c12o.net/posts/go-re2-streaming-filter/</link><guid isPermaLink="true">https://blog.c12o.net/posts/go-re2-streaming-filter/</guid><description>Go 標準の regexp は RE2 実装で、実行時間が入力長に対して線形です。ログをリアルタイムにフィルタする mask-pipe を例に、FindAllStringSubmatchIndex と bufio.Scanner、区間マージ、fail-open の設計を示します。</description><pubDate>Tue, 21 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;Go 標準の &lt;a href=&quot;https://pkg.go.dev/regexp&quot;&gt;&lt;code&gt;regexp&lt;/code&gt;&lt;/a&gt; パッケージは &lt;a href=&quot;https://github.com/google/re2&quot;&gt;RE2&lt;/a&gt; 実装をベースにしており、&lt;strong&gt;正規表現の実行時間が入力長に対して線形&lt;/strong&gt; であることを保証しています。PCRE 系の実装で起きる ReDoS（Regular expression Denial of Service）の心配がありません。ユーザー入力や外部ログを正規表現で処理する CLI ツールに向いています。&lt;/p&gt;
&lt;p&gt;材料は先日公開した &lt;a href=&quot;/posts/mask-pipe-terminal-secret-masking&quot;&gt;mask-pipe&lt;/a&gt; の実装です。Go でストリーミングフィルタを書くときに押さえておきたい点を、そこから拾い出します。&lt;/p&gt;
&lt;h2&gt;RE2 が線形時間である理由&lt;/h2&gt;
&lt;p&gt;PCRE 系のバックトラッキング実装は &lt;code&gt;(a+)+$&lt;/code&gt; のようなネスト量化子で指数的に遅くなります。RE2 は NFA/DFA ベースの実装で、バックトラックを原理的に行わないため入力長 N に対して O(N) で済みます。&lt;/p&gt;
&lt;p&gt;代償としてバックリファレンス（&lt;code&gt;\1&lt;/code&gt;、&lt;code&gt;\2&lt;/code&gt;）やルックアラウンド（&lt;code&gt;(?=...)&lt;/code&gt;、&lt;code&gt;(?!...)&lt;/code&gt;）は使えません。Go の &lt;code&gt;regexp&lt;/code&gt; も同じ制約です。現場で書く「ユーザー入力をサニタイズしたい」「ログからパターンを抜きたい」用途はほぼすべてこの範囲で収まります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import &quot;regexp&quot;

var awsKey = regexp.MustCompile(`AKIA[0-9A-Z]{16}`)

func main() {
    fmt.Println(awsKey.MatchString(&quot;AKIAIOSFODNN7EXAMPLE&quot;)) // true
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;MustCompile&lt;/code&gt; はコンパイル失敗時に panic する初期化用関数です。グローバル変数に代入して使い回すのが一般的な使い方で、&lt;code&gt;init()&lt;/code&gt; で一度コンパイルすればホットループでの再コンパイルを避けられます。&lt;/p&gt;
&lt;h2&gt;FindAllStringSubmatchIndex を使うパターン&lt;/h2&gt;
&lt;p&gt;マスクや置換を行う場合、単に「マッチするか」だけでは足りず、&lt;strong&gt;マッチ位置のインデックス&lt;/strong&gt; が必要です。&lt;code&gt;FindAllStringSubmatchIndex&lt;/code&gt; は各マッチの開始・終了位置を &lt;code&gt;[]int&lt;/code&gt; で返します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pattern := regexp.MustCompile(`sk_(live|test)_[A-Za-z0-9]{24}`)
line := &quot;key=sk_live_abcdef0123456789abcdef01&quot;

matches := pattern.FindAllStringSubmatchIndex(line, -1)
for _, m := range matches {
    start, end := m[0], m[1]
    fmt.Printf(&quot;match at [%d:%d] = %q\n&quot;, start, end, line[start:end])
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返り値は &lt;code&gt;[全体開始, 全体終了, グループ1開始, グループ1終了, ...]&lt;/code&gt; の繰り返しです。位置が取れれば、前後の文字列を残したまま任意のマスク文字列に置き換えられます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func mask(line string, re *regexp.Regexp) string {
    var b strings.Builder
    last := 0
    for _, m := range re.FindAllStringSubmatchIndex(line, -1) {
        b.WriteString(line[last:m[0]])
        b.WriteString(&quot;[REDACTED]&quot;)
        last = m[1]
    }
    b.WriteString(line[last:])
    return b.String()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;strings.Builder&lt;/code&gt; を使うと中間文字列のアロケーションが抑えられ、1 秒に数千行流れるログでも十分な速度が出ます。&lt;/p&gt;
&lt;h2&gt;bufio.Scanner で行単位に流す&lt;/h2&gt;
&lt;p&gt;ストリーミングフィルタの基本形は &lt;code&gt;bufio.Scanner&lt;/code&gt; です。標準入力を 1 行ずつ読み、フィルタをかけて標準出力に書き戻します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func main() {
    scanner := bufio.NewScanner(os.Stdin)
    writer := bufio.NewWriter(os.Stdout)
    defer writer.Flush()

    for scanner.Scan() {
        masked := mask(scanner.Text(), awsKey)
        fmt.Fprintln(writer, masked)
    }
    if err := scanner.Err(); err != nil {
        fmt.Fprintln(os.Stderr, &quot;scan error:&quot;, err)
        os.Exit(1)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;bufio.Scanner&lt;/code&gt; はデフォルトで最大トークンサイズ（&lt;code&gt;bufio.MaxScanTokenSize&lt;/code&gt; = 64 KB）までしか読めず、それを超える長い行があると &lt;code&gt;bufio.ErrTooLong&lt;/code&gt; で停止します。初期バッファは小さく、長い行を読むたびにこの上限まで自動的に伸びる仕組みです。ログが長い場合は &lt;code&gt;scanner.Buffer&lt;/code&gt; で上限を引き上げます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;初期容量と最大容量を個別に指定できます。必要以上に大きくするとメモリ使用量が跳ねるので、ワークロードに合わせて決めます。&lt;/p&gt;
&lt;h2&gt;複数パターンをまとめて適用する&lt;/h2&gt;
&lt;p&gt;mask-pipe では AWS / GitHub / Stripe など複数の既定パターンを同時に適用します。素朴には各パターンで &lt;code&gt;FindAllStringSubmatchIndex&lt;/code&gt; を呼ぶのが正しく、&lt;strong&gt;パイプ &lt;code&gt;|&lt;/code&gt; で連結した巨大な正規表現を作らない&lt;/strong&gt; のが安全策です。&lt;/p&gt;
&lt;p&gt;:::caution[巨大な OR 結合は避ける]
パターンを &lt;code&gt;(?:pat1|pat2|pat3|...)&lt;/code&gt; で結合すると、グループ番号の管理が複雑になり、パターン追加時の影響範囲も広がります。RE2 上でも DFA の状態数が膨れてコンパイル時間が伸びます。各パターンを独立に扱い、マッチ区間のマージだけ後処理する方が保守しやすい構成です。
:::&lt;/p&gt;
&lt;p&gt;マッチ区間は互いに重なりうるので、全パターンのマッチ位置を集めてから左から右にマージします。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type span struct{ start, end int }

func merge(spans []span) []span {
    sort.Slice(spans, func(i, j int) bool { return spans[i].start &amp;lt; spans[j].start })
    out := spans[:0]
    for _, s := range spans {
        if len(out) == 0 || s.start &amp;gt; out[len(out)-1].end {
            out = append(out, s)
        } else if s.end &amp;gt; out[len(out)-1].end {
            out[len(out)-1].end = s.end
        }
    }
    return out
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これで重複しない区間リストが得られ、置換が予測可能になります。&lt;/p&gt;
&lt;h2&gt;マルチライン対応のバッファリング&lt;/h2&gt;
&lt;p&gt;PEM 秘密鍵のように複数行にまたがるブロックは行単位のフィルタでは検出できません。行単位を基本にしつつ、特定のマーカーで始まったら終端マーカーが来るまでバッファリングする方式が現実的です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var inBlock bool
var buf []string

for scanner.Scan() {
    line := scanner.Text()
    switch {
    case strings.Contains(line, &quot;-----BEGIN&quot;):
        inBlock = true
        buf = append(buf, line)
    case inBlock:
        buf = append(buf, line)
        if strings.Contains(line, &quot;-----END&quot;) {
            fmt.Fprintln(writer, &quot;[REDACTED PRIVATE KEY]&quot;)
            buf = nil
            inBlock = false
        }
    default:
        fmt.Fprintln(writer, mask(line, awsKey))
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;運用上のセーフティネットとして「バッファが一定サイズを超えたら fail-open でそのまま流す」動作にしておくと、異常な入力でフリーズしません。mask-pipe では 100 行または 64 KB を上限にしています。&lt;/p&gt;
&lt;h2&gt;ベンチマークで線形性を確認する&lt;/h2&gt;
&lt;p&gt;Go の &lt;code&gt;testing&lt;/code&gt; パッケージで計算量を手触りレベルで検証できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func BenchmarkMask_1KB(b *testing.B)  { benchMask(b, 1024) }
func BenchmarkMask_10KB(b *testing.B) { benchMask(b, 10*1024) }
func BenchmarkMask_100KB(b *testing.B){ benchMask(b, 100*1024) }

func benchMask(b *testing.B, n int) {
    input := strings.Repeat(&quot;a AKIAIOSFODNN7EXAMPLE &quot;, n/23)
    b.SetBytes(int64(len(input)))
    b.ResetTimer()
    for i := 0; i &amp;lt; b.N; i++ {
        _ = mask(input, awsKey)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;go test -bench=. -benchmem&lt;/code&gt; で実行すると、入力サイズに比例してナノ秒が増えることを確認できます。指数的に伸びないのが RE2 の健全性の証拠です。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Go の &lt;code&gt;regexp&lt;/code&gt; は RE2 ベースで ReDoS の心配がなく、CLI のストリーミングフィルタに適している&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FindAllStringSubmatchIndex&lt;/code&gt; でマッチ位置を取り、&lt;code&gt;strings.Builder&lt;/code&gt; で置換を組み立てる&lt;/li&gt;
&lt;li&gt;複数パターンは OR 結合せず、独立に評価して区間マージする&lt;/li&gt;
&lt;li&gt;マルチライン検出は行バッファ + 上限付き fail-open で安全に実装する&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;実装例は &lt;a href=&quot;https://github.com/c12o-dev/mask-pipe&quot;&gt;mask-pipe&lt;/a&gt; のコードが参考になります。ストリーミング前提で RE2 を使う場合、パターン設計と区間マージを分離しておくと後からパターンを足すのが楽になります。&lt;/p&gt;
</content:encoded><category>Tech</category><category>Go</category><category>regexp</category><category>RE2</category><category>ストリーミング</category><category>CLI</category></item><item><title>Cloudflare Workers とは何か：エッジ実行環境の基礎</title><link>https://blog.c12o.net/posts/cloudflare-workers-introduction/</link><guid isPermaLink="true">https://blog.c12o.net/posts/cloudflare-workers-introduction/</guid><description>Cloudflare Workers は V8 Isolate で動くエッジ実行環境で、コールドスタートがほぼゼロ、制限は CPU 時間ベースです。fetch ハンドラ、KV / R2 / D1 / Queues のバインド、Node.js API との違いを整理します。</description><pubDate>Mon, 20 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://workers.cloudflare.com/&quot;&gt;Cloudflare Workers&lt;/a&gt; は Cloudflare のエッジネットワーク上でコードを実行できるサーバーレス環境です。世界 300 以上の都市に配置されたデータセンターで、ユーザーに最も近い拠点から関数が応答します。&lt;/p&gt;
&lt;p&gt;「AWS Lambda のエッジ版」と紹介されることが多いですが、ランタイムモデルと起動オーバーヘッドが根本的に異なります。&lt;/p&gt;
&lt;p&gt;Pages との使い分けを先に知りたい方は &lt;a href=&quot;/posts/cloudflare-pages-vs-workers&quot;&gt;Cloudflare Pages と Workers の違いと使い分け&lt;/a&gt; を参照してください。Pages 単体の入門は &lt;a href=&quot;/posts/cloudflare-pages-introduction&quot;&gt;Cloudflare Pages とは何か&lt;/a&gt; にあります。&lt;/p&gt;
&lt;h2&gt;V8 Isolate モデル&lt;/h2&gt;
&lt;p&gt;Workers は Chrome と同じ &lt;a href=&quot;https://v8.dev/&quot;&gt;V8&lt;/a&gt; JavaScript エンジンの &lt;strong&gt;Isolate&lt;/strong&gt; という単位で動き、Node.js でも Deno でもありません。Isolate は V8 が提供する軽量なサンドボックスで、1 つのプロセス内に数千個を同居させられます。&lt;/p&gt;
&lt;p&gt;AWS Lambda が「リクエストごとに専用のコンテナを起動する」のに対し、Workers は「1 つのプロセスの中で Isolate を切り替える」方式です。結果として以下の特性が生まれます。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;項目&lt;/th&gt;
&lt;th&gt;Lambda&lt;/th&gt;
&lt;th&gt;Workers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;起動単位&lt;/td&gt;
&lt;td&gt;コンテナ（MB〜GB）&lt;/td&gt;
&lt;td&gt;Isolate（数 MB）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;コールドスタート&lt;/td&gt;
&lt;td&gt;数百 ms〜数秒&lt;/td&gt;
&lt;td&gt;0〜5 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;実行時間制限&lt;/td&gt;
&lt;td&gt;最大 15 分（wall-clock）&lt;/td&gt;
&lt;td&gt;CPU 時間 既定 30 秒／最大 5 分（Paid）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;メモリ&lt;/td&gt;
&lt;td&gt;〜10 GB&lt;/td&gt;
&lt;td&gt;128 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Lambda の「15 分」は wall-clock（実時間）の上限です。Workers の「30 秒 / 5 分」は &lt;strong&gt;CPU を実際に使った時間&lt;/strong&gt; の上限です。HTTP リクエストの wall-clock 自体はクライアント接続中は制限なく、&lt;code&gt;fetch&lt;/code&gt; のネットワーク待ちはカウントされません。軸が違う点に注意してください。&lt;/p&gt;
&lt;p&gt;Isolate の起動が数ミリ秒で済むため、&lt;strong&gt;Workers には実質的なコールドスタートが存在しません&lt;/strong&gt;。常に「新しい Isolate を立ち上げる」コストで済み、事前ウォームアップが不要です。&lt;/p&gt;
&lt;h2&gt;リクエストライフサイクル&lt;/h2&gt;
&lt;p&gt;Workers のコードは &lt;code&gt;fetch&lt;/code&gt; ハンドラを export するのが基本形です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise&amp;lt;Response&amp;gt; {
    const url = new URL(request.url);
    return new Response(`Hello from ${url.pathname}`);
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;引数は 3 つです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;request&lt;/code&gt;: 標準の &lt;a href=&quot;https://developer.mozilla.org/ja/docs/Web/API/Request&quot;&gt;&lt;code&gt;Request&lt;/code&gt;&lt;/a&gt; オブジェクト&lt;/li&gt;
&lt;li&gt;&lt;code&gt;env&lt;/code&gt;: バインド（後述）経由で注入される外部リソース&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctx&lt;/code&gt;: &lt;code&gt;waitUntil&lt;/code&gt; で非同期処理をレスポンス後に継続させるためのコンテキスト&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Node.js の &lt;code&gt;http&lt;/code&gt; モジュールや Express のような独自 API はありません。すべて Web 標準の Fetch API です。フロントエンドで &lt;code&gt;fetch()&lt;/code&gt; を使っている感覚のまま、サーバー側も書けます。&lt;/p&gt;
&lt;h3&gt;waitUntil で非同期処理を後回しに&lt;/h3&gt;
&lt;p&gt;ログ送信やキャッシュ更新など、レスポンスを待たせたくない処理は &lt;code&gt;ctx.waitUntil&lt;/code&gt; で実行します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ctx.waitUntil(sendAnalytics(request));
return new Response(&quot;ok&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;レスポンスは即座に返りつつ、分析イベントの送信は Worker の実行時間内で継続します。&lt;/p&gt;
&lt;h2&gt;Bindings で外部リソースを注入する&lt;/h2&gt;
&lt;p&gt;Workers から他の Cloudflare リソースを使うには &lt;strong&gt;バインド&lt;/strong&gt; を設定します。環境変数のリソース版だと捉えてください。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;wrangler.toml&lt;/code&gt;（または &lt;code&gt;wrangler.jsonc&lt;/code&gt;）で宣言します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;name = &quot;my-worker&quot;
main = &quot;src/index.ts&quot;

[[kv_namespaces]]
binding = &quot;CACHE&quot;
id = &quot;xxxxxxxxxxxx&quot;

[[d1_databases]]
binding = &quot;DB&quot;
database_name = &quot;my-db&quot;
database_id = &quot;yyyyyyyyyyyy&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;コード側は &lt;code&gt;env.CACHE&lt;/code&gt; や &lt;code&gt;env.DB&lt;/code&gt; として型付きで参照できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export default {
  async fetch(request, env) {
    const cached = await env.CACHE.get(&quot;key&quot;);
    if (cached) return new Response(cached);
    const { results } = await env.DB.prepare(&quot;SELECT name FROM users&quot;).all();
    await env.CACHE.put(&quot;key&quot;, JSON.stringify(results), { expirationTtl: 60 });
    return Response.json(results);
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;主要なバインド先は以下の通りです。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;バインド&lt;/th&gt;
&lt;th&gt;用途&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;KV&lt;/td&gt;
&lt;td&gt;グローバル分散の Key-Value ストア。読み込み頻度が高く、書き込みがまれな設定値向け&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R2&lt;/td&gt;
&lt;td&gt;S3 互換のオブジェクトストレージ。帯域無料&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D1&lt;/td&gt;
&lt;td&gt;エッジで動く SQLite。小〜中規模のリレーショナルデータに&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Queues&lt;/td&gt;
&lt;td&gt;メッセージキュー。バッチ処理やリトライ付きジョブに&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Durable Objects&lt;/td&gt;
&lt;td&gt;キー単位で状態を持てる強整合オブジェクト。WebSocket セッションやカウンタに&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Service Bindings&lt;/td&gt;
&lt;td&gt;別の Worker を関数のように呼び出す&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;実行時間とリソース制限&lt;/h2&gt;
&lt;p&gt;Workers の無料枠では CPU 時間が 1 リクエストあたり 10 ms までです。Paid プラン（$5/月〜）では既定で 30 秒、&lt;code&gt;wrangler.toml&lt;/code&gt; の &lt;code&gt;[limits] cpu_ms&lt;/code&gt; を設定すれば最大 5 分（300,000 ms）まで引き上げられます。ここで言う CPU 時間は「実際に CPU を使った時間」で、&lt;code&gt;fetch&lt;/code&gt; の待ち時間（ネットワーク I/O）は含まれません。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[limits]
cpu_ms = 60000  # 60 秒まで許可
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;メモリは 128 MB 固定です。重い画像処理や機械学習モデルのロードには向かず、そうした処理は R2 や &lt;a href=&quot;https://developers.cloudflare.com/workers-ai/&quot;&gt;Workers AI&lt;/a&gt; に委ねる構成が推奨されます。&lt;/p&gt;
&lt;p&gt;:::tip[Cron Triggers と Scheduled Workers]
&lt;code&gt;scheduled&lt;/code&gt; ハンドラを export すると、cron 式で定期実行される Worker が作れます。Pages Functions では使えない機能で、バッチ処理や外部 API のポーリングは Workers 単体で組む必要があります。
:::&lt;/p&gt;
&lt;h2&gt;デプロイ&lt;/h2&gt;
&lt;p&gt;デプロイは &lt;a href=&quot;https://developers.cloudflare.com/workers/wrangler/&quot;&gt;Wrangler&lt;/a&gt; CLI で行います。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ pnpm create cloudflare@latest my-worker
$ cd my-worker
$ pnpm run deploy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;wrangler deploy&lt;/code&gt; は TypeScript のコンパイル、バンドル、Cloudflare への配信までを一気に行います。デプロイから世界中のエッジに反映されるまで数秒です。&lt;/p&gt;
&lt;p&gt;開発中は &lt;code&gt;wrangler dev&lt;/code&gt; でローカル実行できます。V8 Isolate を Miniflare で模倣しており、バインドされた KV や D1 もローカルのエミュレータで動きます。&lt;/p&gt;
&lt;h2&gt;Node.js と何が違うか&lt;/h2&gt;
&lt;p&gt;Workers は Web 標準 API が中心で、Node.js 固有 API（&lt;code&gt;fs&lt;/code&gt;、&lt;code&gt;net&lt;/code&gt; など）は使えません。代わりに以下の対応があります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ファイル I/O: R2 や KV に置き換える&lt;/li&gt;
&lt;li&gt;TCP / UDP: &lt;code&gt;connect()&lt;/code&gt; API（TCP のみ、制限あり）&lt;/li&gt;
&lt;li&gt;Crypto: &lt;a href=&quot;https://developer.mozilla.org/ja/docs/Web/API/Web_Crypto_API&quot;&gt;Web Crypto API&lt;/a&gt; 標準&lt;/li&gt;
&lt;li&gt;環境変数: &lt;code&gt;env&lt;/code&gt; 経由&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Node.js 互換性が必要なら &lt;code&gt;nodejs_compat&lt;/code&gt; フラグを有効にすると、&lt;a href=&quot;https://developers.cloudflare.com/workers/runtime-apis/nodejs/&quot;&gt;主要な Node.js API のポリフィル&lt;/a&gt; が使えます。ただし &lt;code&gt;express&lt;/code&gt; のような「プロセス前提」のフレームワークは動かず、全 API が揃っているわけではありません。&lt;a href=&quot;https://hono.dev/&quot;&gt;Hono&lt;/a&gt; のように Web 標準だけで書かれたフレームワークを選ぶのが安全です。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Workers は V8 Isolate で動くエッジ実行環境で、コールドスタートがほぼゼロ&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fetch&lt;/code&gt; ハンドラを export するだけで始められ、API は Web 標準の Fetch API&lt;/li&gt;
&lt;li&gt;バインド経由で KV / R2 / D1 / Queues などを型安全に扱える&lt;/li&gt;
&lt;li&gt;Node.js 互換 API は限定的。Hono などの Web 標準ベースのフレームワークと相性が良い&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;次は具体的なユースケースとして API Proxy の構築例を書きます。静的サイトとの組み合わせは &lt;a href=&quot;/posts/cloudflare-pages-vs-workers&quot;&gt;Pages と Workers の使い分け&lt;/a&gt; を、無料枠の全体像は &lt;a href=&quot;/posts/cloudflare-free-tier-guide&quot;&gt;Cloudflare 無料枠運用ガイド&lt;/a&gt; を参照してください。&lt;/p&gt;
</content:encoded><category>Tech</category><category>Cloudflare</category><category>Cloudflare Workers</category><category>エッジコンピューティング</category><category>Serverless</category></item><item><title>Cloudflare Pages とは何か：静的サイトホスティングの実力</title><link>https://blog.c12o.net/posts/cloudflare-pages-introduction/</link><guid isPermaLink="true">https://blog.c12o.net/posts/cloudflare-pages-introduction/</guid><description>Cloudflare Pages は Git に push するだけで自動ビルドとエッジ配信が動く静的サイトホスティングです。ブランチプレビュー、カスタムドメイン、Pages Functions、Vercel・Netlify との帯域無制限の差まで入門レベルで整理します。</description><pubDate>Sun, 19 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://pages.cloudflare.com/&quot;&gt;Cloudflare Pages&lt;/a&gt; は Cloudflare が提供する静的サイトホスティングサービスです。GitHub や GitLab のリポジトリと連携し、push するだけで自動ビルド・デプロイが実行されます。&lt;/p&gt;
&lt;p&gt;Vercel や Netlify と同じカテゴリのサービスです。差別化ポイントは、Cloudflare のエッジネットワーク（世界 300 都市以上）上に直接配信される点と、無料枠の太さです。&lt;/p&gt;
&lt;h2&gt;基本的な仕組み&lt;/h2&gt;
&lt;p&gt;Cloudflare Pages のデプロイフローは以下の通りです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flowchart LR
    A[&quot;git push&quot;] --&amp;gt; B[&quot;Cloudflare Build&quot;]
    B --&amp;gt; C[&quot;静的アセット&quot;]
    C --&amp;gt; D[&quot;エッジ配信&amp;lt;br/&amp;gt;300+ 都市&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;GitHub / GitLab のリポジトリに push する&lt;/li&gt;
&lt;li&gt;Cloudflare のビルド環境でフレームワークのビルドコマンド（&lt;code&gt;npm run build&lt;/code&gt; など）を実行&lt;/li&gt;
&lt;li&gt;出力された静的ファイル（HTML, CSS, JS, 画像）をエッジネットワークに配信&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;開発者がやることは「push する」だけです。CI/CD の設定、CDN の構築、SSL 証明書の管理はすべて Cloudflare が担います。&lt;/p&gt;
&lt;h2&gt;プロジェクトの作成&lt;/h2&gt;
&lt;p&gt;Cloudflare ダッシュボードから作成する方法と、CLI（&lt;code&gt;wrangler&lt;/code&gt;）から作成する方法があります。&lt;/p&gt;
&lt;h3&gt;ダッシュボードから&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Cloudflare ダッシュボード → Workers &amp;amp; Pages → Create&lt;/li&gt;
&lt;li&gt;Pages タブを選択&lt;/li&gt;
&lt;li&gt;GitHub アカウントを接続し、リポジトリを選択&lt;/li&gt;
&lt;li&gt;ビルド設定（フレームワーク、ビルドコマンド、出力ディレクトリ）を指定&lt;/li&gt;
&lt;li&gt;Deploy&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;主要なフレームワーク（Astro, Next.js, Nuxt, SvelteKit, Hugo, Gatsby など）にはプリセットが用意されています。リポジトリを選択すると、ビルドコマンドと出力ディレクトリが自動的に設定されます。&lt;/p&gt;
&lt;h3&gt;CLI から&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$ pnpm add -g wrangler
$ wrangler pages project create my-site
$ wrangler pages deploy dist
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;wrangler pages deploy&lt;/code&gt; は指定したディレクトリの中身をそのままアップロードします。Git 連携なしで使えるため、GitHub Actions など外部の CI からデプロイしたい場合に便利です。&lt;/p&gt;
&lt;h2&gt;ブランチプレビュー&lt;/h2&gt;
&lt;p&gt;Cloudflare Pages の最も便利な機能の 1 つがブランチプレビューです。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;main&lt;/code&gt; 以外のブランチに push すると、そのブランチ専用のプレビュー URL が自動的に発行されます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;本番:              my-site.pages.dev
プレビュー（ハッシュ）: abc123.my-site.pages.dev
プレビュー（ブランチ）: fix-api.my-site.pages.dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;プレビュー URL はコミットごとに変わるハッシュ形式と、ブランチ名を使った固定エイリアス形式の両方が同時に発行されます。ハッシュ形式は「このコミット時点」の URL としてリグレッション確認に、ブランチ形式は PR のレビュー URL として共有するのに向きます。&lt;/p&gt;
&lt;p&gt;PR ごとにプレビュー環境が立ち上がるため、レビュアーがローカルでビルドしなくても変更内容を確認できます。GitHub にはコメントでプレビュー URL が自動投稿されます。&lt;/p&gt;
&lt;p&gt;プレビューデプロイは本番と同じエッジネットワーク上で配信されるため、パフォーマンス面でもほぼ本番と同じ条件でテストできます。&lt;/p&gt;
&lt;h2&gt;カスタムドメイン&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;my-site.pages.dev&lt;/code&gt; のデフォルトドメインに加えて、独自ドメインを設定できます。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;ダッシュボード → Pages プロジェクト → Custom domains&lt;/li&gt;
&lt;li&gt;ドメインを入力&lt;/li&gt;
&lt;li&gt;DNS レコードの追加を承認&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Cloudflare の DNS を使っている場合は自動的に CNAME レコードが追加されます。SSL 証明書も自動で発行・更新されるため、手動での管理は不要です。&lt;/p&gt;
&lt;p&gt;外部の DNS を使っている場合は、表示される CNAME レコードを手動で追加します。&lt;/p&gt;
&lt;h2&gt;ビルド設定のカスタマイズ&lt;/h2&gt;
&lt;h3&gt;環境変数&lt;/h3&gt;
&lt;p&gt;ダッシュボードの Settings → Environment variables からビルド時の環境変数を設定できます。Production と Preview で異なる値を設定することも可能です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Production:  API_URL = https://api.example.com
Preview:     API_URL = https://staging-api.example.com
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ビルドキャッシュ&lt;/h3&gt;
&lt;p&gt;Cloudflare Pages はビルド間で &lt;code&gt;node_modules&lt;/code&gt; をキャッシュします。2 回目以降のビルドは依存のインストールがスキップされるため、その分ビルドが速くなります。&lt;/p&gt;
&lt;p&gt;キャッシュが原因でビルドが失敗する場合は、ダッシュボードからキャッシュを手動でクリアできます。&lt;/p&gt;
&lt;h2&gt;Pages Functions&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;functions/&lt;/code&gt; ディレクトリにファイルを配置すると、サーバーサイドの処理を追加できます。ランタイムは &lt;a href=&quot;https://workers.cloudflare.com/&quot;&gt;Cloudflare Workers&lt;/a&gt; と同じ V8 Isolate 環境です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// functions/api/time.ts
export const onRequestGet: PagesFunction = async () =&amp;gt; {
  return Response.json({ now: new Date().toISOString() });
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ファイルパスがそのままルーティングになります。上の例では &lt;code&gt;/api/time&lt;/code&gt; に GET リクエストを送ると JSON が返ります。&lt;/p&gt;
&lt;p&gt;Pages Functions は静的サイトに動的機能を追加するオプションであり、複雑なバックエンドが必要なら &lt;a href=&quot;/posts/cloudflare-pages-vs-workers&quot;&gt;Workers を別途使う方が適切&lt;/a&gt;です。&lt;/p&gt;
&lt;h2&gt;他サービスとの比較&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Cloudflare Pages&lt;/th&gt;
&lt;th&gt;Vercel&lt;/th&gt;
&lt;th&gt;Netlify&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;無料枠のビルド数&lt;/td&gt;
&lt;td&gt;500 回/月&lt;/td&gt;
&lt;td&gt;6,000 分/月&lt;/td&gt;
&lt;td&gt;300 分/月&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;帯域&lt;/td&gt;
&lt;td&gt;無制限&lt;/td&gt;
&lt;td&gt;100 GB/月&lt;/td&gt;
&lt;td&gt;100 GB/月&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;エッジロケーション&lt;/td&gt;
&lt;td&gt;300+&lt;/td&gt;
&lt;td&gt;グローバル CDN&lt;/td&gt;
&lt;td&gt;グローバル CDN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;サーバーサイド&lt;/td&gt;
&lt;td&gt;Pages Functions&lt;/td&gt;
&lt;td&gt;Serverless Functions&lt;/td&gt;
&lt;td&gt;Netlify Functions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;プレビューデプロイ&lt;/td&gt;
&lt;td&gt;全ブランチ自動&lt;/td&gt;
&lt;td&gt;全ブランチ自動&lt;/td&gt;
&lt;td&gt;全ブランチ自動&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Cloudflare Pages の突出したメリットは &lt;strong&gt;帯域無制限&lt;/strong&gt; です。Vercel や Netlify では無料枠の帯域を超えると課金が始まります。Cloudflare Pages にはこの制限がありません。画像が多いサイトやアクセスが読めないサイトでは大きな差になります。なお、各サービスの料金体系は頻繁に変更されるため、最新の上限値は公式サイトで確認してください。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Cloudflare Pages は Git 連携の自動ビルド・デプロイ、ブランチプレビュー、SSL 自動管理が揃った静的サイトホスティング&lt;/li&gt;
&lt;li&gt;帯域無制限・月 500 ビルドの無料枠は個人開発には十分すぎる水準&lt;/li&gt;
&lt;li&gt;Pages Functions で軽量なサーバーサイド処理も追加可能&lt;/li&gt;
&lt;li&gt;より詳しい Workers との使い分けは&lt;a href=&quot;/posts/cloudflare-pages-vs-workers&quot;&gt;こちらの記事&lt;/a&gt;を、無料枠の全体像は&lt;a href=&quot;/posts/cloudflare-free-tier-guide&quot;&gt;こちらの記事&lt;/a&gt;を参照&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;静的サイトのホスティング先を探しているなら、まずは Cloudflare Pages を試してみるのがおすすめです。GitHub リポジトリを接続して push するだけで動きます。&lt;/p&gt;
</content:encoded><category>Tech</category><category>Cloudflare</category><category>Cloudflare Pages</category><category>静的サイト</category><category>ホスティング</category></item><item><title>uv で Python 環境構築を爆速にする</title><link>https://blog.c12o.net/posts/uv-python-setup/</link><guid isPermaLink="true">https://blog.c12o.net/posts/uv-python-setup/</guid><description>Rust 製の Python パッケージマネージャー uv で、Python のインストール、仮想環境、依存の追加とロック、スクリプトとツールの実行を 1 つのコマンド体系にまとめる方法。requirements.txt からの移行、pip / poetry との比較表つき。</description><pubDate>Sat, 18 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;Python の環境構築は長年の課題です。pip でパッケージを入れ、venv で仮想環境を作り、requirements.txt でバージョンを固定し、場合によっては pyenv で Python 自体のバージョンも管理する。ツールが多すぎて、環境構築だけで疲弊することが珍しくありません。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.astral.sh/uv/&quot;&gt;uv&lt;/a&gt; は Astral 社（Ruff の開発元）が提供する Rust 製の Python パッケージマネージャーです。pip の 10〜100 倍速いインストール速度と、Python バージョン管理・仮想環境・パッケージ管理の一元化が特徴です。&lt;/p&gt;
&lt;h2&gt;インストール&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ curl -LsSf https://astral.sh/uv/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Windows なら PowerShell で以下を実行します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ powershell -ExecutionPolicy ByPass -c &quot;irm https://astral.sh/uv/install.ps1 | iex&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;インストール後、&lt;code&gt;uv&lt;/code&gt; コマンドが使えることを確認します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ uv --version
uv x.y.z
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Python 自体のインストール&lt;/h2&gt;
&lt;p&gt;uv は Python のバージョン管理も担います。pyenv が不要になります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ uv python install 3.12
$ uv python list
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;uv python install&lt;/code&gt; はビルド済みバイナリをダウンロードするため、pyenv のようなソースからのコンパイルが不要です。数秒で終わります。&lt;/p&gt;
&lt;h2&gt;プロジェクトの初期化&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ uv init my-project
$ cd my-project
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;uv init&lt;/code&gt; は以下のファイルを生成します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;my-project/
├── .git/
├── .gitignore
├── .python-version
├── README.md
├── main.py
└── pyproject.toml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;uv init&lt;/code&gt; は内部で &lt;code&gt;git init&lt;/code&gt; も実行するため、&lt;code&gt;.git/&lt;/code&gt; と &lt;code&gt;.gitignore&lt;/code&gt; も同時に生成されます。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pyproject.toml&lt;/code&gt; がプロジェクトの中心です。依存パッケージ、Python バージョン制約、プロジェクトメタデータをすべてここに書きます。&lt;/p&gt;
&lt;h2&gt;パッケージのインストール&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ uv add requests
$ uv add ruff --dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;uv add&lt;/code&gt; はパッケージを &lt;code&gt;pyproject.toml&lt;/code&gt; に追加し、&lt;code&gt;uv.lock&lt;/code&gt; にバージョンをロックし、仮想環境にインストールするところまでを 1 コマンドで行います。仮想環境が存在しなければ自動的に作成されます。&lt;/p&gt;
&lt;p&gt;速度の違いは体感で明らかです。pip で 30 秒かかるインストールが、uv では 1〜2 秒で終わることも珍しくありません。Rust 製の依存解決エンジンと、パッケージのグローバルキャッシュが速さの理由です。&lt;/p&gt;
&lt;h3&gt;pip からの移行&lt;/h3&gt;
&lt;p&gt;既存の &lt;code&gt;requirements.txt&lt;/code&gt; からの移行も簡単です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ uv add -r requirements.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これだけで &lt;code&gt;requirements.txt&lt;/code&gt; に書かれた全パッケージが &lt;code&gt;pyproject.toml&lt;/code&gt; に移行され、&lt;code&gt;uv.lock&lt;/code&gt; が生成されます。&lt;/p&gt;
&lt;h2&gt;スクリプトの実行&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ uv run python main.py
$ uv run ruff check .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;uv run&lt;/code&gt; は仮想環境をアクティベートせずにコマンドを実行します。&lt;code&gt;source .venv/bin/activate&lt;/code&gt; を忘れて「パッケージが見つからない」と悩む問題がなくなります。&lt;/p&gt;
&lt;h3&gt;インラインスクリプト&lt;/h3&gt;
&lt;p&gt;ファイル先頭にメタデータを書くことで、依存パッケージ付きのスクリプトを単体実行できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# /// script
# requires-python = &quot;&amp;gt;=3.12&quot;
# dependencies = [&quot;requests&quot;, &quot;rich&quot;]
# ///

import requests
from rich import print

resp = requests.get(&quot;https://httpbin.org/get&quot;)
print(resp.json())
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ uv run fetch.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;uv run&lt;/code&gt; がメタデータを読み取り、依存パッケージを自動でインストールしてから実行します。使い捨てスクリプトの共有に便利です。&lt;/p&gt;
&lt;h2&gt;ツールの実行&lt;/h2&gt;
&lt;p&gt;グローバルにインストールせずに CLI ツールを実行する &lt;code&gt;uvx&lt;/code&gt; コマンドもあります。&lt;code&gt;npx&lt;/code&gt; の Python 版です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ uvx ruff check .
$ uvx black --check .
$ uvx mypy src/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ツールは一時的な隔離環境にインストールされ、プロジェクトの依存を汚しません。&lt;/p&gt;
&lt;h2&gt;pip / poetry との比較&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;操作&lt;/th&gt;
&lt;th&gt;pip + venv&lt;/th&gt;
&lt;th&gt;poetry&lt;/th&gt;
&lt;th&gt;uv&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;仮想環境の作成&lt;/td&gt;
&lt;td&gt;&lt;code&gt;python -m venv .venv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;自動&lt;/td&gt;
&lt;td&gt;自動&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;パッケージ追加&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pip install&lt;/code&gt; → 手動で requirements.txt 更新&lt;/td&gt;
&lt;td&gt;&lt;code&gt;poetry add&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;uv add&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ロックファイル&lt;/td&gt;
&lt;td&gt;なし（pip-tools で別途生成）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;poetry.lock&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;uv.lock&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python バージョン管理&lt;/td&gt;
&lt;td&gt;pyenv 等が別途必要&lt;/td&gt;
&lt;td&gt;pyenv 等が別途必要&lt;/td&gt;
&lt;td&gt;&lt;code&gt;uv python install&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;インストール速度&lt;/td&gt;
&lt;td&gt;遅い&lt;/td&gt;
&lt;td&gt;遅い&lt;/td&gt;
&lt;td&gt;10〜100 倍速い&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ツール実行（隔離環境）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pipx&lt;/code&gt;（別途インストール）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pipx&lt;/code&gt;（別途インストール）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;uvx&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;uv の最大の強みは &lt;strong&gt;これまで 3〜4 個のツールで賄っていた機能が 1 つに統合されている&lt;/strong&gt; 点です。pyenv + venv + pip + pip-tools の組み合わせを uv 単体で置き換えられます。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;uv は Python のバージョン管理・仮想環境・パッケージ管理を 1 コマンドに統合する&lt;/li&gt;
&lt;li&gt;Rust 製の依存解決エンジンにより、pip の 10〜100 倍速いインストールを実現&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uv run&lt;/code&gt; で仮想環境のアクティベートが不要、&lt;code&gt;uvx&lt;/code&gt; でグローバルインストールなしのツール実行が可能&lt;/li&gt;
&lt;li&gt;既存の &lt;code&gt;requirements.txt&lt;/code&gt; からの移行は &lt;code&gt;uv add -r requirements.txt&lt;/code&gt; だけ&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;新規プロジェクトなら最初から uv を使うのがおすすめです。既存プロジェクトは、&lt;code&gt;uv add -r requirements.txt&lt;/code&gt; で移行するところから始められます。速度の違いだけでも導入する価値があります。&lt;/p&gt;
</content:encoded><category>Tech</category><category>Python</category><category>uv</category><category>開発環境</category></item><item><title>Cloudflare を無料枠だけで運用する実践ガイド</title><link>https://blog.c12o.net/posts/cloudflare-free-tier-guide/</link><guid isPermaLink="true">https://blog.c12o.net/posts/cloudflare-free-tier-guide/</guid><description>Cloudflare の Pages・Workers・R2・D1・KV の Free プラン上限を一覧にし、個人開発の 3 つの構成パターンと、Workers のリクエスト数・KV の書き込み・D1 の行数・R2 の操作クラスで無料枠に収める設計の要点をまとめます。</description><pubDate>Fri, 17 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.cloudflare.com/&quot;&gt;Cloudflare&lt;/a&gt; は無料枠が異常に太いことで知られています。&lt;br /&gt;
Pages、Workers、R2、D1、KV など個人開発で使うサービスのほとんどが Free プランで提供されており驚きを隠せません。&lt;/p&gt;
&lt;p&gt;ただし「無料で使える」と「無料枠で収まる設計にする」は別の話です。上限を知らずに設計すると、思わぬところで課金が発生します。&lt;/p&gt;
&lt;p&gt;各サービスの Free プラン上限と、個人開発で無料枠に収まる構成パターンは次のとおりです。&lt;/p&gt;
&lt;p&gt;:::tip[この記事で登場する Cloudflare 用語]&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;R2: 画像・動画・PDF などを置く S3 互換オブジェクトストレージ。egress（下り転送量）が無料で CDN 的な配信にも向く&lt;/li&gt;
&lt;li&gt;D1: SQLite ベースのリレーショナル DB。JOIN やトランザクションが必要なアプリで、従来の RDB と同じ感覚で使える&lt;/li&gt;
&lt;li&gt;KV: キーに対して値を返す超軽量な Key-Value ストア。設定値やキャッシュなど「読み取りが多く書き込みは少ない」データ向け
:::&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;無料枠の上限一覧&lt;/h2&gt;
&lt;p&gt;2026 年 4 月時点の主要サービスの Free プラン上限です。最新情報は公式の &lt;a href=&quot;https://www.cloudflare.com/plans/&quot;&gt;Plans ページ&lt;/a&gt; で確認してください。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;サービス&lt;/th&gt;
&lt;th&gt;主な上限&lt;/th&gt;
&lt;th&gt;超過時の挙動&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pages&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;無制限のサイト、500 回/月ビルド&lt;/td&gt;
&lt;td&gt;ビルド失敗&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Workers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10 万リクエスト/日、10ms CPU 時間/リクエスト&lt;/td&gt;
&lt;td&gt;リクエスト拒否&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;KV&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10 万読み取り/日、1,000 書き込み/日&lt;/td&gt;
&lt;td&gt;リクエスト拒否&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;R2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10 GB ストレージ、100 万 Class A/月、1,000 万 Class B/月&lt;/td&gt;
&lt;td&gt;課金開始&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;D1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5 GB ストレージ、500 万行読み取り/日、10 万行書き込み/日&lt;/td&gt;
&lt;td&gt;リクエスト拒否&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Queues&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100 万オペレーション/月&lt;/td&gt;
&lt;td&gt;課金開始&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Pages のホスティング自体は事実上無制限です。制限があるのはビルド回数（月 500 回）ですが、個人ブログで月 500 回デプロイすることはまずありません。&lt;/p&gt;
&lt;h2&gt;構成パターン&lt;/h2&gt;
&lt;h3&gt;パターン 1: 静的サイト（このブログの構成）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Pages（Astro SSG）→ 静的 HTML/CSS/JS を配信
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最もシンプルな構成で、Workers も KV も使いません。ビルド回数以外に上限を気にする要素がなく、無料枠を超える心配は事実上ゼロです。&lt;/p&gt;
&lt;p&gt;ブログ、ポートフォリオ、ドキュメントサイトはこの構成で十分です。&lt;/p&gt;
&lt;h3&gt;パターン 2: 静的サイト + 簡易 API&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Pages（フロント）+ Pages Functions（API）+ KV（データ）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;お問い合わせフォームやアクセスカウンターなど、軽量な動的機能を追加するパターンです。Pages Functions は Workers と同じリクエスト上限（10 万/日）を共有します。&lt;/p&gt;
&lt;p&gt;KV の書き込み上限は 1,000 回/日とやや厳しいため、書き込み頻度が高い機能には不向きです。閲覧カウンターのように読み取りが中心のユースケースに適しています。&lt;/p&gt;
&lt;h3&gt;パターン 3: アプリ + DB + ストレージ&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Pages（フロント）+ Workers（API）+ D1（DB）+ R2（ファイル）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;フルスタックの個人開発アプリ向けです。D1 の読み取り上限は 500 万行/日と余裕がありますが、書き込みは 10 万行/日です。&lt;/p&gt;
&lt;p&gt;R2 はストレージが 10 GB まで無料で、egress（下り転送量）は無制限です。画像や PDF のホスティングに向いています。S3 互換 API なので、既存の S3 クライアントがそのまま使えるのも利点です。&lt;/p&gt;
&lt;h2&gt;無料枠に収めるための設計のコツ&lt;/h2&gt;
&lt;h3&gt;Workers のリクエスト数を減らす&lt;/h3&gt;
&lt;p&gt;10 万リクエスト/日は多いように見えますが、SPA のバックエンド API として使うと意外に消費します。以下の工夫で節約できます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;静的化できるレスポンスは Pages で配信する&lt;/strong&gt;。Workers を経由させない&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cache API を使う&lt;/strong&gt;。Workers 内で &lt;code&gt;caches.default&lt;/code&gt; にレスポンスをキャッシュすれば、同じリクエストに対する Workers の実行回数を減らせる&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;バッチ API を設計する&lt;/strong&gt;。フロントエンドから 10 回 fetch する代わりに、1 回のリクエストでまとめて取得できる API を作る&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;KV の書き込み上限に注意する&lt;/h3&gt;
&lt;p&gt;KV の読み取りは 10 万回/日ですが、書き込みは 1,000 回/日です。2 桁違います。&lt;/p&gt;
&lt;p&gt;セッション管理のように頻繁に書き込むユースケースには向きません。設定値やキャッシュのように「書き込みは稀、読み取りは頻繁」なデータに使います。頻繁な書き込みが必要なら D1 を検討してください。&lt;/p&gt;
&lt;h3&gt;D1 は行数ベースで考える&lt;/h3&gt;
&lt;p&gt;D1 の制限は「行数」ベースで、「リクエスト数」ではありません。&lt;code&gt;SELECT * FROM users&lt;/code&gt; が 100 行返せば、100 行分の読み取りとしてカウントされます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-- 悪い例: 全件取得して JS で絞り込む
SELECT * FROM logs

-- 良い例: WHERE で絞ってから取得する
SELECT * FROM logs WHERE created_at &amp;gt; date(&apos;now&apos;, &apos;-7 days&apos;) LIMIT 100
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不要な行を読まない設計が、そのまま無料枠の節約になります。&lt;/p&gt;
&lt;h3&gt;R2 の Class A / B を理解する&lt;/h3&gt;
&lt;p&gt;R2 の操作は Class A（変更系 + LIST: PUT, POST, DELETE, LIST）と Class B（読み取り系: GET, HEAD）に分類されます。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;無料上限&lt;/th&gt;
&lt;th&gt;主な操作&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;100 万回/月&lt;/td&gt;
&lt;td&gt;PUT, POST, DELETE, LIST&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;1,000 万回/月&lt;/td&gt;
&lt;td&gt;GET, HEAD&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;画像を CDN 的に配信する場合、読み取り（Class B）は 1,000 万回/月あるので十分です。一方で LIST 操作（ファイル一覧の取得）は Class A にカウントされます。管理画面でファイル一覧を頻繁にリロードすると、Class A の消費が早まります。&lt;/p&gt;
&lt;h2&gt;モニタリング&lt;/h2&gt;
&lt;p&gt;無料枠の消費状況は Cloudflare ダッシュボードの &lt;strong&gt;Analytics&lt;/strong&gt; セクションで確認できます。Workers の場合は Workers &amp;amp; Pages → 該当 Worker → Metrics から日次のリクエスト数を見られます。&lt;/p&gt;
&lt;p&gt;上限に近づいたときにアラートを受け取りたい場合は、ダッシュボードの &lt;strong&gt;Notifications&lt;/strong&gt; から設定できます。無料枠でも通知設定は利用可能です。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Cloudflare の無料枠は個人開発には十分すぎる水準だが、サービスごとの上限は把握しておく必要がある&lt;/li&gt;
&lt;li&gt;静的サイトなら Pages のみで事実上無制限。動的機能を追加する場合は Workers のリクエスト数と KV の書き込み上限がボトルネックになりやすい&lt;/li&gt;
&lt;li&gt;D1 は行数ベース、R2 は操作クラスベースの課金体系。不要なデータを読まない設計がそのまま節約になる&lt;/li&gt;
&lt;li&gt;Pages と Workers の使い分けについては&lt;a href=&quot;/posts/cloudflare-pages-vs-workers&quot;&gt;前回の記事&lt;/a&gt;を、Pages そのものの詳細は &lt;a href=&quot;/posts/cloudflare-pages-introduction&quot;&gt;Cloudflare Pages 入門&lt;/a&gt;を参照&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>Tech</category><category>Cloudflare</category><category>Cloudflare Pages</category><category>Cloudflare Workers</category><category>個人開発</category></item><item><title>ターミナル出力のシークレットをパイプで隠す CLI「mask-pipe」を作った</title><link>https://blog.c12o.net/posts/mask-pipe-terminal-secret-masking/</link><guid isPermaLink="true">https://blog.c12o.net/posts/mask-pipe-terminal-secret-masking/</guid><description>画面共有中に env や docker logs を映して API キーが流れる瞬間を防ぐため、Go 製 CLI mask-pipe を作りました。パイプに繋ぐだけで出力中のシークレットを伏せられます。誤検知を抑えるため組み込みパターンは 8 つに絞り、TOML 設定で足せます。</description><pubDate>Fri, 17 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;画面共有で &lt;code&gt;env&lt;/code&gt; を叩いてヒヤッとしたことがある&lt;/h2&gt;
&lt;p&gt;ペアプロ中に環境変数を確認しようと &lt;code&gt;env&lt;/code&gt; を叩くと、相手の画面に AWS のアクセスキーがそのまま流れます。&lt;code&gt;docker logs&lt;/code&gt; に平文のトークンが出力される、CI のジョブログに DB 接続 URL が出る、という形でも同じ漏れ方をします。&lt;/p&gt;
&lt;p&gt;この「ターミナル出力経由でシークレットが漏れる瞬間」を塞ぐために、&lt;a href=&quot;https://github.com/c12o-dev/mask-pipe&quot;&gt;mask-pipe&lt;/a&gt; という Go 製の CLI を作りました。&lt;a href=&quot;https://github.com/trufflesecurity/trufflehog&quot;&gt;TruffleHog&lt;/a&gt; がリポジトリを守り、&lt;a href=&quot;https://github.com/secretlint/secretlint&quot;&gt;secretlint&lt;/a&gt; がコミットを守るのに対して、mask-pipe は &lt;strong&gt;ターミナル画面&lt;/strong&gt; を守る役割を担います。v0.1.1 をリリースしたので、設計と使い方をまとめておきます。&lt;/p&gt;
&lt;h2&gt;デモ&lt;/h2&gt;
&lt;p&gt;使い方はシンプルで、隠したい出力の先にパイプで繋ぐだけです。&lt;/p&gt;
&lt;p&gt;:::photo{title=&quot;env | mask-pipe の実行デモ&quot;}
&lt;img src=&quot;/images/posts/mask-pipe-terminal-secret-masking/demo.gif&quot; alt=&quot;env と docker logs の出力がパイプ経由でマスクされる様子&quot; /&gt;
:::&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ env | mask-pipe
AWS_ACCESS_KEY_ID=AKIA************MPLE
AWS_SECRET_ACCESS_KEY=wJal********************************EKEY
DATABASE_URL=postgres://admin:****@db.example.com:5432/mydb
GITHUB_TOKEN=ghp_********************************ef01
STRIPE_SK=sk_live_********************************p7dc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Docker や Kubernetes のログもそのまま流せます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ docker logs -f my-app 2&amp;gt;&amp;amp;1 | mask-pipe
$ kubectl logs pod-name | mask-pipe --show-tail 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;本番運用のコマンドに噛ませる前に、何が置換されるかを確認したいときは &lt;code&gt;--dry-run&lt;/code&gt; を使います。元の出力はそのまま流れ、マスク対象があった行だけが報告されます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ terraform plan | mask-pipe --dry-run
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;インストール&lt;/h2&gt;
&lt;p&gt;静的ビルドのシングルバイナリとして配布しています。Homebrew・&lt;code&gt;go install&lt;/code&gt;・バイナリ直ダウンロードのいずれでも入ります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ brew install c12o-dev/tap/mask-pipe
$ go install github.com/c12o-dev/mask-pipe/cmd/mask-pipe@latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;macOS（Intel / Apple Silicon）、Linux（amd64 / arm64）、Windows（amd64）を公式ビルド対象にしています。依存ライブラリは一切なく、ランタイムも不要です。&lt;/p&gt;
&lt;h2&gt;組み込みパターンは 8 つだけ&lt;/h2&gt;
&lt;p&gt;v1 の時点では以下の 8 パターンだけを既定で有効にしています。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ID&lt;/th&gt;
&lt;th&gt;検出対象&lt;/th&gt;
&lt;th&gt;マスク例&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;aws_access_key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AKIA&lt;/code&gt; + 16 文字の AWS アクセスキー ID&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AKIA************MPLE&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;aws_secret_key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;aws_secret_access_key=...&lt;/code&gt; の値部分&lt;/td&gt;
&lt;td&gt;&lt;code&gt;wJal****EKEY&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;github_token&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ghp_&lt;/code&gt; / &lt;code&gt;gho_&lt;/code&gt; / &lt;code&gt;ghu_&lt;/code&gt; / &lt;code&gt;ghs_&lt;/code&gt; / &lt;code&gt;ghr_&lt;/code&gt; で始まるトークン&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ghp_****1234&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;github_pat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;github_pat_&lt;/code&gt; で始まる fine-grained PAT&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gith****BBcc&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stripe_key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sk_live_&lt;/code&gt; / &lt;code&gt;sk_test_&lt;/code&gt; / &lt;code&gt;pk_live_&lt;/code&gt; / &lt;code&gt;pk_test_&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sk_l****p7dc&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;jwt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ドット区切り 3 セグメントの base64url&lt;/td&gt;
&lt;td&gt;&lt;code&gt;eyJh****sR8U&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;db_url_password&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;scheme://user:pass@host&lt;/code&gt; のパスワード部分&lt;/td&gt;
&lt;td&gt;&lt;code&gt;://user:****@host&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pem_private_key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-----BEGIN ... PRIVATE KEY-----&lt;/code&gt; ブロック&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[REDACTED PRIVATE KEY]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;全パターンの仕様は &lt;a href=&quot;https://github.com/c12o-dev/mask-pipe/blob/main/docs/specs/002-pattern-library.md&quot;&gt;&lt;code&gt;docs/specs/002-pattern-library.md&lt;/code&gt;&lt;/a&gt; に書きました。&lt;/p&gt;
&lt;p&gt;設計上のいちばん大事なポリシーは &lt;strong&gt;「再現率より精度を優先する」&lt;/strong&gt; ことです。通常の出力を壊す誤検知 1 件は、見逃し 10 件よりユーザーの信頼を損ないます。TruffleHog の 800 以上あるパターンをそのまま持ち込まなかったのは、誤検知率をコントロールしきれないと判断したためです。自分たちで精度 99% を保証できる 8 パターンから始めて、少しずつ足していく方針にしています。&lt;/p&gt;
&lt;p&gt;細かい話だと、Stripe キーの正規表現に &lt;code&gt;\b&lt;/code&gt; アンカーを付けるかで悩みました。アンカーがないと &lt;code&gt;disk_test_xxx&lt;/code&gt; のようなカラム名を誤検出することを、パターンレビュー用のエージェントが見つけました。そこで全パターンに単語境界を入れてあります。PEM 秘密鍵は単一行では一致できません。そのため ADR 0004 に書いた &lt;strong&gt;BEGIN/END マーカー方式のマルチラインバッファリング&lt;/strong&gt; で実装しています。バッファ上限の 100 行または 64KB を超えたら、fail-open でそのまま流します。&lt;/p&gt;
&lt;h2&gt;設定でカスタマイズ&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;~/.mask-pipe.toml&lt;/code&gt; を置くだけでパターンの切り替え、カスタムパターンの追加、allowlist、表示スタイルの調整ができます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[builtin]
jwt = false   # JWT 検出をオフに

[[custom]]
name    = &quot;internal-api-key&quot;
pattern = &apos;mycompany-key-[a-zA-Z0-9]{32}&apos;

[[allowlist]]
name    = &quot;aws-doc-example&quot;
pattern = &apos;AKIAIOSFODNN7EXAMPLE&apos;

[display]
mask_char = &quot;*&quot;
show_tail = 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;allowlist は「公式ドキュメントのサンプルキー」など、既知の安全な値をマスクしないためのエスケープハッチです。&lt;code&gt;--mask-char &apos;#&apos;&lt;/code&gt; や &lt;code&gt;--show-tail 0&lt;/code&gt; のような CLI フラグでも同じ調整ができます。&lt;/p&gt;
&lt;p&gt;設定の検証には &lt;code&gt;mask-pipe doctor&lt;/code&gt;、有効パターンの確認には &lt;code&gt;mask-pipe list-patterns&lt;/code&gt; を用意しました。トラブル時はまずこの 2 つを叩いてもらう想定です。&lt;/p&gt;
&lt;h2&gt;設計で悩んだところ&lt;/h2&gt;
&lt;h3&gt;なぜ「パイプ専用」にしたのか&lt;/h3&gt;
&lt;p&gt;最初は 1Password の &lt;code&gt;op run&lt;/code&gt; のように、シェルに仕込んでおけば全ての子プロセスの stdout を透過的にマスクするモードも検討しました。ただ、&lt;code&gt;op run&lt;/code&gt; には &lt;a href=&quot;https://www.1password.community/discussions/developers/op-run-changes-stdout-and-stderr-to-not-be-ttys-when-masking/26040&quot;&gt;TTY が潰れて TUI アプリが正しく表示されなくなる&lt;/a&gt; という有名な問題があります。同じ問題を避けるため、mask-pipe は &lt;strong&gt;明示的にパイプで繋いだときだけ動く&lt;/strong&gt; 設計にしました（ADR 0002）。色付き出力も対話プロンプトもそのまま動きます。PTY プロキシ方式の &lt;code&gt;mask-pipe shell&lt;/code&gt; は v2 で再検討する予定です。&lt;/p&gt;
&lt;h3&gt;なぜ Go なのか&lt;/h3&gt;
&lt;p&gt;単一バイナリ配布が絶対条件で、OpenSSL の動的リンクで起きるトラブルを避けたかったので Go を選びました（ADR 0001）。標準の &lt;code&gt;regexp&lt;/code&gt; パッケージは &lt;a href=&quot;https://github.com/google/re2&quot;&gt;RE2&lt;/a&gt; を採用しており、&lt;strong&gt;計算量が入力長に対して線形&lt;/strong&gt; であることが保証されます。ストリーミングで遅延を予測できるのは、リアルタイムに流れるログをフィルタする CLI にとってかなり重要でした。&lt;code&gt;goreleaser&lt;/code&gt; で Homebrew・Scoop・APT を一気に配布できるのも楽です。&lt;/p&gt;
&lt;h3&gt;TruffleHog と競合にしない&lt;/h3&gt;
&lt;p&gt;比較表は README に詳しく書きましたが、要点だけ書くとこうなります。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;mask-pipe&lt;/th&gt;
&lt;th&gt;TruffleHog&lt;/th&gt;
&lt;th&gt;secretlint&lt;/th&gt;
&lt;th&gt;GitHub &lt;code&gt;add-mask&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;守る対象&lt;/td&gt;
&lt;td&gt;ターミナル出力&lt;/td&gt;
&lt;td&gt;Git リポジトリ&lt;/td&gt;
&lt;td&gt;コミット前のファイル&lt;/td&gt;
&lt;td&gt;CI ログ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;実行タイミング&lt;/td&gt;
&lt;td&gt;リアルタイム（pipe）&lt;/td&gt;
&lt;td&gt;事後スキャン&lt;/td&gt;
&lt;td&gt;pre-commit&lt;/td&gt;
&lt;td&gt;CI 実行時&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ローカルで動く&lt;/td&gt;
&lt;td&gt;○&lt;/td&gt;
&lt;td&gt;○&lt;/td&gt;
&lt;td&gt;○&lt;/td&gt;
&lt;td&gt;×（CI 専用）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;いずれも &lt;strong&gt;補完関係&lt;/strong&gt; にあり、全部組み合わせるのが現実的な防御ラインだと考えています。&lt;/p&gt;
&lt;h2&gt;既知の限界&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TUI アプリはラップできません。&lt;/strong&gt; これはパイプで動かすことを選んだ時点での意図的な制約です。&lt;code&gt;vim&lt;/code&gt; や &lt;code&gt;docker run -it&lt;/code&gt; を巻き込んだ瞬間に操作不能になります。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;エントロピーベースの検出は v1 では採用していません。&lt;/strong&gt; Gitleaks 系の誤検知が許容しきれない領域だったので、パターンマッチ一本で割り切りました。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PTY プロキシ（&lt;code&gt;mask-pipe shell&lt;/code&gt;）は v2 で検討中です。&lt;/strong&gt; 「いちいち &lt;code&gt;| mask-pipe&lt;/code&gt; と書くのが面倒」という声への答えですが、TTY 問題を再発させずに実装する道筋が見えてから着手します。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;おわりに&lt;/h2&gt;
&lt;p&gt;公開場所は &lt;a href=&quot;https://github.com/c12o-dev/mask-pipe&quot;&gt;github.com/c12o-dev/mask-pipe&lt;/a&gt; です。&lt;code&gt;brew install c12o-dev/tap/mask-pipe&lt;/code&gt; 一発で入ります。想定している使い手は、画面共有で一度でもヒヤッとしたことがある人です。フィードバックは GitHub Discussions で歓迎します。Star も貰えたら嬉しいです。&lt;/p&gt;
</content:encoded><category>Tech</category><category>Go</category><category>CLI</category><category>セキュリティ</category><category>mask-pipe</category><category>ターミナル</category></item><item><title>SQLite WASM + OPFS でブラウザにリレーショナル DB を持ち込む</title><link>https://blog.c12o.net/posts/sqlite-wasm-opfs/</link><guid isPermaLink="true">https://blog.c12o.net/posts/sqlite-wasm-opfs/</guid><description>SQLite WASM を OPFS と組み合わせ、ブラウザ内でリレーショナル DB を永続化する方法。IndexedDB との比較表、導入手順、Worker 必須の理由と postMessage での通信、複数タブや WASM サイズの制約まで。</description><pubDate>Thu, 16 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;posts/dexie-indexeddb-guide&quot;&gt;前回の記事&lt;/a&gt;では Dexie.js で IndexedDB を実用レベルに引き上げる方法を紹介しました。IndexedDB は手軽ですが、キーバリュー型のストレージであり、複雑なクエリや JOIN は苦手です。&lt;/p&gt;
&lt;p&gt;ブラウザの中でも SQL を書きたいという要望に応えるのが &lt;a href=&quot;https://sqlite.org/wasm/doc/trunk/index.md&quot;&gt;SQLite WASM&lt;/a&gt; です。&lt;br /&gt;
SQLite の C ソースコードを WebAssembly にコンパイルし、ブラウザ上でフル機能のリレーショナル DB を動かします。&lt;br /&gt;
永続化には &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/File_System_API/Origin_private_file_system&quot;&gt;Origin Private File System（OPFS）&lt;/a&gt; を組み合わせることで、IndexedDB を超えるパフォーマンスが得られます。&lt;/p&gt;
&lt;h2&gt;IndexedDB との比較&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;IndexedDB&lt;/th&gt;
&lt;th&gt;SQLite WASM + OPFS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;データモデル&lt;/td&gt;
&lt;td&gt;キーバリュー（オブジェクトストア）&lt;/td&gt;
&lt;td&gt;リレーショナル（テーブル + SQL）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;クエリ言語&lt;/td&gt;
&lt;td&gt;JavaScript API（カーソル、インデックス）&lt;/td&gt;
&lt;td&gt;SQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JOIN&lt;/td&gt;
&lt;td&gt;不可（アプリ層で結合）&lt;/td&gt;
&lt;td&gt;可能&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;トランザクション&lt;/td&gt;
&lt;td&gt;暗黙コミット（リクエスト完了時）&lt;/td&gt;
&lt;td&gt;明示的（BEGIN / COMMIT）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;書き込み性能&lt;/td&gt;
&lt;td&gt;中程度&lt;/td&gt;
&lt;td&gt;OPFS 経由で高速&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ブラウザ対応&lt;/td&gt;
&lt;td&gt;全モダンブラウザ&lt;/td&gt;
&lt;td&gt;Chrome 102+, Firefox 111+, Safari 17.4+&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;単一テーブルの CRUD であれば IndexedDB（+ Dexie.js）で十分です。複数テーブルの結合や集計が頻繁に発生するなら、SQLite の方が自然に書けます。&lt;/p&gt;
&lt;h2&gt;セットアップ&lt;/h2&gt;
&lt;p&gt;公式の SQLite WASM ビルドを使う方法と、コミュニティのラッパーライブラリを使う方法があります。ここでは npm パッケージとして使いやすい &lt;a href=&quot;https://www.npmjs.com/package/@sqlite.org/sqlite-wasm&quot;&gt;@sqlite.org/sqlite-wasm&lt;/a&gt; を紹介します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ pnpm add @sqlite.org/sqlite-wasm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;SQLite WASM は Web Worker 内で動かすのが推奨です。OPFS の同期アクセス API（&lt;code&gt;createSyncAccessHandle&lt;/code&gt;）は Worker 専用のため、メインスレッドからは使えません。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// sqlite-worker.ts
import sqlite3InitModule from &quot;@sqlite.org/sqlite-wasm&quot;;

const sqlite3 = await sqlite3InitModule();
const db = new sqlite3.oo1.OpfsDb(&quot;/app.sqlite3&quot;);

db.exec(`
  CREATE TABLE IF NOT EXISTS tasks (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT NOT NULL,
    done INTEGER DEFAULT 0,
    created_at TEXT DEFAULT (datetime(&apos;now&apos;))
  )
`);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;OpfsDb&lt;/code&gt; を使うことで、データは OPFS 上のファイルとして永続化されます。ブラウザを閉じてもデータは残り、次回アクセス時にそのまま読み込まれます。&lt;/p&gt;
&lt;h2&gt;CRUD 操作&lt;/h2&gt;
&lt;p&gt;SQL をそのまま書けるのが最大の利点です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 作成
db.exec({
  sql: &quot;INSERT INTO tasks (title) VALUES (?)&quot;,
  bind: [&quot;記事を書く&quot;],
});

// 読み取り
const rows = db.exec({
  sql: &quot;SELECT * FROM tasks WHERE done = 0 ORDER BY created_at DESC&quot;,
  returnValue: &quot;resultRows&quot;,
  rowMode: &quot;object&quot;,
});

// 更新
db.exec({
  sql: &quot;UPDATE tasks SET done = 1 WHERE id = ?&quot;,
  bind: [1],
});

// 削除
db.exec({
  sql: &quot;DELETE FROM tasks WHERE id = ?&quot;,
  bind: [1],
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;IndexedDB ではアプリ層で実装する必要があった集計やサブクエリも、SQL の表現力でそのまま書けます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const stats = db.exec({
  sql: `
    SELECT
      done,
      COUNT(*) as count,
      MIN(created_at) as oldest
    FROM tasks
    GROUP BY done
  `,
  returnValue: &quot;resultRows&quot;,
  rowMode: &quot;object&quot;,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;OPFS とは何か&lt;/h2&gt;
&lt;p&gt;Origin Private File System は Web 標準のファイルシステム API です。通常のファイルシステムとは異なり、オリジン（ドメイン）ごとに隔離されたプライベートな領域を提供します。&lt;/p&gt;
&lt;p&gt;SQLite WASM にとって OPFS が重要な理由は &lt;strong&gt;同期的なファイル I/O&lt;/strong&gt; が可能な点です。IndexedDB 経由の永続化は非同期です。SQLite は同期的なファイルアクセスを前提に設計されているため相性が悪く、パフォーマンスが劣化します。OPFS の &lt;code&gt;createSyncAccessHandle&lt;/code&gt; はバイト単位の同期読み書きを提供するため、SQLite のネイティブに近い I/O パターンを再現できます。使える場所は Worker 内だけです。&lt;/p&gt;
&lt;h2&gt;メインスレッドとの通信&lt;/h2&gt;
&lt;p&gt;DB 操作は Worker 内で行うため、メインスレッドからは &lt;code&gt;postMessage&lt;/code&gt; で通信します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// main.ts
const worker = new Worker(
  new URL(&quot;./sqlite-worker.ts&quot;, import.meta.url),
  { type: &quot;module&quot; },
);

function query(sql: string, bind?: unknown[]): Promise&amp;lt;unknown[]&amp;gt; {
  return new Promise((resolve) =&amp;gt; {
    const id = crypto.randomUUID();
    worker.addEventListener(
      &quot;message&quot;,
      (e) =&amp;gt; {
        if (e.data.id === id) resolve(e.data.rows);
      },
      { once: true },
    );
    worker.postMessage({ id, sql, bind });
  });
}

const tasks = await query(&quot;SELECT * FROM tasks WHERE done = 0&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;実際のアプリケーションでは、この通信層を薄いラッパーとして整備し、コンポーネント側からは通常の非同期関数として呼び出せるようにしておくのがおすすめです。&lt;/p&gt;
&lt;h2&gt;制約と注意点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Worker 必須&lt;/strong&gt;&lt;br /&gt;
OPFS の同期アクセスは Worker 内でしか使えません。メインスレッドで使う場合は IndexedDB バックエンドにフォールバックしますが、パフォーマンスは OPFS より落ちます。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;同時アクセス&lt;/strong&gt;&lt;br /&gt;
同一オリジンの複数タブから同じ DB ファイルを開くと、ロック競合が発生します。SharedWorker で DB アクセスを一元化するか、Web Locks API で排他制御を設計します。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;WASM のサイズ&lt;/strong&gt;&lt;br /&gt;
SQLite WASM のバイナリは gzip 圧縮後で約 400 KB です。初回ロードのコストとして許容できるかはアプリの性質次第です。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;デバッグ&lt;/strong&gt;&lt;br /&gt;
OPFS のファイルは DevTools から直接参照しにくいのが現状です。Chrome の &lt;code&gt;navigator.storage.getDirectory()&lt;/code&gt; で探索するか、アプリ側にエクスポート機能を用意しておくと開発が楽になります。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;SQLite WASM はブラウザ上でフル機能の SQL を実行でき、OPFS と組み合わせると高速な永続化が可能&lt;/li&gt;
&lt;li&gt;IndexedDB が苦手な JOIN や集計を SQL の表現力でカバーできる&lt;/li&gt;
&lt;li&gt;Worker 内での実行が前提。メインスレッドとは &lt;code&gt;postMessage&lt;/code&gt; で通信する設計が必要&lt;/li&gt;
&lt;li&gt;複数タブの同時アクセスや WASM サイズなど、導入前に検討すべき制約がある&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;単純な CRUD なら &lt;a href=&quot;/posts/dexie-indexeddb-guide&quot;&gt;Dexie.js + IndexedDB&lt;/a&gt;、複雑なクエリが必要なら SQLite WASM + OPFS、というのが現時点での使い分けの目安です。&lt;/p&gt;
</content:encoded><category>Tech</category><category>SQLite</category><category>WebAssembly</category><category>OPFS</category><category>ローカルファースト</category></item><item><title>Cloudflare Pages と Workers の違いと使い分け</title><link>https://blog.c12o.net/posts/cloudflare-pages-vs-workers/</link><guid isPermaLink="true">https://blog.c12o.net/posts/cloudflare-pages-vs-workers/</guid><description>Cloudflare Pages は静的アセット配信、Workers はリクエスト処理が起点です。Pages Functions の制約と Workers への統合の方向性を踏まえ、静的サイト主体なら Pages、動的処理主体なら Workers という選定指針をまとめます。</description><pubDate>Wed, 15 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;Cloudflare でウェブアプリをデプロイしようとすると、最初に迷うのが「&lt;a href=&quot;https://pages.cloudflare.com/&quot;&gt;Pages&lt;/a&gt; と &lt;a href=&quot;https://workers.cloudflare.com/&quot;&gt;Workers&lt;/a&gt;、どっちを使えばいいのか」です。&lt;/p&gt;
&lt;p&gt;どちらもエッジで動き、どちらも無料枠があり、Pages には Functions という Workers 互換の機能も用意されている。公式ドキュメントを読んでも境界が曖昧に感じられます。&lt;/p&gt;
&lt;p&gt;境界は両者の設計思想の違いで決まり、それがプロジェクトの特性に応じた選定指針になります。&lt;/p&gt;
&lt;h2&gt;設計思想の違い&lt;/h2&gt;
&lt;p&gt;根本的な違いは「何を起点に設計されているか」です。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Pages&lt;/th&gt;
&lt;th&gt;Workers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;起点&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;静的アセット&lt;/strong&gt; の配信&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;リクエスト&lt;/strong&gt; の処理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;デプロイ単位&lt;/td&gt;
&lt;td&gt;Git リポジトリ（ビルド → 配信）&lt;/td&gt;
&lt;td&gt;スクリプト（wrangler deploy）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ルーティング&lt;/td&gt;
&lt;td&gt;ファイルシステムベース&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fetch&lt;/code&gt; イベントハンドラ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;プレビュー&lt;/td&gt;
&lt;td&gt;ブランチごとに自動生成&lt;/td&gt;
&lt;td&gt;自動ブランチプレビューなし&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Pages は「HTML/CSS/JS をビルドしてエッジに配る」のが主務で、動的処理は後から追加するオプションです。Workers は「リクエストを受けてレスポンスを返す」関数が出発点で、静的アセットの配信は後から追加する機能です。&lt;/p&gt;
&lt;h2&gt;Pages Functions の位置付け&lt;/h2&gt;
&lt;p&gt;Pages には &lt;a href=&quot;https://developers.cloudflare.com/pages/functions/&quot;&gt;Functions&lt;/a&gt; という機能があります。&lt;code&gt;functions/&lt;/code&gt; ディレクトリにファイルを置くと、Workers と同じランタイム（V8 Isolate）で動的処理を追加できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// functions/api/hello.ts
export const onRequestGet: PagesFunction = async (context) =&amp;gt; {
  return new Response(JSON.stringify({ message: &quot;Hello&quot; }), {
    headers: { &quot;Content-Type&quot;: &quot;application/json&quot; },
  });
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これが混乱のもとです。「Pages でも API が書けるなら Workers はいらないのでは？」と思えてきます。&lt;/p&gt;
&lt;p&gt;:::tip[この記事で登場する Cloudflare 用語]&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Durable Objects: キー単位で状態を持てる耐久オブジェクト。強整合な少量データの保持や WebSocket セッションの管理に使う&lt;/li&gt;
&lt;li&gt;Static Assets: Workers に静的ファイル配信を任せる機能。Workers 単体で SPA + API の構成が組める&lt;/li&gt;
&lt;li&gt;バインド: 別リソース（KV、D1、他の Workers など）への参照を環境に紐付ける仕組み。環境変数のリソース版&lt;/li&gt;
&lt;li&gt;Wrangler: Workers/Pages 用の公式 CLI。&lt;code&gt;wrangler deploy&lt;/code&gt; などでデプロイを実行する&lt;/li&gt;
&lt;li&gt;V8 Isolate: Chrome の JS エンジン V8 が提供する軽量なサンドボックス。Workers のランタイム基盤
:::&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;しかし Pages Functions にはいくつかの制約があります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cron Triggers が使えない&lt;/strong&gt;。定期実行が必要なら Workers が必須&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Durable Objects を直接ホストできない&lt;/strong&gt;。クラスの定義は Workers でしかできない（Pages Functions から別 Workers の Durable Objects へバインドは可能）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;デプロイがビルドと結合している&lt;/strong&gt;。静的アセットと Functions は常に同時にデプロイされるため、API だけの更新でもビルドが実行される&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ルーティングがファイルシステム固定&lt;/strong&gt;。&lt;code&gt;_middleware.ts&lt;/code&gt; による前処理や &lt;code&gt;_routes.json&lt;/code&gt; での制御はできるが、Workers の &lt;code&gt;fetch&lt;/code&gt; ハンドラのように URL パターンをコードで自由に振り分けたい構成には向かない&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;逆に言えば、これらに該当しなければ Pages Functions で十分です。&lt;/p&gt;
&lt;h2&gt;選定のフローチャート&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;flowchart TD
    A[&quot;プロジェクトの種類は？&quot;] --&amp;gt; B{&quot;静的サイト / SSG が主体？&quot;}
    B -- Yes --&amp;gt; C{&quot;API やサーバー処理は？&quot;}
    C -- 不要 --&amp;gt; D[&quot;Pages のみ&quot;]
    C -- 簡単な API --&amp;gt; E[&quot;Pages + Functions&quot;]
    C -- Cron / Durable Objects --&amp;gt; F[&quot;Pages + 別 Workers&quot;]
    B -- No --&amp;gt; G{&quot;静的アセットは？&quot;}
    G -- なし --&amp;gt; H[&quot;Workers のみ&quot;]
    G -- あり --&amp;gt; I[&quot;Workers + Static Assets&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;実際の判断基準をもう少し具体的に書くと、こうなります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ブログ、ドキュメントサイト、LP: Pages のみ。ビルド → 配信のパイプラインがそのまま使えるので、これ以上シンプルな選択肢はない&lt;/li&gt;
&lt;li&gt;SSG + お問い合わせフォーム / 簡易 API: Pages + Functions。API のために別サービスを立てる必要がない&lt;/li&gt;
&lt;li&gt;SPA + バックエンド API: Workers（Static Assets 機能で SPA を配信しつつ、同じプロジェクトで API も処理）&lt;/li&gt;
&lt;li&gt;定期バッチ、Webhook 受信、WebSocket: Workers 単体。Cron Triggers や Durable Objects は Workers でしか使えない&lt;/li&gt;
&lt;li&gt;SSG + 重めのバックエンド: Pages（フロント）+ Workers（API）の分離構成&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;統合の方向性&lt;/h2&gt;
&lt;p&gt;Cloudflare は 2024 年以降、Pages と Workers の統合を段階的に進めています。Workers に &lt;a href=&quot;https://developers.cloudflare.com/workers/static-assets/&quot;&gt;Static Assets&lt;/a&gt; 機能が追加され、Workers 単体で静的ファイルの配信が可能になりました。&lt;/p&gt;
&lt;p&gt;長期的には Workers が統一プラットフォームになり、Pages は Workers の「静的サイト向けプリセット」という位置付けになっていく方向です。実際、公式の移行ガイドも Pages から Workers への移行手順を案内しています。ただし現時点では Pages のブランチプレビューや Git 連携ビルドは Workers にはなく、静的サイトなら Pages を選ぶ理由は十分にあります。&lt;/p&gt;
&lt;p&gt;新規プロジェクトでの判断としては、以下の方針がおすすめです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;静的サイトが主体: Pages を選ぶ。Git 連携ビルドとブランチプレビューの価値が高い&lt;/li&gt;
&lt;li&gt;動的処理が主体: Workers を選ぶ。制約が少なく、将来の拡張にも対応しやすい&lt;/li&gt;
&lt;li&gt;迷ったら: Pages から始める。Pages Functions で足りなくなった時点で、その機能だけ Workers に切り出す&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Pages は「静的アセットの配信」が起点、Workers は「リクエストの処理」が起点。設計思想が根本的に違う&lt;/li&gt;
&lt;li&gt;Pages Functions は Workers 互換のランタイムだが、Cron Triggers や Durable Objects は使えない&lt;/li&gt;
&lt;li&gt;静的サイト + 簡易 API なら Pages + Functions、動的処理が主体なら Workers を選ぶ&lt;/li&gt;
&lt;li&gt;長期的には統合が進むが、現時点では Pages のブランチプレビューと Git 連携が差別化ポイント&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;このブログ自体は Astro の SSG を Pages でホスティングしており、動的処理は一切使っていません。Pages のみで完結する最もシンプルな構成です。&lt;/p&gt;
&lt;p&gt;Pages そのものの仕組みや機能を詳しく知りたい方は、&lt;a href=&quot;/posts/cloudflare-pages-introduction&quot;&gt;Cloudflare Pages とは何か：静的サイトホスティングの実力&lt;/a&gt;も参考にしてください。&lt;/p&gt;
</content:encoded><category>Tech</category><category>Cloudflare</category><category>Cloudflare Pages</category><category>Cloudflare Workers</category><category>エッジコンピューティング</category></item><item><title>Dexie.js で IndexedDB をまともに使う</title><link>https://blog.c12o.net/posts/dexie-indexeddb-guide/</link><guid isPermaLink="true">https://blog.c12o.net/posts/dexie-indexeddb-guide/</guid><description>Dexie.js v4 で IndexedDB を Promise ベースの API とスキーマ管理で扱う方法。class での DB 定義、CRUD、version を積み上げるマイグレーション、where と and の複合クエリ、bulkAdd、容量制限の注意点まで。</description><pubDate>Tue, 14 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;ブラウザには、構造化データを大量に保存できる &lt;a href=&quot;https://developer.mozilla.org/ja/docs/Web/API/IndexedDB_API&quot;&gt;IndexedDB&lt;/a&gt; が組み込まれています。&lt;br /&gt;
リレーショナルではありませんが、PWA やローカルファーストなアプリでは避けて通れないストレージです。&lt;br /&gt;
生の API はコールバックベースで冗長で、トランザクション管理が煩雑、型の恩恵もありません。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://dexie.org/&quot;&gt;Dexie.js&lt;/a&gt; は IndexedDB の上に Promise ベースの薄いラッパーを提供するライブラリです。&lt;br /&gt;
TypeScript との相性がよく、スキーマのバージョン管理も宣言的に書けます。&lt;br /&gt;
Dexie.js v4 を対象に、導入、日常的な CRUD、スキーマ移行の順に進めます。&lt;/p&gt;
&lt;h2&gt;生の IndexedDB がつらい理由&lt;/h2&gt;
&lt;p&gt;IndexedDB の API をそのまま使うと、データベースのオープンだけでこれだけのコードが必要です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const request = indexedDB.open(&quot;mydb&quot;, 1);
request.onupgradeneeded = (event) =&amp;gt; {
  const db = (event.target as IDBOpenDBRequest).result;
  if (!db.objectStoreNames.contains(&quot;tasks&quot;)) {
    db.createObjectStore(&quot;tasks&quot;, { keyPath: &quot;id&quot;, autoIncrement: true });
  }
};
request.onsuccess = (event) =&amp;gt; {
  const db = (event.target as IDBOpenDBRequest).result;
  // ここからトランザクションを開いて操作...
};
request.onerror = (event) =&amp;gt; {
  console.error(&quot;DB open failed&quot;, event);
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;イベントハンドラがネストし、型がなく、エラーハンドリングが散在します。&lt;br /&gt;
localStorage の手軽さと比べると、導入コストが高すぎます。&lt;/p&gt;
&lt;h2&gt;Dexie.js のセットアップ&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ pnpm add dexie
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;データベースの定義は class ベースで書きます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// db.ts
import Dexie, { type EntityTable } from &quot;dexie&quot;;

interface Task {
  id: number;
  title: string;
  done: boolean;
  createdAt: Date;
}

const db = new Dexie(&quot;taskdb&quot;) as Dexie &amp;amp; {
  tasks: EntityTable&amp;lt;Task, &quot;id&quot;&amp;gt;;
};

db.version(1).stores({
  tasks: &quot;++id, done, createdAt&quot;,
});

export { db };
export type { Task };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&quot;++id, done, createdAt&quot;&lt;/code&gt; はインデックスの定義です。&lt;br /&gt;
&lt;code&gt;++&lt;/code&gt; は自動採番の主キー、カンマ区切りでセカンダリインデックスを追加します。&lt;br /&gt;
ここに書かなかったプロパティもデータとしては保存されますが、インデックスで検索できないだけです。&lt;/p&gt;
&lt;h2&gt;CRUD 操作&lt;/h2&gt;
&lt;p&gt;Dexie の API は直感的です。Promise を返すので &lt;code&gt;async/await&lt;/code&gt; でそのまま書けます。&lt;/p&gt;
&lt;h3&gt;作成&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;await db.tasks.add({
  title: &quot;記事を書く&quot;,
  done: false,
  createdAt: new Date(),
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;読み取り&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 全件取得
const allTasks = await db.tasks.toArray();

// 条件付き取得
const pending = await db.tasks.where(&quot;done&quot;).equals(false).toArray();

// 主キーで取得
const task = await db.tasks.get(1);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;更新&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;await db.tasks.update(1, { done: true });
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;削除&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;await db.tasks.delete(1);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;生の IndexedDB ではトランザクションのオープン、オブジェクトストアの取得、リクエストの発行、イベントハンドラの登録…&lt;br /&gt;
と 4 ステップ必要だった操作が、1 行で完結します。&lt;/p&gt;
&lt;h2&gt;スキーマのバージョン管理&lt;/h2&gt;
&lt;p&gt;アプリの進化に伴い、テーブル構造を変更したくなるのは必然です。&lt;br /&gt;
Dexie はバージョンを積み上げる宣言的な移行をサポートしています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;db.version(1).stores({
  tasks: &quot;++id, done, createdAt&quot;,
});

db.version(2).stores({
  tasks: &quot;++id, done, createdAt, priority&quot;,
});

db.version(3)
  .stores({
    tasks: &quot;++id, done, createdAt, priority&quot;,
    tags: &quot;++id, name&quot;,
  })
  .upgrade((tx) =&amp;gt; {
    return tx
      .table(&quot;tasks&quot;)
      .toCollection()
      .modify((task) =&amp;gt; {
        task.priority = task.priority ?? &quot;medium&quot;;
      });
  });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;バージョン 2 で &lt;code&gt;priority&lt;/code&gt; インデックスを追加し、バージョン 3 で &lt;code&gt;tags&lt;/code&gt; テーブルを追加しつつ既存データにデフォルト値を埋めています。&lt;br /&gt;
&lt;code&gt;upgrade&lt;/code&gt; コールバックには、データマイグレーションも書けます。&lt;/p&gt;
&lt;p&gt;過去のバージョン定義は消さずに残す必要があります。&lt;br /&gt;
ユーザーがバージョン 1 のまま久しぶりにアクセスした場合、Dexie は 1 → 2 → 3 と順番にマイグレーションを実行してくれます。&lt;/p&gt;
&lt;h2&gt;実践的なパターン&lt;/h2&gt;
&lt;h3&gt;複合クエリ&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const recentPending = await db.tasks
  .where(&quot;done&quot;)
  .equals(false)
  .and((task) =&amp;gt; task.createdAt &amp;gt; oneWeekAgo)
  .sortBy(&quot;createdAt&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;where&lt;/code&gt; でインデックスを使った高速フィルタリングを行い、&lt;code&gt;and&lt;/code&gt; で追加条件を適用するパターンです。&lt;br /&gt;
&lt;code&gt;and&lt;/code&gt; の条件は &lt;code&gt;where&lt;/code&gt; の結果に対する JS フィルタになるため、先に &lt;code&gt;where&lt;/code&gt; で対象を減らしておきます。&lt;/p&gt;
&lt;h3&gt;一括操作&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;await db.tasks.bulkAdd([
  { title: &quot;タスク A&quot;, done: false, createdAt: new Date() },
  { title: &quot;タスク B&quot;, done: false, createdAt: new Date() },
  { title: &quot;タスク C&quot;, done: false, createdAt: new Date() },
]);

await db.tasks.where(&quot;done&quot;).equals(true).delete();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;bulkAdd&lt;/code&gt; は個別の &lt;code&gt;add&lt;/code&gt; を繰り返すより高速です。&lt;br /&gt;
内部的に 1 つのトランザクションでまとめて書き込みます。&lt;/p&gt;
&lt;h2&gt;注意点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;容量制限はブラウザ依存&lt;/strong&gt;&lt;br /&gt;
Chrome はオリジンごとにディスク空き容量の一定割合（目安で数百 MB〜数 GB）まで。&lt;br /&gt;
Safari 17 以降も同様にディスクベースの上限が適用されます。&lt;br /&gt;
ただし、ユーザーがストレージをクリアすればデータは消えるため、永続化が必須ならサーバーとの同期を別途設計しておきます。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Web Worker からもアクセス可能&lt;/strong&gt;&lt;br /&gt;
IndexedDB はメインスレッドと Web Worker の両方から使えます。&lt;br /&gt;
重い集計処理は Worker で実行すると UI がブロックされません。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;DevTools で中身を確認できる&lt;/strong&gt;&lt;br /&gt;
Chrome DevTools の Application タブ → IndexedDB からテーブルの中身をリアルタイムで確認できます。&lt;br /&gt;
デバッグ時に重宝します。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;IndexedDB はブラウザ内蔵の強力なストレージだが、生の API は実用にはつらすぎる&lt;/li&gt;
&lt;li&gt;Dexie.js を入れれば Promise ベースの直感的な API で CRUD が 1 行で書ける&lt;/li&gt;
&lt;li&gt;スキーマのバージョン管理が宣言的に書けるため、アプリの進化に追従しやすい&lt;/li&gt;
&lt;li&gt;ローカルファーストなアプリや PWA のデータ層として、導入コストに対するリターンが大きい&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;次の記事では、IndexedDB とは異なるアプローチでブラウザ内にリレーショナル DB を持ち込む &lt;a href=&quot;/posts/sqlite-wasm-opfs&quot;&gt;SQLite WASM + OPFS&lt;/a&gt; を紹介します。&lt;/p&gt;
</content:encoded><category>Tech</category><category>Dexie.js</category><category>IndexedDB</category><category>TypeScript</category><category>ローカルファースト</category></item><item><title>Claude Code を導入してターミナル中心の開発フローを作る</title><link>https://blog.c12o.net/posts/claude-code-cli-workflow/</link><guid isPermaLink="true">https://blog.c12o.net/posts/claude-code-cli-workflow/</guid><description>Claude Code のインストール、permission の事前許可、CLAUDE.md によるプロジェクト文脈の共有、バグ修正・リファクタリング・コードレビューの日常フローまで、ターミナル中心の開発フローを作る手順です。</description><pubDate>Mon, 13 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;AI コーディング支援といえば VS Code の拡張が主流です。ターミナルで完結する開発フローを好むエンジニアには、IDE 前提のツールは使いにくいことがあります。&lt;br /&gt;
&lt;a href=&quot;https://docs.anthropic.com/en/docs/claude-code/overview&quot;&gt;Claude Code&lt;/a&gt; は Anthropic が提供する CLI ベースの AI エージェントで、ターミナルから直接コードの読み書き・実行・Git 操作までを対話的に行えます。&lt;/p&gt;
&lt;p&gt;Claude Code を導入し、日常の開発フローに組み込むまでの流れは次のとおりです。&lt;/p&gt;
&lt;h2&gt;インストールと初期設定&lt;/h2&gt;
&lt;p&gt;Claude Code の利用には Anthropic の Pro・Max・Team・Enterprise、または API（Console）アカウントが必要です。&lt;br /&gt;
無料プランでは利用できません。&lt;/p&gt;
&lt;p&gt;インストール方法は 2 通りあります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Node.js 不要のネイティブインストーラー&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;$ curl -fsSL https://claude.ai/install.sh | bash
$ claude
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;npm でグローバルインストール&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;$ npm install -g @anthropic-ai/claude-code
$ claude
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;初回起動時に Anthropic アカウントとの認証が始まります。&lt;br /&gt;
ブラウザが開くので、ログインして承認すれば完了です。&lt;/p&gt;
&lt;p&gt;認証後、プロジェクトのルートディレクトリで &lt;code&gt;claude&lt;/code&gt; を実行するだけで対話が始まります。&lt;br /&gt;
Claude Code は起動時にディレクトリ構造やファイルの内容を読み取り、プロジェクトの文脈を自動的に把握します。&lt;/p&gt;
&lt;h2&gt;基本の使い方&lt;/h2&gt;
&lt;p&gt;Claude Code の操作はシンプルです。ターミナルに自然言語でやりたいことを伝えるだけで動きます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ claude
&amp;gt; このプロジェクトの構成を教えて
&amp;gt; src/utils/auth.ts のバリデーションにメールアドレス形式のチェックを追加して
&amp;gt; 直近の変更をコミットして
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ファイルの読み取り・編集・作成、シェルコマンドの実行、Git 操作など、開発に必要な操作は一通りカバーされています。&lt;br /&gt;
破壊的な操作（ファイル削除や &lt;code&gt;git push&lt;/code&gt; など）の前には確認プロンプトが出るため、意図しない変更が実行される心配はありません。&lt;/p&gt;
&lt;h3&gt;ワンショット実行&lt;/h3&gt;
&lt;p&gt;対話モードに入らず、結果を標準出力に返す &lt;code&gt;-p&lt;/code&gt;（&lt;code&gt;--print&lt;/code&gt;）オプションも便利です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ claude -p &quot;package.json の依存パッケージで非推奨になっているものを教えて&quot;
$ claude -p &quot;src/components/Header.tsx を TypeScript の strict モードに対応させて&quot; --allowedTools Edit,Write
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CI やスクリプトに組み込む場合はこちらを使います。&lt;/p&gt;
&lt;h2&gt;permission の設定&lt;/h2&gt;
&lt;p&gt;Claude Code を快適に使ううえで重要なのが &lt;code&gt;.claude/settings.json&lt;/code&gt; による permission 設定です。&lt;br /&gt;
デフォルトでは Bash コマンドの実行ごとに承認が必要ですが、安全なコマンドは事前に許可しておけます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;permissions&quot;: {
    &quot;allow&quot;: [
      &quot;Bash(pnpm lint)&quot;,
      &quot;Bash(pnpm check)&quot;,
      &quot;Bash(pnpm build)&quot;,
      &quot;Bash(git status)&quot;,
      &quot;Bash(git diff*)&quot;,
      &quot;Bash(git log*)&quot;
    ],
    &quot;deny&quot;: [
      &quot;Bash(git push --force*)&quot;
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;リント・ビルド・Git の読み取り系は &lt;code&gt;allow&lt;/code&gt; に入れ、&lt;code&gt;git push --force&lt;/code&gt; のような破壊的コマンドは &lt;code&gt;deny&lt;/code&gt; で明示的にブロックします。&lt;br /&gt;
これがないと、エージェントが毎回承認を求めてきて作業のテンポが崩れます。&lt;/p&gt;
&lt;h2&gt;CLAUDE.md でプロジェクト文脈を渡す&lt;/h2&gt;
&lt;p&gt;プロジェクトのルートに &lt;a href=&quot;https://docs.anthropic.com/en/docs/claude-code/memory#claudemd&quot;&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt;&lt;/a&gt; を置くと、毎セッション自動的に読み込まれます。&lt;br /&gt;
ここにスタック情報・コーディング規約・よく使うコマンドを書いておくと「この repo は何？」から始まる説明コストがゼロになります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## Stack
- Astro 5 + Svelte 5 + Tailwind 3
- Package manager: pnpm
- Formatter: Biome (tabs, double quotes)

## Commands
| Purpose | Command |
|---|---|
| Dev server | `pnpm dev` |
| Build | `pnpm build` |
| Lint | `pnpm lint` |
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CLAUDE.mdは短く保つのが推奨されています。&lt;br /&gt;
プロジェクトが大きくなって情報量が増えたら、&lt;a href=&quot;/posts/claude-code-context-layers&quot;&gt;slash command やサブエージェントに切り出す方法&lt;/a&gt;を検討してください。&lt;/p&gt;
&lt;h2&gt;日常ワークフローの例&lt;/h2&gt;
&lt;p&gt;実際にこのブログの運用で使っている典型的なフローを紹介します。&lt;/p&gt;
&lt;h3&gt;バグ修正&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; ビルドで Warning が出ている。原因を調べて修正して
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Claude Code がビルドを実行し、Warning のスタックトレースを読み取り、該当ファイルを特定して修正案を提示します。&lt;br /&gt;
修正内容の diff が表示されるので、承認すればそのまま適用されます。&lt;/p&gt;
&lt;h3&gt;リファクタリング&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; src/utils/ にある日付関連のヘルパーが 3 ファイルに散らばっている。1 つに統合して
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;影響範囲の調査（import の追跡）から、ファイルの統合、既存の参照箇所の書き換えまでを一気に行います。&lt;br /&gt;
途中で判断に迷う箇所があれば質問が返ってくるので、対話しながら進められます。&lt;/p&gt;
&lt;h3&gt;コードレビュー&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; git diff main...HEAD の変更をレビューして。セキュリティとパフォーマンスの観点で
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ブランチの差分を読んで、問題点を指摘してくれます。&lt;br /&gt;
PR を出す前のセルフレビューとして使うと、人間のレビュアーの負荷を減らせます。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Claude Code はターミナルから AI エージェントとペアプログラミングできる CLI ツール&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.claude/settings.json&lt;/code&gt; の permission 設定で安全なコマンドを事前許可し、作業テンポを確保する&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; にプロジェクト文脈を書いておけば、毎セッションの説明コストがなくなる&lt;/li&gt;
&lt;li&gt;バグ修正・リファクタリング・コードレビューなど、日常の開発タスクにそのまま組み込める&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;まずは &lt;code&gt;npm install -g @anthropic-ai/claude-code&lt;/code&gt; でインストールし、&lt;br /&gt;
既存プロジェクトのディレクトリで &lt;code&gt;claude&lt;/code&gt; を起動してみるところから始めるのがおすすめです。&lt;/p&gt;
</content:encoded><category>Tech</category><category>Claude Code</category><category>AI</category><category>開発ツール</category><category>CLI</category></item><item><title>Astro の OGP 画像生成で日本語の改行を自然にする</title><link>https://blog.c12o.net/posts/astro-og-image-generation/</link><guid isPermaLink="true">https://blog.c12o.net/posts/astro-og-image-generation/</guid><description>Astro + Cloudflare Pages の OGP 画像生成で、sharp がランタイムで動かない問題は prerender: true と @resvg/resvg-js で、日本語タイトルが文節の途中で折り返される問題は budoux で解決しました。</description><pubDate>Sun, 12 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;ブログ記事を SNS でシェアしたとき、OGP 画像の有無でクリック率は大きく変わります。&lt;br /&gt;
Astro なら &lt;a href=&quot;https://github.com/vercel/satori&quot;&gt;satori&lt;/a&gt; を使ってビルド時に自動生成する方法がよく使われますが、&lt;br /&gt;
&lt;strong&gt;Cloudflare Pages にデプロイする日本語ブログ&lt;/strong&gt;ではいくつかの罠があります。&lt;/p&gt;
&lt;p&gt;このブログで実際に踏んだ問題は次の 2 つです。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;sharp&lt;/code&gt; が Cloudflare のランタイムで動かない&lt;/li&gt;
&lt;li&gt;日本語タイトルが文字単位で折返され、文節の途中で改行される&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;構成の全体像&lt;/h2&gt;
&lt;p&gt;最終的に採用したパイプラインは以下の通りです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flowchart LR
    A[&quot;getStaticPaths()&quot;] --&amp;gt; B[&quot;satori&amp;lt;br/&amp;gt;HTML → SVG&quot;]
    B --&amp;gt; C[&quot;resvg&amp;lt;br/&amp;gt;SVG → PNG&quot;]
    C --&amp;gt; D[&quot;dist/og/slug.png&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ポイントは &lt;code&gt;export const prerender = true&lt;/code&gt; を宣言し、&lt;br /&gt;
&lt;strong&gt;ビルド時に全投稿分の OGP を静的ファイルとして出力する&lt;/strong&gt;点です。ランタイムでは一切画像生成をしません。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// src/pages/og/[slug].png.ts
export const prerender = true;

export async function getStaticPaths() {
  const posts = await getCollection(&quot;posts&quot;);
  return posts.map((post) =&amp;gt; ({
    params: { slug: post.slug },
    props: { post },
  }));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;記事ページ側では、&lt;code&gt;&amp;lt;meta property=&quot;og:image&quot;&amp;gt;&lt;/code&gt; に &lt;code&gt;/og/${slug}.png&lt;/code&gt; を指定しておけば、&lt;br /&gt;
ビルドで生成された静的ファイルがそのまま配信されます。&lt;/p&gt;
&lt;h2&gt;なぜ sharp ではダメだったのか&lt;/h2&gt;
&lt;p&gt;Astro の OGP 画像生成で検索すると、多くの記事が &lt;code&gt;satori&lt;/code&gt; + &lt;code&gt;sharp&lt;/code&gt; の組み合わせを紹介しています。&lt;br /&gt;
僕も最初はこの構成でした。&lt;/p&gt;
&lt;p&gt;ところが Cloudflare Pages では &lt;strong&gt;sharp がランタイムで動きません&lt;/strong&gt;。&lt;br /&gt;
sharp は C++ の native binding に依存しており、Cloudflare の V8 Isolate 環境では実行できないからです。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;prerender: true&lt;/code&gt; を付ければビルド時（= Node.js 環境）で実行されるため sharp でも動きますが、&lt;br /&gt;
この宣言を忘れると Astro は SSR ルート扱いにします。&lt;br /&gt;
結果として本番デプロイ後に OGP エンドポイントが 5xx を返し、SNS のクローラーが画像を取得できなくなります。&lt;/p&gt;
&lt;p&gt;解決策は &lt;code&gt;sharp&lt;/code&gt; を &lt;a href=&quot;https://github.com/yisibl/resvg-js&quot;&gt;&lt;code&gt;@resvg/resvg-js&lt;/code&gt;&lt;/a&gt; に置き換えることです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Resvg } from &quot;@resvg/resvg-js&quot;;

// satori が返した SVG 文字列を PNG に変換
const resvg = new Resvg(svg, {
  fitTo: { mode: &quot;width&quot;, value: 1200 },
});
const png = resvg.render().asPng();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;@resvg/resvg-js&lt;/code&gt; も native Node binding ですが、&lt;br /&gt;
&lt;code&gt;prerender: true&lt;/code&gt; と組み合わせることでビルド時にしか実行されないため、ランタイム非対応の問題を回避できます。&lt;/p&gt;
&lt;p&gt;なお、native binding を含むパッケージは Astro の server bundle に含まれるとビルドが失敗します。&lt;br /&gt;
&lt;code&gt;astro.config.mjs&lt;/code&gt; で明示的に除外します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// astro.config.mjs
vite: {
  ssr: {
    external: [&quot;@resvg/resvg-js&quot;, &quot;satori&quot;, &quot;satori-html&quot;],
  },
},
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;日本語タイトルの改行問題&lt;/h2&gt;
&lt;p&gt;ここまでで OGP 画像は生成できるようになりましたが、日本語ブログ特有の問題が残ります。&lt;/p&gt;
&lt;p&gt;satori のテキストレイアウトエンジンは &lt;strong&gt;文字単位&lt;/strong&gt; で折返しを判定します。&lt;br /&gt;
英語なら単語境界（スペース）で自然に切れますが、日本語にはスペースによる単語区切りがないため、&lt;br /&gt;
「渡す」が「渡」と「す」に分断されるような不自然な改行が発生しました。&lt;/p&gt;
&lt;h3&gt;ZWSP は使えない&lt;/h3&gt;
&lt;p&gt;最初に試したのは zero-width space（&lt;code&gt;U+200B&lt;/code&gt;）を文節境界に挿入して &lt;code&gt;word-break: keep-all&lt;/code&gt; と組み合わせる方法でした。&lt;br /&gt;
CSS の仕様上これが正攻法ですが、&lt;br /&gt;
&lt;strong&gt;satori は ZWSP をグリフとして描画してしまい、□（tofu）が表示される&lt;/strong&gt;という問題がありました。&lt;/p&gt;
&lt;h3&gt;budoux で解決&lt;/h3&gt;
&lt;p&gt;最終的に採用したのは、Google 製の日本語改行ライブラリ &lt;a href=&quot;https://github.com/google/budoux&quot;&gt;budoux&lt;/a&gt; です。&lt;br /&gt;
budoux は学習済みモデルで日本語の文節境界を推定し、自然な位置で分割してくれます。&lt;/p&gt;
&lt;p&gt;アプローチは以下の通りです。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;budoux&lt;/code&gt; でタイトルを文節に分割する&lt;/li&gt;
&lt;li&gt;フォントサイズとコンテナ幅から 1 行あたりの最大文字幅を計算する&lt;/li&gt;
&lt;li&gt;文節を貪欲法で行に詰め、上限を超えたら次の行へ送る&lt;/li&gt;
&lt;li&gt;各行を個別の &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; として satori に渡す&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;import { loadDefaultJapaneseParser } from &quot;budoux&quot;;

const jpParser = loadDefaultJapaneseParser();

function estimateWidth(text: string): number {
  let w = 0;
  for (const ch of text) {
    w += ch.charCodeAt(0) &amp;gt; 0x7f ? 1 : 0.55;
  }
  return w;
}

function splitTitleIntoLines(title: string, fontSize: number): string[] {
  const maxUnits = (CONTENT_WIDTH / fontSize) * 0.95;
  const phrases = jpParser.parse(title);
  const lines: string[] = [];
  let current = &quot;&quot;;

  for (const phrase of phrases) {
    const next = current + phrase;
    if (estimateWidth(next) &amp;gt; maxUnits &amp;amp;&amp;amp; current.length &amp;gt; 0) {
      lines.push(current);
      current = phrase;
    } else {
      current = next;
    }
  }
  if (current) lines.push(current);

  return lines;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;estimateWidth&lt;/code&gt; では CJK 文字を幅 &lt;code&gt;1.0&lt;/code&gt;、ASCII 文字を &lt;code&gt;0.55&lt;/code&gt; として近似しています。&lt;br /&gt;
行折返しの判定にはこの近似で実用上十分な精度が出るため、正確なグリフ幅測定はしていません。&lt;/p&gt;
&lt;h3&gt;Before / After&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;記事タイトル&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/posts/claude-code-context-layers&quot;&gt;Claude Code にプロジェクト文脈を渡す 4 層構造&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;...文脈を渡&lt;/code&gt; / &lt;code&gt;す 4 層構造&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;...プロジェクト文脈を&lt;/code&gt; / &lt;code&gt;渡す 4 層構造&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SubCutter - ブラウザ完結の固定費管理PWAを作った&lt;/td&gt;
&lt;td&gt;&lt;code&gt;...完結の固定費&lt;/code&gt; / &lt;code&gt;管理PWAを作った&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;...ブラウザ完結の&lt;/code&gt; / &lt;code&gt;固定費管理PWAを作った&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;はまりポイントまとめ&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;prerender: true&lt;/code&gt; を忘れると本番で動かない&lt;/strong&gt;&lt;br /&gt;
Cloudflare Pages + Astro の組み合わせでは、native binding を使うエンドポイントには必ず &lt;code&gt;prerender: true&lt;/code&gt; を付ける。付け忘れると開発環境では動くのに本番で 5xx になる。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;satori は全要素に &lt;code&gt;display: flex&lt;/code&gt; が必要&lt;/strong&gt;&lt;br /&gt;
satori のレイアウトエンジンは &lt;a href=&quot;https://github.com/facebook/yoga&quot;&gt;Yoga&lt;/a&gt; ベースで、すべての要素が Flexbox として評価される。&lt;code&gt;display: flex&lt;/code&gt; を省略すると予期しないレイアウト崩れが起きる。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;satori-html&lt;/code&gt; の tagged template は値を HTML エスケープする&lt;/strong&gt;&lt;br /&gt;
&lt;code&gt;html`...${dynamicHtml}...`&lt;/code&gt; と書くと &lt;code&gt;dynamicHtml&lt;/code&gt; 内の &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; がエスケープされてしまう。動的に生成した HTML を挿入するには、raw 文字列として渡すヘルパーが必要。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Astro + Cloudflare Pages で OGP 画像を自動生成するなら、&lt;strong&gt;&lt;code&gt;prerender: true&lt;/code&gt; + satori + @resvg/resvg-js&lt;/strong&gt; がビルド時完結で安定する&lt;/li&gt;
&lt;li&gt;日本語ブログでは &lt;strong&gt;budoux を入れるだけで改行品質が劇的に改善&lt;/strong&gt; される。約 30 KB の依存を追加するだけで、全記事の OGP 画像が自然な文節区切りになる&lt;/li&gt;
&lt;li&gt;satori 特有の制約（display: flex 必須、ZWSP の tofu 化、satori-html のエスケープ）は事前に知っておくとデバッグ時間を節約できる&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;まずは自分のブログの OGP エンドポイントに &lt;code&gt;export const prerender = true&lt;/code&gt; が付いているか確認するところから始めるのがおすすめです。&lt;/p&gt;
</content:encoded><category>Tech</category><category>Astro</category><category>Cloudflare Pages</category><category>OGP</category><category>satori</category><category>budoux</category></item><item><title>Claude Code にプロジェクト文脈を渡す 4 層構造</title><link>https://blog.c12o.net/posts/claude-code-context-layers/</link><guid isPermaLink="true">https://blog.c12o.net/posts/claude-code-context-layers/</guid><description>Claude Code にプロジェクト文脈を渡す CLAUDE.md・slash command・subagent・skill の 4 層の役割分担。CLAUDE.md は毎セッション読まれる共有規約、残り 3 層は必要時だけ読まれる専門知識と分け、ブログの構成を例に示します。</description><pubDate>Sat, 11 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.anthropic.com/en/docs/claude-code/overview&quot;&gt;Claude Code&lt;/a&gt; を個人プロジェクトで本格的に使い始めると、最初にぶつかるのが「毎回同じ文脈を口頭で説明するのがつらい」という問題です。&lt;br /&gt;
リポジトリ直下に &lt;code&gt;CLAUDE.md&lt;/code&gt; を置く解決策は既に広く使われていますが、プロジェクトが大きくなるとこの 1 ファイルだけでは足りなくなります。&lt;/p&gt;
&lt;p&gt;Claude Code が提供する &lt;strong&gt;CLAUDE.md / slash command / subagent / skill&lt;/strong&gt; の 4 つのレイヤーに役割を分け、&lt;br /&gt;
プロジェクト文脈をエージェントへ渡します。例は実際にこのブログで組んだ構成です。&lt;/p&gt;
&lt;h2&gt;なぜ CLAUDE.md だけでは足りないのか&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.anthropic.com/en/docs/claude-code/memory#claudemd&quot;&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt;&lt;/a&gt; は Claude Code が毎セッションの先頭で必ず読み込む Markdown ファイルです。&lt;br /&gt;
プロジェクトのスタック・規約・よく使うコマンドをここに書いておくと、&lt;br /&gt;
毎回「この repo は Astro で、pnpm を使って、Biome で整形していて…」と説明しなくて済みます。&lt;br /&gt;
常時メモリとして非常に強力です。&lt;/p&gt;
&lt;p&gt;しかし単体では 3 つの限界があります。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;全セッションの先頭で読まれる&lt;/strong&gt;&lt;br /&gt;
必ず読まれるため、大きくなるほど毎回のトークンを圧迫します。&lt;br /&gt;
Claude Code のベストプラクティスでは 200 行以下が目安とされており、これを超えると「読まれているけれど守られない」状態になりがちです。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;手続き的なワークフローを書くには冗長&lt;/strong&gt;&lt;br /&gt;
「新規記事を作るときは、まずスラッグを決めて、次に雛形を生成して、フロントマターを書き換えて…」&lt;br /&gt;
のような順序依存の作業は、自然言語で羅列しても実行順序を保証できません。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;専門的な知識は読み捨てになる危険性&lt;/strong&gt;&lt;br /&gt;
たとえば SEO レビューのような重いチェックリストを CLAUDE.md に書くと、&lt;br /&gt;
SEO レビューをしない他のセッションでもトークンを消費してしまいます。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;この 3 つを解決するために、残り 3 つのレイヤーを追加します。&lt;/p&gt;
&lt;h2&gt;4 層の役割分担&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;層&lt;/th&gt;
&lt;th&gt;置き場所&lt;/th&gt;
&lt;th&gt;何を書くか&lt;/th&gt;
&lt;th&gt;起動契機&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CLAUDE.md&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;リポジトリルート&lt;/td&gt;
&lt;td&gt;不変の規約（スタック、ディレクトリ構成、コマンド、コミットスタイル）&lt;/td&gt;
&lt;td&gt;毎セッション自動&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;slash command&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.claude/commands/*.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;手続き的ワークフロー（雛形生成、特定タスクの手順）&lt;/td&gt;
&lt;td&gt;ユーザーが &lt;code&gt;/command-name&lt;/code&gt; と入力&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;subagent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.claude/agents/*.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;独立した context で動く専門家。長い指示や大量の参照ファイルを抱える&lt;/td&gt;
&lt;td&gt;ユーザー指定、または description による自動判断&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;skill&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.claude/skills/&amp;lt;name&amp;gt;/SKILL.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;スコープの狭い検証・変換ロジック。progressive disclosure で必要時のみ読まれる&lt;/td&gt;
&lt;td&gt;エージェントが必要と判断した時&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;ポイントは &lt;strong&gt;コンテキストの寿命と読まれる頻度&lt;/strong&gt; です。&lt;br /&gt;
&lt;code&gt;CLAUDE.md&lt;/code&gt; は毎回全員が読む共有知識、&lt;code&gt;skill&lt;/code&gt; は必要なときだけ特定のエージェントが読むニッチ知識、という使い分けを意識します。&lt;/p&gt;
&lt;h2&gt;実例: 個人ブログで組んだ構成&lt;/h2&gt;
&lt;p&gt;今回実装した構成は次の通りです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;blog/
├── CLAUDE.md                          # 113 行。スタック・記事規約・コマンド
└── .claude/
    ├── settings.json                  # permission allowlist
    ├── commands/
    │   └── new-post.md                # /new-post &amp;lt;slug&amp;gt; で記事雛形を生成
    ├── agents/
    │   ├── post-planner.md            # 記事企画とアウトライン生成
    │   └── seo-reviewer.md            # 公開前の SEO レビュー
    └── skills/
        └── frontmatter-check/
            └── SKILL.md               # frontmatter の schema 検証
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;それぞれが「どの問いに答えるか」を 1 行で言えるように設計してあります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CLAUDE.md: このブログはどんな規約で書かれているか？&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/new-post&lt;/code&gt;: 新しい記事を始めるとき、どういう手順を踏むか？&lt;/li&gt;
&lt;li&gt;&lt;code&gt;post-planner&lt;/code&gt;: このアイデアは記事として成立するか？ outline はどうなるか？&lt;/li&gt;
&lt;li&gt;&lt;code&gt;seo-reviewer&lt;/code&gt;: この記事は公開に耐えるか？&lt;/li&gt;
&lt;li&gt;&lt;code&gt;frontmatter-check&lt;/code&gt;: この YAML はスキーマに合っているか？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;この 5 つを &lt;code&gt;CLAUDE.md&lt;/code&gt; に詰め込もうとすると、確実に 500 行を超えます。&lt;br /&gt;
分割したことで &lt;code&gt;CLAUDE.md&lt;/code&gt; は 110 行前後で安定し、各エージェントとスキルも 50〜60 行の手ごろなサイズに収まりました。&lt;/p&gt;
&lt;h2&gt;アイデアを「エージェントが読める形」で保つ&lt;/h2&gt;
&lt;p&gt;もう 1 つ重要な気付きがあります。&lt;br /&gt;
&lt;strong&gt;計画や企画書もコードと同じくプロジェクト文脈に含まれる&lt;/strong&gt; ということです。&lt;/p&gt;
&lt;p&gt;このブログでは記事アイデアを &lt;code&gt;docs/blog-post-plan/&lt;/code&gt; 配下で以下のようなファネルで管理しています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flowchart LR
      A[&quot;Inbox&amp;lt;br/&amp;gt;inbox.md&quot;] --&amp;gt;
  B[&quot;Backlog&amp;lt;br/&amp;gt;spinoff-candidates.md&quot;]
      B --&amp;gt; C[&quot;Active&amp;lt;br/&amp;gt;NN-*.md&quot;]
      C --&amp;gt; D[&quot;Posts&amp;lt;br/&amp;gt;src/content/posts/&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重要なのは、&lt;code&gt;post-planner&lt;/code&gt; エージェントの定義ファイルに&lt;br /&gt;
「新規企画を受けたら、outline を書く前に必ず &lt;code&gt;inbox.md&lt;/code&gt; と &lt;code&gt;spinoff-candidates.md&lt;/code&gt; を grep して重複を確認せよ」&lt;br /&gt;
という義務を明記している点です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## Inputs you should always gather

2. **Duplicate check (mandatory)**. Before producing any outline, grep
   both of these for overlapping keywords from the pitch:
   - docs/blog-post-plan/inbox.md
   - docs/blog-post-plan/spinoff-candidates.md
   If you find an overlapping entry, stop and report it before writing
   a new plan.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;結果として、企画メモ自体が「エージェントが読みに行く構造化ドキュメント」になり、僕がアイデアの重複を手動でチェックする必要がなくなりました。&lt;br /&gt;
アイデア管理が &lt;strong&gt;検索可能なコンテキスト&lt;/strong&gt; になった感覚です。&lt;/p&gt;
&lt;h2&gt;運用の勘所&lt;/h2&gt;
&lt;p&gt;以下は実際に運用して役立ったポイントです。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CLAUDE.md は 200 行を厳守する&lt;/strong&gt;&lt;br /&gt;
200 行を超えた時点で、役割の切り分けに失敗している兆候です。&lt;br /&gt;
特定ドメインに深入りした記述や「特定のケースでだけ役立つ手順」が増え始めたら、迷わずサブエージェントかスキルに切り出します。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;コマンドとエージェントを混同しない&lt;/strong&gt;&lt;br /&gt;
「手順が決まっている」 → コマンド、「判断が必要」 → エージェント、と分けます。&lt;br /&gt;
&lt;code&gt;/new-post&lt;/code&gt; は決まったステップの実行なのでコマンドです。SEO レビューは記事ごとの文脈判断が必要なのでエージェント（&lt;code&gt;seo-reviewer&lt;/code&gt;）にしています。&lt;br /&gt;
この区別を間違えると、エージェントがただの手順書になったり、コマンドが曖昧な判断を抱え込んだりします。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;permission の allowlist を先に整える&lt;/strong&gt;&lt;br /&gt;
&lt;code&gt;.claude/settings.json&lt;/code&gt; に &lt;code&gt;pnpm lint&lt;/code&gt;、&lt;code&gt;pnpm check&lt;/code&gt;、&lt;code&gt;git status&lt;/code&gt; 等のコマンドを事前承認しておくと、エージェントが毎回承認を求めてきません。&lt;br /&gt;
一方で &lt;code&gt;git push --force&lt;/code&gt; のような破壊的コマンドは明示的に deny に入れておきます。&lt;br /&gt;
これがないと、便利なエージェントほど承認プロンプトで作業が止まります。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Skill は呼ばれないと読まれない&lt;/strong&gt;&lt;br /&gt;
Claude Code の skill は description フィールドで自動判定されるか、&lt;br /&gt;
エージェントが明示的に呼んだときに初めて &lt;code&gt;SKILL.md&lt;/code&gt; の本文が読み込まれます。&lt;br /&gt;
&lt;strong&gt;常に読んで欲しい知識は CLAUDE.md に、必要な時だけで十分な知識は skill に&lt;/strong&gt;&lt;br /&gt;
という分離の鉄則を守ると、全体のトークン効率が目に見えて良くなります。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; は「常時読まれる共有規約」、それ以外（commands / agents / skills）は「必要時だけ読まれる専門知識」という区別が設計で一番大事な点&lt;/li&gt;
&lt;li&gt;企画書やアイデア帳のような人間向けドキュメントも、エージェントが grep できる構造にすると自動化の対象にできる&lt;/li&gt;
&lt;li&gt;個人ブログ規模でもこの 4 層を分けるだけで、毎回のセッションで「最近何書いたっけ」を説明する必要がなくなる&lt;/li&gt;
&lt;li&gt;この仕組みは &lt;a href=&quot;/posts/astro-og-image-generation&quot;&gt;OGP 画像の自動生成&lt;/a&gt;のような別の自動化とも相性が良く、エージェントが規約を理解した上で実装を進められる&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Claude Code のベストプラクティスは進化が速い領域なので、一度組んだ構成も定期的に見直す前提で運用するのがおすすめです。&lt;br /&gt;
まずは &lt;code&gt;CLAUDE.md&lt;/code&gt; を 200 行以下に保ちつつ、&lt;code&gt;/new-post&lt;/code&gt; のような &lt;a href=&quot;https://docs.anthropic.com/en/docs/claude-code/slash-commands&quot;&gt;&lt;code&gt;.claude/commands/&lt;/code&gt;&lt;/a&gt; を 1 つ作ってみるところから始めるのが最短です。&lt;br /&gt;
その 1 つが動くと、このレイヤリング設計の意味が実感できます。&lt;/p&gt;
</content:encoded><category>Tech</category><category>Claude Code</category><category>AI</category><category>開発ツール</category></item><item><title>Dockerコンテナ起動時に「address already in use」エラーが出た話</title><link>https://blog.c12o.net/posts/docker-address-already-in-use/</link><guid isPermaLink="true">https://blog.c12o.net/posts/docker-address-already-in-use/</guid><description>WSL2 上の Docker でコンテナ起動時に address already in use が出たときのメモ。lsof / netstat / docker ps で競合プロセスは見つからず、WSL や Docker の再起動も効かず、最終的に PC 再起動で解消した記録。</description><pubDate>Mon, 15 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Dockerコンテナ起動時に以下のエラーが発生した際のメモ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;failed to bind host port for 0.0.0.0:8080:... address already in use
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;環境&lt;/h2&gt;
&lt;p&gt;Windows上のWSL2（Ubuntu）&lt;/p&gt;
&lt;h2&gt;試したこと&lt;/h2&gt;
&lt;p&gt;まずポートが競合していないか、以下のコマンドで調べた。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;WSLで &lt;code&gt;lsof -i:8080&lt;/code&gt; → 何も表示されず&lt;/li&gt;
&lt;li&gt;Windows（PowerShell）で &lt;code&gt;netstat -anp | grep 8080&lt;/code&gt; → 何も表示されず&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker ps -a&lt;/code&gt; で不要なコンテナがないことを確認&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;いずれも競合しているプロセスは見当たらなかった。&lt;/p&gt;
&lt;p&gt;次にDockerやWSL自体の再起動を試みた。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;WSL2の再起動（&lt;code&gt;wsl --shutdown&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;Dockerサービスの再起動 (&lt;code&gt;sudo service docker restart&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Dockerネットワークのリセット(&lt;code&gt;docker network prune&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;しかしいずれも効果なし。&lt;/p&gt;
&lt;h2&gt;最終的な対応&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;PC再起動&lt;/strong&gt; でコンテナ起動に成功した。&lt;/p&gt;
&lt;p&gt;WindowsのFast Startupなどが影響していたのか、あるいはゾンビプロセスのようなものが残っていたのか、原因は特定できなかった。困ったときは再起動に限る。&lt;/p&gt;
</content:encoded><category>Tech</category><category>Docker</category><category>WSL</category><category>Windows</category><category>トラブルシューティング</category></item><item><title>SEO と AEO の違い</title><link>https://blog.c12o.net/posts/seo-and-aeo/</link><guid isPermaLink="true">https://blog.c12o.net/posts/seo-and-aeo/</guid><description>SEO(検索エンジン最適化)と AEO(回答エンジン最適化)の違いを、ゴール・主体・最適化対象・重視される点の 4 項目で比較。ChatGPT や Perplexity に回答として引用されるための FAQ セクション、1 テーマ 1 記事、出典の明示などの施策もまとめました。</description><pubDate>Sat, 13 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;ブログを運営する上で、SEOについて調べていたらAEOという言葉を見かけることが増えてきました。&lt;br /&gt;
軽く調べた内容をもとに、SEOとAEOの違いなどをまとめてみます。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;SEOとは何か&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Search Engine Optimization（検索エンジン最適化）&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Google のクローラーが読みやすい構造、検索順位を上げるための取り組み&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;主な要素&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;キーワード最適化&lt;/li&gt;
&lt;li&gt;内部施策（構造化、内部リンク、サイト速度、UX）&lt;/li&gt;
&lt;li&gt;外部施策（被リンク、ブランド評価）&lt;/li&gt;
&lt;li&gt;コンテンツ品質（E-E-A-T: Experience, Expertise, Authoritativeness, Trustworthiness）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;AEOとは何か&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Answer Engine Optimization（回答エンジン最適化）&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;AIから見つけられるか、AIに選ばれるかを最適化する取り組み&lt;/li&gt;
&lt;li&gt;対象の例: ChatGPT、Google Gemini、Perplexity、音声検索（Siri / Alexa / Google Assistant）など&lt;/li&gt;
&lt;li&gt;特徴: &lt;code&gt;検索&lt;/code&gt;ではなく&lt;code&gt;回答&lt;/code&gt;がゴールになる点が大きな違い&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;SEOとAEOの違い&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;項目&lt;/th&gt;
&lt;th&gt;SEO&lt;/th&gt;
&lt;th&gt;AEO&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ゴール&lt;/td&gt;
&lt;td&gt;SERP の順位を上げる&lt;/td&gt;
&lt;td&gt;AI / 音声アシスタントに&lt;code&gt;最適な回答&lt;/code&gt;として選ばれる&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;主体&lt;/td&gt;
&lt;td&gt;検索エンジン&lt;/td&gt;
&lt;td&gt;回答エンジン（AI、LLM）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;最適化対象&lt;/td&gt;
&lt;td&gt;検索クエリ・ページ&lt;/td&gt;
&lt;td&gt;質問・回答ペア、構造化情報、コンパクトなナレッジ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;重要視される点&lt;/td&gt;
&lt;td&gt;E-E-A-T、被リンク、テキスト量や網羅性&lt;/td&gt;
&lt;td&gt;正確性、簡潔性、文脈理解、構造化（FAQ など）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;AEOが注目される理由&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;AI 検索の普及によりユーザーの流入が変化している&lt;/li&gt;
&lt;li&gt;検索エンジンがページ単位で評価するのではなく、&lt;code&gt;情報単位（ナレッジ）&lt;/code&gt;で回答を抽出する傾向が強まっている&lt;/li&gt;
&lt;li&gt;そのため、自社や自サイトの情報が AI に&lt;code&gt;選ばれる&lt;/code&gt;必要が高まっている&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;AEO対策として効果的な施策&lt;/h3&gt;
&lt;p&gt;以下は実務で取り入れやすく、効果が期待できる施策です。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;ユーザーの質問に&lt;strong&gt;シンプルで正確に答える&lt;/strong&gt;セクションを設ける&lt;/p&gt;
&lt;p&gt;:::note[FAQセクションの例]&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;### Q. AEO とは何ですか？

AEO（Answer Engine Optimization）とは、AI や音声アシスタントが
ユーザーの質問に答える際に、信頼できる情報源として引用されることを
目的とした最適化手法です。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1 テーマ 1 記事のように、扱う&lt;strong&gt;トピックを明確&lt;/strong&gt;にする&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;自分の業界を明確に記載する&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;箇条書き・短文で&lt;code&gt;引用しやすい&lt;/code&gt;文章を心がける&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AI が切り出しやすいように、要点は箇条書きや見出しで整理する&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;出典・根拠を明示して&lt;strong&gt;正確性&lt;/strong&gt;を担保する&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;著者情報や参照リンクを明記し、信頼性を示す&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;実装上の注意&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;構造化データは設定を間違えると検索結果の表示が変わってしまうため、&lt;a href=&quot;https://search.google.com/test/rich-results&quot;&gt;Google Rich Results Test&lt;/a&gt; 等で検証する&lt;/li&gt;
&lt;li&gt;生成 AI での回答が誤情報（hallucination）を起こすことがあるため、必ず人力で検証・注釈を付与する&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;まとめ&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;何を目的とした最適化か
&lt;ul&gt;
&lt;li&gt;SEO = 検索結果に表示されること&lt;/li&gt;
&lt;li&gt;AEO = AI が直接返す答えの材料として使われること&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;良質なコンテンツは SEO にも AEO にも有効&lt;/li&gt;
&lt;li&gt;今後は検索クエリに最適化するだけでなく&lt;code&gt;質問に最適化&lt;/code&gt;する戦略が必要&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>Tech</category><category>SEO</category><category>AEO</category><category>コンテンツ戦略</category></item><item><title>Astro をローカルで起動するまでの手順まとめ</title><link>https://blog.c12o.net/posts/astro-setup/</link><guid isPermaLink="true">https://blog.c12o.net/posts/astro-setup/</guid><description>Astro の公式ブログテンプレートを npm create astro@latest で作成し、ローカルの開発サーバーで表示するまでの手順。Node.js のバージョン条件、プロンプトで選ぶ項目(配置先・テンプレート・依存インストール・git 初期化)、起動ログの例つき。</description><pubDate>Fri, 12 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.astro.build/ja/install-and-setup/&quot;&gt;公式ドキュメント&lt;/a&gt;をもとに、&lt;br /&gt;
Astro の公式ブログテンプレートを使ってローカル環境で起動するまでの手順をまとめています。&lt;/p&gt;
&lt;h2&gt;前提条件&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Node.js: v18.17.1 または v20.3.0 以上（v19 はサポート対象外）&lt;/li&gt;
&lt;li&gt;テキストエディタ: VS Code、Astro 公式拡張機能&lt;/li&gt;
&lt;li&gt;ターミナル: CLI を利用&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;node/npm のバージョン確認例&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ node -v
v22.16.0

$ npm -v
10.9.2
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;プロジェクト作成（手順）&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;プロジェクト作成コマンドを実行&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;npm create astro@latest
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;新規プロジェクトをどこに作成するか&lt;br /&gt;
何も入力せず Enter を押すとカレントディレクトリに &lt;code&gt;fast-field&lt;/code&gt; という名前で作成される&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;dir   Where should we create your new project?
      ./fast-field
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;テンプレートの選択で「Use blog template」を選択（矢印キーで選択）&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;tmpl   How would you like to start your new project?
      ○ A basic, helpful starter project (recommended)
      ● Use blog template
      ○ Use docs (Starlight) template
      ○ Use minimal (empty) template
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;依存関係のインストールで「Yes」を選択&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;deps   Install dependencies? (recommended)
      ● Yes  ○ No
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Git 初期化は「No」を選択&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;git   Initialize a new git repository? (optional)
      ○ Yes  ● No
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;初期化完了後、プロジェクトディレクトリに移動して開発サーバーを起動する&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;cd ./fast-field
npm run dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;参考画像：
&lt;img src=&quot;./setup.png&quot; alt=&quot;プロジェクト作成時のターミナル画像&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;ローカルサーバー起動の確認&lt;/h2&gt;
&lt;p&gt;起動ログの例:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; fast-field@0.0.1 dev
&amp;gt; astro dev

astro  v5.16.5 ready in 640 ms

┃ Local    http://localhost:4321/
┃ Network  use --host to expose

watching for file changes...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ブラウザで &lt;code&gt;http://localhost:4321/&lt;/code&gt; を開くと、Astro のブログテンプレートが表示されます。
&lt;img src=&quot;./local-astro.png&quot; alt=&quot;ローカルで起動した、Astroのブログテンプレート&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;テーマや追加リソース&lt;/h2&gt;
&lt;p&gt;公式テーマ以外にも、Astro のテーママーケットプレイスから様々なテーマを探せます。&lt;br /&gt;
無料テーマを探す場合の参考リンク：&lt;a href=&quot;https://astro.build/themes/1/?search=&amp;amp;categories%5B%5D=blog&amp;amp;price%5B%5D=free&quot;&gt;Themes | Astro&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;npm create astro@latest&lt;/code&gt; でテンプレートからプロジェクト作成&lt;/li&gt;
&lt;li&gt;プロジェクト名・テンプレート・依存インストール・git 初期化をプロンプトで選択&lt;/li&gt;
&lt;li&gt;プロジェクトのディレクトリに移動して &lt;code&gt;npm run dev&lt;/code&gt; でローカルサーバーを起動し、&lt;code&gt;http://localhost:4321/&lt;/code&gt; を確認&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>Tech</category><category>Astro</category></item><item><title>REST Clientの使い方 - VSCodeで簡単にAPIリクエスト</title><link>https://blog.c12o.net/posts/rest-client-usage/</link><guid isPermaLink="true">https://blog.c12o.net/posts/rest-client-usage/</guid><description>VSCode の拡張機能 REST Client で、.http ファイルに書いた GET / POST リクエストをエディタ内から送信してレスポンスを確認する手順。### で複数リクエストを区切る方法、変数の定義、Bearer Token 認証付きリクエストの書き方まで。</description><pubDate>Sun, 07 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;概要&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=humao.rest-client&quot;&gt;REST Client&lt;/a&gt; は Visual Studio Code 上で HTTP リクエストを送信し、レスポンスを直接確認できる拡張機能です。&lt;br /&gt;
API 開発や検証を VSCode 内で完結できます。&lt;/p&gt;
&lt;h2&gt;導入&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;VSCode の拡張機能一覧から &lt;code&gt;REST Client&lt;/code&gt; をインストール
&lt;img src=&quot;./install-extension.png&quot; alt=&quot;拡張機能インストール&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;拡張子 &lt;code&gt;.http&lt;/code&gt; または &lt;code&gt;.rest&lt;/code&gt; のファイルを作成&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;基本的な使用方法&lt;/h2&gt;
&lt;h3&gt;GET リクエスト&lt;/h3&gt;
&lt;p&gt;作成したファイルに以下のように記述し、&lt;code&gt;Send Request&lt;/code&gt; リンクをクリックしてリクエストを送信します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GET https://jsonplaceholder.typicode.com/posts/1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./get-request.png&quot; alt=&quot;GETリクエスト&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;POST リクエスト&lt;/h3&gt;
&lt;p&gt;POST リクエストも同様に記述できます。&lt;br /&gt;
データは JSON 形式で送信できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST https://jsonplaceholder.typicode.com/posts
Content-Type: application/json

{
  &quot;title&quot;: &quot;foo&quot;,
  &quot;body&quot;: &quot;bar&quot;,
  &quot;userId&quot;: 1
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./post-request.png&quot; alt=&quot;POSTリクエスト&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;複数リクエスト&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;###&lt;/code&gt; で区切ると、1 つのファイルに複数のリクエストを定義できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;### GET
GET https://jsonplaceholder.typicode.com/posts/1

### POST
POST https://jsonplaceholder.typicode.com/posts
Content-Type: application/json

{
  &quot;title&quot;: &quot;bar&quot;,
  &quot;body&quot;: &quot;baz&quot;,
  &quot;userId&quot;: 2
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;複数リクエストがある場合、&lt;code&gt;Send Request&lt;/code&gt; リンクがそれぞれのリクエストごとに表示されます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./multiple-requests.png&quot; alt=&quot;複数リクエスト&quot; /&gt;&lt;/p&gt;
&lt;p&gt;:::caution
&lt;code&gt;###&lt;/code&gt; を使わずにリクエストを連続して書いた場合、
&lt;code&gt;Send Request&lt;/code&gt; リンクが1つしか表示されません&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GET https://jsonplaceholder.typicode.com/posts/1

POST https://jsonplaceholder.typicode.com/posts
Content-Type: application/json

{
  &quot;title&quot;: &quot;bar&quot;,
  &quot;body&quot;: &quot;baz&quot;,
  &quot;userId&quot;: 2
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./multiple-requests-warning.png&quot; alt=&quot;複数リクエストになっていない&quot; /&gt;&lt;/p&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h2&gt;変数の利用&lt;/h2&gt;
&lt;p&gt;環境ごとに異なる URL やトークンを変数として定義し、使い回すことができます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;定義&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;@baseUrl = https://jsonplaceholder.typicode.com
@userId = 1
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;利用&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;GET {{baseUrl}}/posts/{{userId}}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;@baseUrl = https://jsonplaceholder.typicode.com
@userId = 1

### GET
GET {{baseUrl}}/posts/{{userId}}

### POST
POST {{baseUrl}}/posts
Content-Type: application/json

{
  &quot;title&quot;: &quot;bar&quot;,
  &quot;body&quot;: &quot;baz&quot;,
  &quot;userId&quot;: 2
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./using-variables.png&quot; alt=&quot;変数の利用&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;認証付きリクエスト&lt;/h2&gt;
&lt;p&gt;認証が必要な API も簡単にリクエストを送信できます。&lt;/p&gt;
&lt;h3&gt;Bearer Token&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;@token = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

GET https://api.example.com/users/me
Authorization: Bearer {{token}}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;利用シーン&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;API 開発中の動作確認&lt;/li&gt;
&lt;li&gt;外部 API の動作検証&lt;/li&gt;
&lt;li&gt;ドキュメントに記載された API の動作確認&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;補足&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;サンプルには &lt;a href=&quot;https://jsonplaceholder.typicode.com/&quot;&gt;JSONPlaceholder&lt;/a&gt; を利用しています。&lt;br /&gt;
JSONPlaceholder はモック API サービスで、GET/POST/PUT/DELETE などの HTTP リクエストを試すことができます。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>Tech</category><category>VSCode</category><category>REST-API</category></item></channel></rss>