<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>yanhuolt</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://yanhuolt.dpdns.org/</id>
  <link href="https://yanhuolt.dpdns.org/" rel="alternate"/>
  <link href="https://yanhuolt.dpdns.org/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, yanhuolt</rights>
  <subtitle>记录一些想法、折腾和踩过的坑</subtitle>
  <title>yanhuolt</title>
  <updated>2026-09-28T13:30:00.000Z</updated>
  <entry>
    <author>
      <name>yanhuolt</name>
    </author>
    <category term="建站" scheme="https://yanhuolt.dpdns.org/categories/%E5%BB%BA%E7%AB%99/"/>
    <category term="Cloudflare" scheme="https://yanhuolt.dpdns.org/tags/Cloudflare/"/>
    <category term="建站" scheme="https://yanhuolt.dpdns.org/tags/%E5%BB%BA%E7%AB%99/"/>
    <category term="Hexo" scheme="https://yanhuolt.dpdns.org/tags/Hexo/"/>
    <content>
      <![CDATA[<p>把博客从 Astro 换成了 Hexo + Butterfly 主题。这篇记录一下现在的技术选型和几个实际的坑。</p><h2 id="为什么是纯静态"><a href="#为什么是纯静态" class="headerlink" title="为什么是纯静态"></a>为什么是纯静态</h2><p>博客的本质就是「把 Markdown 渲染成 HTML 然后分发出去」。一旦接受这个前提，动态博客（WordPress 那类，PHP + 数据库）就是明显的过度设计：</p><ul><li>要维护运行环境和数据库备份</li><li>要操心安全补丁</li><li>服务器挂了站点就挂了</li></ul><p>静态站点把这些全部消掉——<strong>构建产物就是一堆 HTML 文件</strong>，扔到 CDN 上，没有后端可攻击，没有数据库可炸。</p><h2 id="技术栈"><a href="#技术栈" class="headerlink" title="技术栈"></a>技术栈</h2><table><thead><tr><th>层</th><th>选型</th><th>说明</th></tr></thead><tbody><tr><td>生成</td><td>Hexo 8</td><td>中文社区成熟，主题生态大</td></tr><tr><td>主题</td><td>Butterfly 5.7</td><td>功能齐全，配置项多</td></tr><tr><td>托管</td><td>Cloudflare Pages</td><td>免费、全球 CDN、自动 HTTPS</td></tr><tr><td>搜索</td><td>hexo-generator-searchdb</td><td>构建时生成索引，无后端</td></tr><tr><td>评论</td><td>Giscus</td><td>基于 GitHub Discussions，无数据库</td></tr><tr><td>统计</td><td>Pages Functions + KV</td><td>自建，不依赖第三方</td></tr></tbody></table><h2 id="为什么不用不蒜子"><a href="#为什么不用不蒜子" class="headerlink" title="为什么不用不蒜子"></a>为什么不用不蒜子</h2><p>中文博客做浏览量统计，默认答案通常是不蒜子（busuanzi）。但实测它的计数接口主机 <code>events.busuanzi.ibruce.info</code> <strong>已经不可达</strong>——脚本能加载，数据拿不到。</p><p>所以改成自己写：一个 Cloudflare Pages Function 加一个 KV 命名空间。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// functions/api/views.ts 的核心逻辑</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">onRequestPost</span> = <span class="keyword">async</span> (<span class="params">&#123; request, env &#125;</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; path &#125; = <span class="keyword">await</span> request.<span class="title function_">json</span>();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> current = <span class="title class_">Number</span>(<span class="keyword">await</span> env.<span class="property">VIEWS</span>.<span class="title function_">get</span>(path)) || <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">await</span> env.<span class="property">VIEWS</span>.<span class="title function_">put</span>(path, <span class="title class_">String</span>(current + <span class="number">1</span>));</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="title class_">Response</span>.<span class="title function_">json</span>(&#123; path, <span class="attr">views</span>: current + <span class="number">1</span> &#125;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>前端只要扫一遍页面上的计数占位符，汇总成一个请求即可。这样没有任何外部依赖，数据也在自己的账号里。</p><h2 id="几个实际的坑"><a href="#几个实际的坑" class="headerlink" title="几个实际的坑"></a>几个实际的坑</h2><blockquote><p>Cloudflare 绑定自定义域名时<strong>不会</strong>自动创建 DNS 记录。</p></blockquote><p>这是最容易卡住的地方。域名状态会一直停在 <code>pending</code>，报 <code>CNAME record not set</code>，HTTPS 完全不可用。必须手动加一条指向 <code>&lt;项目名&gt;.pages.dev</code> 的 CNAME，而且<strong>代理（橙色云朵）必须打开</strong>。</p><p>另外，Hexo 的构建产物目录也叫 <code>public/</code>，和很多静态站点生成器的「静态资源目录」同名，切换框架时容易搞混。</p><h2 id="部署流程"><a href="#部署流程" class="headerlink" title="部署流程"></a>部署流程</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">npm run server   <span class="comment"># 本地预览，热更新</span></span><br><span class="line">npm run build    <span class="comment"># 生成到 public/</span></span><br><span class="line">npm run deploy   <span class="comment"># 推到 Cloudflare Pages</span></span><br></pre></td></tr></table></figure><p>没有 CI、没有服务器、没有需要守护的进程。构建产物推上去，全球节点几分钟内就是最新的。</p>]]>
    </content>
    <id>https://yanhuolt.dpdns.org/posts/hello-hexo/</id>
    <link href="https://yanhuolt.dpdns.org/posts/hello-hexo/"/>
    <published>2026-09-27T02:00:00.000Z</published>
    <summary>从零搭一个纯静态博客：Hexo 生成、Cloudflare Pages 托管、自建浏览量统计，全程不依赖任何第三方统计服务。</summary>
    <title>这个博客现在的技术栈</title>
    <updated>2026-09-28T13:30:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>yanhuolt</name>
    </author>
    <category term="技术" scheme="https://yanhuolt.dpdns.org/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="Cloudflare" scheme="https://yanhuolt.dpdns.org/tags/Cloudflare/"/>
    <category term="网络" scheme="https://yanhuolt.dpdns.org/tags/%E7%BD%91%E7%BB%9C/"/>
    <content>
      <![CDATA[<p>以前想让外网访问家里的服务，路子无非两条：要么在路由器上做端口映射并暴露公网 IP，要么租一台有公网 IP 的 VPS 做反向代理。</p><p>这两种做法都有个共同的毛病——<strong>你确实在公网上开了一个入口</strong>。哪怕只转发 443，扫描器也会很快找上门。</p><p>Cloudflare Tunnel 换了思路：连接是<strong>从内网主动向外建立的</strong>，公网侧根本不存在可以扫描的入站端口。</p><h2 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">访客 → Cloudflare 边缘节点 →（已建立的长连接）→ cloudflared → localhost:3000</span><br></pre></td></tr></table></figure><p><code>cloudflared</code> 进程在你这台机器上跑着，启动时主动连到 Cloudflare 并保持一条隧道。外部请求到达边缘节点后，顺着这条已建立的连接回传进来。</p><p>所以：</p><ul><li>路由器上<strong>不需要</strong>开任何端口</li><li><strong>不需要</strong>公网 IP，NAT 后面就行</li><li>出口流量，防火墙通常不会拦</li></ul><h2 id="基本用法"><a href="#基本用法" class="headerlink" title="基本用法"></a>基本用法</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">cloudflared tunnel login</span><br><span class="line">cloudflared tunnel create my-blog</span><br><span class="line">cloudflared tunnel route dns my-blog blog.example.com</span><br></pre></td></tr></table></figure><p>配置文件 <code>~/.cloudflared/config.yml</code>：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">tunnel:</span> <span class="string">ff19965a-1b58-45e6-91bc-6d1d2202293c</span></span><br><span class="line"><span class="attr">credentials-file:</span> <span class="string">/home/user/.cloudflared/ff19965a-....json</span></span><br><span class="line"></span><br><span class="line"><span class="attr">ingress:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">hostname:</span> <span class="string">blog.example.com</span></span><br><span class="line">    <span class="attr">service:</span> <span class="string">http://localhost:3000</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># 兜底规则必须放最后，否则 cloudflared 会拒绝启动</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">http_status:404</span></span><br></pre></td></tr></table></figure><p>最后那个 <code>http_status:404</code> 是<strong>必需的</strong>——少了它 cloudflared 会启动失败，因为 ingress 规则要求有一个不带 hostname 的兜底项。</p><h2 id="几个会踩的点"><a href="#几个会踩的点" class="headerlink" title="几个会踩的点"></a>几个会踩的点</h2><p><strong>DNS 记录必须指向隧道。</strong> 用 <code>tunnel route dns</code> 加出来的记录是一条 CNAME，指向 <code>&lt;隧道ID&gt;.cfargotunnel.com</code>，而且必须保持**橙色云朵（已代理）**打开。手动改成 DNS only 就废了。</p><p><strong>Windows 上装成服务：</strong></p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cloudflared service install</span><br></pre></td></tr></table></figure><p><strong>区分「隧道」和「DNS」两套权限。</strong> Cloudflare 的 API Token 权限粒度很细——能改 DNS 的 Token 未必能管理隧道，<code>Account → Cloudflare Tunnel → Edit</code> 是另一个独立权限。报 <code>9109</code> 的时候先怀疑这个。</p><h2 id="和-Pages-怎么选"><a href="#和-Pages-怎么选" class="headerlink" title="和 Pages 怎么选"></a>和 Pages 怎么选</h2><table><thead><tr><th></th><th>Cloudflare Tunnel</th><th>Cloudflare Pages</th></tr></thead><tbody><tr><td>需要常开的机器</td><td>是</td><td>否</td></tr><tr><td>适合动态服务</td><td>是</td><td>否（纯静态）</td></tr><tr><td>全球加速</td><td>依赖源站</td><td>原生 CDN</td></tr><tr><td>运维成本</td><td>中</td><td>几乎为零</td></tr></tbody></table><p>结论很简单：<strong>静态博客用 Pages，动态服务才上 Tunnel。</strong> 让一台机器整年开着只为托管几页 Markdown，实在没必要。</p><p>不过 Tunnel 有个 Pages 替代不了的优势：本地开发环境可以挂个域名直接给别人看，改完代码刷新即生效，不用重新部署。</p>]]>
    </content>
    <id>https://yanhuolt.dpdns.org/posts/cloudflare-tunnel-notes/</id>
    <link href="https://yanhuolt.dpdns.org/posts/cloudflare-tunnel-notes/"/>
    <published>2026-09-20T06:00:00.000Z</published>
    <summary>不开放任何入站端口、不需要公网 IP，用 Tunnel 把家里的服务安全地挂到公网域名上。</summary>
    <title>Cloudflare Tunnel 把内网服务暴露出去</title>
    <updated>2026-09-27T13:30:34.127Z</updated>
  </entry>
  <entry>
    <author>
      <name>yanhuolt</name>
    </author>
    <category term="随笔" scheme="https://yanhuolt.dpdns.org/categories/%E9%9A%8F%E7%AC%94/"/>
    <category term="随笔" scheme="https://yanhuolt.dpdns.org/tags/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<p>搭这个博客的时候，我差点又掉进同一个坑里。</p><p>先是纠结静态站点生成器选哪个：Hugo 构建快、Hexo 主题多、Astro 组件化好。选定之后，又开始比较主题——看了几十个，收藏了七个。接着觉得默认样式不够「有辨识度」，准备自己写一套设计系统。</p><p>到第二天晚上我才反应过来：<strong>一篇正文都还没写。</strong></p><h2 id="工具链是手段，不是目的"><a href="#工具链是手段，不是目的" class="headerlink" title="工具链是手段，不是目的"></a>工具链是手段，不是目的</h2><p>这种事我干过不止一次。</p><p>想学摄影，先花两周研究机身镜头参数；想练字，先挑了半天笔和纸；想读点书，先折腾出一套带标签、双链、自动同步的笔记系统。等装备齐了，那股劲也过去了。</p><p>有个说法叫「生产力色情」（productivity porn）——沉迷于优化工作流程本身，享受那种「一切尽在掌握」的快感，却始终没开始真正的工作。搭工具链的反馈是即时且确定的（装好了就是装好了），而写文章的反馈是延迟且模糊的（写完了也不知道好不好）。大脑当然会选前者。</p><h2 id="两条规矩"><a href="#两条规矩" class="headerlink" title="两条规矩"></a>两条规矩</h2><p>所以这次我给自己定了两条：</p><p><strong>第一，工具链最多花一天。</strong> 一天之内跑不起来，就直接用最土的办法——手写 HTML 也不是不行。工具的价值在于降低摩擦，如果搭工具的过程本身成了最大的摩擦，那它就是负资产。</p><p><strong>第二，写作和改版分开。</strong> 写正文的时候不碰样式。看着不顺眼的地方记在待办里，攒够一批再统一改。边写边调 CSS 的结果通常是文章写了一半，样式改了二十遍。</p><h2 id="反过来想"><a href="#反过来想" class="headerlink" title="反过来想"></a>反过来想</h2><p>当然也不是说折腾工具就毫无价值——每次折腾都会摸清楚一些东西：静态生成器的构建流程、CDN 的缓存规则、API Token 的权限模型，这些知识以后都用得上。</p><p>区别在于<strong>它应该是写文章的副产品，而不是写文章的替代品</strong>。</p><blockquote><p>完成比完美重要。先让东西存在，再让它变好。</p></blockquote><p>现在这个博客的样式说不上多精致，但至少它已经在了。这比停在草稿箱里的完美设计稿强得多。</p>]]>
    </content>
    <id>https://yanhuolt.dpdns.org/posts/toolchain-first-trap/</id>
    <link href="https://yanhuolt.dpdns.org/posts/toolchain-first-trap/"/>
    <published>2026-09-12T13:00:00.000Z</published>
    <summary>花三天搭环境、写十分钟正文——聊聊这种本末倒置，以及我给自己定的两条规矩。</summary>
    <title>关于「先把工具链搭好」这件事</title>
    <updated>2026-09-27T13:30:34.129Z</updated>
  </entry>
</feed>
