<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Efo's Sandbox</title><link>https://efo-yu.github.io/</link><description>Recent content on Efo's Sandbox</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Fri, 24 Apr 2026 09:27:37 +0900</lastBuildDate><atom:link href="https://efo-yu.github.io/index.xml" rel="self" type="application/rss+xml"/><item><title>Discord Webhook 429エラーをProxyで回避する</title><link>https://efo-yu.github.io/post/tech/cfw-proxy-for-discord-webhook/</link><pubDate>Fri, 24 Apr 2026 09:27:37 +0900</pubDate><guid>https://efo-yu.github.io/post/tech/cfw-proxy-for-discord-webhook/</guid><description>&lt;h2 id="課題"&gt;課題&lt;/h2&gt;
&lt;p&gt;Google Apps Script（GAS）からDiscordへWebhookでメッセージを送信する際，GASの共有IPアドレスが原因でDiscordのレートリミットに巻き込まれ，IPブロックされることがある：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Discord API Error (429): error code: 1015&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;この問題は自身の送信頻度が適正であっても発生する．&lt;/p&gt;
&lt;h2 id="解決策"&gt;解決策&lt;/h2&gt;
&lt;p&gt;Cloudflare Workers（CFW）を用いて，リクエストをDiscordへ横流しするだけのプロキシを構築する．&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;drop-in replacement&lt;/strong&gt;: クライアント側の変更を最小限にするため，Base URLの置換だけで機能するようパスをそのまま引き継ぐ仕様とする．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;whitelist管理&lt;/strong&gt;: セキュリティ対策として，&lt;code&gt;WH_&lt;/code&gt;というプレフィックスから始まるCloudflare Secretsに登録されたWebhook URLのみを許可する．&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="実装"&gt;実装&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;index.ts&lt;/code&gt;のみ．&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;export&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;interface&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Env&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; [&lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;]&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;unknown&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;export&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fetch&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;request&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;Request&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;env&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;Env&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;ExecutionContext&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Promise&lt;/span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;Response&lt;/span&gt;&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;request&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;method&lt;/span&gt; &lt;span style="color:#f92672"&gt;!==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;POST&amp;#34;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Response&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;JSON&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;stringify&lt;/span&gt;({ &lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Method Not Allowed&amp;#34;&lt;/span&gt; }), {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;status&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;405&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;headers&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; { &lt;span style="color:#e6db74"&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;requestUrl&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;URL&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;request&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;pathname&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;requestUrl&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;pathname&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;targetWebhook&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`https://discord.com&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;pathname&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;allowedWebhooks&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;[] &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Object.&lt;span style="color:#a6e22e"&gt;keys&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;env&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;filter&lt;/span&gt;((&lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;startsWith&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;WH_&amp;#34;&lt;/span&gt;) &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;typeof&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;env&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;] &lt;span style="color:#f92672"&gt;===&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;string&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;map&lt;/span&gt;((&lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;env&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;] &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#f92672"&gt;!&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;allowedWebhooks&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;includes&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;targetWebhook&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Response&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;JSON&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;stringify&lt;/span&gt;({
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Forbidden: Webhook URL is not in the allowlist&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;status&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;403&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;headers&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; { &lt;span style="color:#e6db74"&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;contentType&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;request&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;headers&lt;/span&gt;.&lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;content-type&amp;#34;&lt;/span&gt;) &lt;span style="color:#f92672"&gt;||&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;body&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;request&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;text&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;discordResponse&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fetch&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;targetWebhook&lt;/span&gt;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;method&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;POST&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;headers&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;contentType&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;body&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;body&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;responseBody&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;discordResponse&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;text&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Response&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;responseBody&lt;/span&gt;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;status&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;discordResponse.status&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;headers&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;discordResponse&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;headers&lt;/span&gt;.&lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;) &lt;span style="color:#f92672"&gt;||&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;catch&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Response&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;JSON&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;stringify&lt;/span&gt;({ &lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Internal Server Error while forwarding&amp;#34;&lt;/span&gt; }),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;status&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;500&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;headers&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; { &lt;span style="color:#e6db74"&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="使用方法"&gt;使用方法&lt;/h2&gt;
&lt;p&gt;Webhook URLを&lt;code&gt;WH_&lt;/code&gt;というプレフィックスから始まる名前でSecretsに登録し，送信元で&lt;code&gt;discord.com&lt;/code&gt;を&lt;code&gt;your-worker.your-subdomain.workers.dev&lt;/code&gt;に置換するだけ．&lt;/p&gt;</description></item><item><title>Colabでの開発体験をいい感じにする小ネタ集</title><link>https://efo-yu.github.io/post/tech/enhance-colab-dev-experience/</link><pubDate>Mon, 15 Dec 2025 00:20:25 +0900</pubDate><guid>https://efo-yu.github.io/post/tech/enhance-colab-dev-experience/</guid><description>&lt;h1 id="vscode-上のコードを-colab-で実行する"&gt;VSCode 上のコードを Colab で実行する&lt;/h1&gt;
&lt;p&gt;VSCode には Google 公式 Colab 拡張機能がある：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=Google.colab"&gt;拡張機能&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.googleblog.com/en/google-colab-is-coming-to-vs-code/"&gt;公式ブログの記事&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;この拡張機能を使うことで VSCode 上から Colab のランタイムに直接接続し，コードを実行できる．&lt;/p&gt;
&lt;p&gt;これにより，VSCode でコーディングし，Colab の計算リソースを使って実行する，というワークフローをとれる．&lt;/p&gt;
&lt;p&gt;なお，Google Drive のデスクトップアプリをインストールしておけば，Drive 上のファイルを VSCode で直接開ける．&lt;/p&gt;
&lt;p&gt;この状態で Colab のランタイムを接続すれば，Drive 上のファイルをそのまま編集し，Colab 環境で実行できることになる．&lt;/p&gt;
&lt;h1 id="github-上のコードを-colab-で開く"&gt;GitHub 上のコードを Colab で開く&lt;/h1&gt;
&lt;p&gt;Colab はもともと，GitHub 上の public repo から直接ノートブックを開く機能を備えている．&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/googlecolab/colabtools/blob/master/notebooks/colab-github-demo.ipynb"&gt;公式解説&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;GitHub public repo への URL を&lt;code&gt;github.com&lt;/code&gt;→&lt;code&gt;colab.research.google.com/github&lt;/code&gt;と置換するだけでよい．&lt;/p&gt;</description></item><item><title>ViteでReact+Tailwind+TSをサクッと構成する</title><link>https://efo-yu.github.io/post/tech/vite-react-tailwind-ts-swc/</link><pubDate>Sat, 01 Nov 2025 13:34:53 +0900</pubDate><guid>https://efo-yu.github.io/post/tech/vite-react-tailwind-ts-swc/</guid><description>&lt;h1 id="repository"&gt;Repository&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://github.com/Efo-YU/vite-react-tailwind-ts-swc"&gt;https://github.com/Efo-YU/vite-react-tailwind-ts-swc&lt;/a&gt;&lt;/p&gt;
&lt;h1 id="motivation"&gt;Motivation&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nextjs.org/"&gt;Next.js&lt;/a&gt;では&lt;a href="https://www.typescriptlang.org/"&gt;TypeScript&lt;/a&gt;と&lt;a href="https://tailwindcss.com/"&gt;Tailwind CSS&lt;/a&gt;(とベースたる&lt;a href="https://react.dev/"&gt;React&lt;/a&gt;)が標準的な構成として用いられる(&lt;a href="https://nextjs.org/docs/app/getting-started/installation#automatic-installation"&gt;&lt;code&gt;create-next-app&lt;/code&gt;時に自動でsetupできる&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Next.jsはReactの代表的な &lt;em&gt;full-stack framework&lt;/em&gt; とされるため，Reactを用いる際は上記の構成を真似したい&lt;/li&gt;
&lt;li&gt;だがNext.js自体は超クソデカframeworkであるため，この構成を使うためだけにNext.jsを用いるのは無駄が多い&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vite.dev/"&gt;Vite&lt;/a&gt;なら簡単かつminimalにReact+Tailwind+TSを構成できる&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- raw HTML omitted --&gt;
&lt;h1 id="procedure"&gt;Procedure&lt;/h1&gt;
&lt;h2 id="initialize-project"&gt;Initialize project&lt;/h2&gt;
&lt;p&gt;Commit: &lt;a href="https://github.com/Efo-YU/vite-react-tailwind-ts-swc/commit/7d4de5eb7ac7561dc163684a4ccc556c874b6d94"&gt;https://github.com/Efo-YU/vite-react-tailwind-ts-swc/commit/7d4de5eb7ac7561dc163684a4ccc556c874b6d94&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;下記を実行：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pnpm create vite
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;dialogで以下のように選択：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;◇ Project name:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ foo-bar-project &lt;span style="color:#75715e"&gt;# 任意にキーボード入力，Enterで決定&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;◇ Select a framework:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ React &lt;span style="color:#75715e"&gt;# 十字キーで選択，Enterで決定&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;◇ Select a variant:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ TypeScript + SWC &lt;span style="color:#75715e"&gt;# 十字キーで選択，Enterで決定&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; &lt;a href="https://swc.rs/"&gt;SWC&lt;/a&gt;はNext.jsでも用いられているとのこと．&lt;/p&gt;
&lt;h2 id="add-tailwindcss-and-tailwindcssvite-dependencies"&gt;Add tailwindcss and @tailwindcss/vite dependencies&lt;/h2&gt;
&lt;p&gt;Commit: &lt;a href="https://github.com/Efo-YU/vite-react-tailwind-ts-swc/commit/e9eda14599f8ef6cf8fd082c6611686abbebf1c0"&gt;https://github.com/Efo-YU/vite-react-tailwind-ts-swc/commit/e9eda14599f8ef6cf8fd082c6611686abbebf1c0&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pnpm i tailwindcss @tailwindcss/vite
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="configure-tailwind-css"&gt;Configure Tailwind CSS&lt;/h2&gt;
&lt;p&gt;Commit: &lt;a href="https://github.com/Efo-YU/vite-react-tailwind-ts-swc/commit/42633c835ac2f62520d83d1b2d50a5c5da79a237"&gt;https://github.com/Efo-YU/vite-react-tailwind-ts-swc/commit/42633c835ac2f62520d83d1b2d50a5c5da79a237&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;src/App.css&lt;/code&gt;を削除&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/index.css&lt;/code&gt;を&lt;code&gt;@import &amp;quot;tailwindcss&amp;quot;&lt;/code&gt;ただ一行だけにする&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-css" data-lang="css"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;tailwindcss&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;vite.config.ts&lt;/code&gt;にtailwindcssを追加&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;defineConfig&lt;/span&gt; } &lt;span style="color:#66d9ef"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;vite&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;react&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;@vitejs/plugin-react-swc&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;tailwindcss&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;@tailwindcss/vite&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// https://vite.dev/config/
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;export&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;defineConfig&lt;/span&gt;({
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;plugins&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&lt;span style="color:#a6e22e"&gt;react&lt;/span&gt;(), &lt;span style="color:#a6e22e"&gt;tailwindcss&lt;/span&gt;()],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="edit-srcapptsx"&gt;Edit &lt;code&gt;src/App.tsx&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;前節までで構成は完了．ただし，&lt;code&gt;pnpm create vite&lt;/code&gt;時に生成された&lt;code&gt;src/App.tsx&lt;/code&gt;はTailwindを用いておらず，もはや使えなくなっているため，Tailwindを用いたものに変える必要がある．&lt;/p&gt;</description></item><item><title>Brand New Site!</title><link>https://efo-yu.github.io/post/blog/brand-new-site/</link><pubDate>Sat, 01 Nov 2025 12:04:23 +0900</pubDate><guid>https://efo-yu.github.io/post/blog/brand-new-site/</guid><description>&lt;p&gt;Rebuilt my website using Hugo with PaperMod theme, which was originally created with VitePress.&lt;/p&gt;</description></item><item><title>About Me</title><link>https://efo-yu.github.io/aboutme/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://efo-yu.github.io/aboutme/</guid><description>&lt;p&gt;&lt;img loading="lazy" src="https://efo-yu.github.io/img/efo.png"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;handle: Efo&lt;/li&gt;
&lt;li&gt;undergrad in the DEPARTMENT of Science, &lt;a href="https://www.sci.yamagata-u.ac.jp/?normal"&gt;Faculty of Science, Yamagata University in Japan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;full-stack student / &lt;strong&gt;&lt;code&gt;full-stUck developer&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;contact: &lt;a href="mailto:goodev140@gmail.com"&gt;goodev140@gmail.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;website: &lt;a href="https://efo-yu.github.io/"&gt;https://efo-yu.github.io/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="stats"&gt;Stats&lt;/h1&gt;
&lt;p&gt;&lt;img loading="lazy" src="https://github-readme-stats.vercel.app/api?username=Efo-YU&amp;theme=tokyonight"&gt;&lt;/p&gt;</description></item><item><title>身内向けプロフィール</title><link>https://efo-yu.github.io/secret/private-profile/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://efo-yu.github.io/secret/private-profile/</guid><description>&lt;h1 id="基本情報"&gt;基本情報&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;本名：&lt;em&gt;ここに書くのはやめとこ&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;ハンドルネーム：Efo(イーフォ)&lt;/li&gt;
&lt;li&gt;身の上：山形大学理学部で物理を専攻，データサイエンスの研究室に所属，趣味でパソコンカタカタをしている学生&lt;/li&gt;
&lt;li&gt;生年：2003 年&lt;/li&gt;
&lt;li&gt;メアド：goodev140@gmail.com&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="所属抜粋"&gt;所属(抜粋)&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.sci.yamagata-u.ac.jp/"&gt;山形大学理学部&lt;/a&gt;理学科(2023.4-)
&lt;ul&gt;
&lt;li&gt;物理コース(2024.4-)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.waki-lab.net/"&gt;脇研究室&lt;/a&gt;(2025.9-)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;山形大学のサークル
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://virtual.yamagata.jp/"&gt;VR 部&lt;/a&gt;(2023.4-2025.3, 2025.12-)
&lt;ul&gt;
&lt;li&gt;元副代表(S7)，G6&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cherry.yum-archives.net/nokoshitai/"&gt;まちの記憶を残し隊&lt;/a&gt;(2024.5-)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.instagram.com/scita_center_yamagata/"&gt;科学イベントサークル SCITA&lt;/a&gt;(2024.10-)
&lt;ul&gt;
&lt;li&gt;元副代表(2024.10-2025.8)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.instagram.com/yamadai_shorinji_kempo/"&gt;少林寺拳法部&lt;/a&gt;(2025.4-)
&lt;ul&gt;
&lt;li&gt;初段&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://naseverse-lab.yz.yamagata-u.ac.jp/"&gt;ナセバース認定研究拠点&lt;/a&gt;(2023.9-)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://morigaku-shop.com/view/page/company"&gt;株式会社森の学校&lt;/a&gt;(2024.6-2025.6)
&lt;ul&gt;
&lt;li&gt;アルバイターとして，介護福祉事業所向けのソフトウェア開発をしていました&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="取り組んでいるもの"&gt;取り組んでいるもの&lt;/h1&gt;
&lt;h2 id="勉強全般"&gt;勉強全般&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;形式上の専攻は物理・データサイエンスです&lt;/li&gt;
&lt;li&gt;興味としては理数系が中心ですが，化学・生物や地学，人文などにも関心や多少の知見はあります
&lt;ul&gt;
&lt;li&gt;そもそも学ぶこと全般が好きです&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;基本的に独学してます
&lt;ul&gt;
&lt;li&gt;講義への出席はあんまりしないです，物理ゆえ実験やグループワークない&lt;del&gt;し出ても仕方ないものが多い&lt;/del&gt;ので&amp;hellip;
&lt;ul&gt;
&lt;li&gt;きちんと修得はしているので，B3 前期終了時点の通算 GPA は 3.6 以上でした&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;学者・技術者・専門家などと名乗るには程遠い浅学です&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="運動"&gt;運動&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;移動手段・趣味・トレーニングとしてウォーキングをします
&lt;ul&gt;
&lt;li&gt;だいたい 10 ～ 30km 程度で，20km 程度までなら連日できます&lt;/li&gt;
&lt;li&gt;山形から上山，寒河江・大江，尾花沢までは行ったことがあります&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="家事"&gt;家事&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;料理は特に好きです
&lt;ul&gt;
&lt;li&gt;作業として楽しく奥深いし，身体に合ったものを自由に作れるので
&lt;ul&gt;
&lt;li&gt;味濃いものや脂っこいものがきつくなってきたし&amp;hellip;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;作って満足するので食べきるのが大変なのが悩み&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;掃除も好きです
&lt;ul&gt;
&lt;li&gt;継続的に清潔・整理整頓を保てるような部屋づくりを意識してます&lt;/li&gt;
&lt;li&gt;部屋の乱れは心の乱れだと思ってます&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="芸術クリエイティブ"&gt;芸術・クリエイティブ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;音楽は好きです
&lt;ul&gt;
&lt;li&gt;音楽経験：ドラム 4 年，ギター・ベース触った程度，PA 4 年 &lt;del&gt;，音ゲ長年&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;特定のアーティストや楽曲よりも，音や楽器の構造に興味があります&lt;/li&gt;
&lt;li&gt;たまにカラオケ行ったりします&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;音声，画像，動画，3D データなどの編集をある程度やってましたが，精力的な活動にできるほどではないです&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="開発パソコンカタカタ"&gt;開発・パソコンカタカタ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;S/W，特に Web を中心にやってます
&lt;ul&gt;
&lt;li&gt;Scripting や Automation をよくやります&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;様々なコストを極力抑えて開発することに強く気持ちがあります&lt;/li&gt;
&lt;li&gt;環境構築をしっかり行ったうえで，Best Practice や DevOps を意識して開発しています&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>