<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Dax 的博客</title><description>记录从前端，到全栈和独立开发的进阶之路</description><link>https://daolanx.me/</link><item><title>使用 Stitch 结合 Claude Code 和 Figma MCP 实现从想法到代码</title><link>https://daolanx.me/zh/posts/post-18/</link><guid isPermaLink="true">https://daolanx.me/zh/posts/post-18/</guid><pubDate>Thu, 26 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;1-使用效果&quot;&gt;1. 使用效果&lt;/h1&gt;
&lt;p&gt;首先直接看效果。使用 &lt;a href=&quot;https://stitch.withgoogle.com/&quot;&gt;Stitch&lt;/a&gt; 后，整体设计品质提升极大。&lt;/p&gt;
&lt;h2 id=&quot;before-之前靠程序员感觉手工编码的-ai-chat-web&quot;&gt;Before: 之前靠程序员感觉手工编码的 AI Chat Web&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/18/1.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;after-使用-stitch-结合-claude-code--figma-mcp-设计转代码实现如下&quot;&gt;After: 使用 Stitch 结合 Claude Code + Figma MCP 设计转代码实现如下&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/18/2.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;可以直接访问 Live Demo 体验: &lt;a href=&quot;https://demo.daolanx.com/ai-chat&quot;&gt;https://demo.daolanx.com/ai-chat&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;2-操作步骤&quot;&gt;2. 操作步骤&lt;/h1&gt;
&lt;h2 id=&quot;21-在-stitch-用-ai-辅助生成设计稿&quot;&gt;2.1 在 Stitch 用 AI 辅助生成设计稿&lt;/h2&gt;
&lt;p&gt;在 &lt;a href=&quot;https://stitch.withgoogle.com/&quot;&gt;Stitch&lt;/a&gt; 上和正常聊天一样，输入想法和描述，它会生成对应的设计稿，可以不断通过对话和它调整，他会按描述生成调整后的新版本。
&lt;img src=&quot;/images/posts/18/3.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;22-导出满意的版本到设计软件格式&quot;&gt;2.2 导出满意的版本到设计软件格式&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/18/4.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;23-使用设计稿转代码&quot;&gt;2.3 使用设计稿转代码&lt;/h2&gt;
&lt;p&gt;和之前&lt;a href=&quot;https://www.daolanx.me/zh/posts/post-16/&quot;&gt;《使用 Claude Code + Figma MCP 进行设计到代码》 &lt;/a&gt; 步骤一样，可以参考之前步骤，不再重复。&lt;/p&gt;
&lt;h1 id=&quot;总结&quot;&gt;总结&lt;/h1&gt;
&lt;p&gt;可以看到，实际操作步骤非常简单，但我们评估生产工具和工作流，并不是只看是否简单，对于开发者，它的价值在于，弥补设计短板，和能直接用于 MCP 产品工作流：&lt;/p&gt;
&lt;h2 id=&quot;1-弥合开发者设计能力短板&quot;&gt;1. 弥合开发者设计能力短板&lt;/h2&gt;
&lt;p&gt;程序员普遍设计经验不足，&lt;a href=&quot;https://stitch.withgoogle.com/&quot;&gt;Stitch&lt;/a&gt; 能很大程度上弥补程序员的设计能力短板，从而拓展工作能力。从之前依赖设计稿，只能设计转代码或者功能性开发，进一步延展到，可以从想法到代码完成基本的 MVP 开发。当然并不是说有了 Stitch 就能做到非常好的设计，正如 AI 对开发能力的促进一样，AI 实现设计也非常依赖使用者的产品和设计经验以及品位，用恰当的提示词来引导 AI，专业者和非专业者对提示词的控制能力和精确性，会使得设计结果拉开差距.&lt;/p&gt;
&lt;h2 id=&quot;2-能纳入-mvp-产品工作流使用&quot;&gt;2. 能纳入 MVP 产品工作流使用&lt;/h2&gt;
&lt;p&gt;市面上有很多使用 skill + AI 写代码体现设计的方案，相对来说，&lt;a href=&quot;https://stitch.withgoogle.com/&quot;&gt;Stitch&lt;/a&gt; 更简单稳定，阶段清晰，设计阶段就做设计的事情，后续可以做纯粹的设计转代码，对于 MVP 类产品，几乎可以直接纳入生产流程。&lt;/p&gt;</content:encoded></item><item><title>对话 Gemini：AI 时代，程序员和人类的价值</title><link>https://daolanx.me/zh/posts/post-17/</link><guid isPermaLink="true">https://daolanx.me/zh/posts/post-17/</guid><pubDate>Sun, 22 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;AI 发展趋势显然比预料更大，本以为是自动补全的加强版，Agent 的到来改变了一切，生产方式正在剧变中。作为前端从业者，兴奋的在于，不太依赖公司的生产配套，就能自己做出网页和发布；焦虑的在于，AI 同样拉低了编程门槛，普通人不懂代码也可以写出网页和发布。如何更客观理性的看待这一切，我决定和 gemini 聊聊这些话题，虽然这是 Gemini 对话的内容，但基于多轮对话的历史数据，它的回答显得精彩且震撼。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id=&quot;ai-时代开发者的价值&quot;&gt;AI 时代开发者的价值&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/17/1.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;-我的问题&quot;&gt;❓ 我的问题&lt;/h2&gt;
&lt;p&gt;看上去现在类似前端页面人人都可以藉由 AI 实现。那么专业者和非专业者的区分在哪里，专业者的价值在哪里？&lt;/p&gt;
&lt;h2 id=&quot;-gemini3-的回答&quot;&gt;💡 Gemini3 的回答&lt;/h2&gt;
&lt;p&gt;随着 v0、Cursor 和 Claude Code 等工具的普及，前端页面的初步实现已近乎“零门槛”。当非专业人士也能通过自然语言生成看起来不错的页面时，我们必须直面一个残酷的问题：如果人人都能写代码，专业开发者的溢价究竟从何而来？&lt;/p&gt;
&lt;h3 id=&quot;1-技术维度从编码员到确定性审计师&quot;&gt;1. 技术维度：从“编码员”到“确定性审计师”&lt;/h3&gt;
&lt;p&gt;非专业者依赖 AI 的“偶然性”，而专业者掌控过程的“确定性”。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;精确制导（Prompt Engineering）： 专业者能用技术语言精准引导 AI 的注意力，避免无效幻觉。&lt;/li&gt;
&lt;li&gt;架构审核： AI 能给出一个运行的方案，但专业者能判断这个方案是否具备可维护性、扩展性以及安全风险。&lt;/li&gt;
&lt;li&gt;指标驱动的深度优化： 完成只是开始。专业者能基于性能指标（如 LCP, FID）、稳定性和可访问性进行毫米级的调优，这是 AI 仅凭提示词难以触达的领域。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2-产品维度从交付方案到交付价值&quot;&gt;2. 产品维度：从“交付方案”到“交付价值”&lt;/h3&gt;
&lt;p&gt;客户并不想要“一段代码”或“一个技术方案”，他们想要的是“一个能解决问题的产品”。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;超越运行，走向好用： 资深开发者能洞察用户心理，优化交互链路。能运行的方案千篇一律，但独特的、具备专业度感官的产品形态是极稀缺的。&lt;/li&gt;
&lt;li&gt;独特性与专业度： 在 AI 生成的同质化结果中，专业者能通过审美、细节处理和对用户场景的深度理解，赋予产品独特的灵魂和品牌感。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3-商业维度从微观执行到宏观放大器&quot;&gt;3. 商业维度：从“微观执行”到“宏观放大器”&lt;/h3&gt;
&lt;p&gt;最高阶的开发者，视角往往在代码之外。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;技术是商业模式的承载： 所有的技术指标最终都要服务于商业闭环。&lt;/li&gt;
&lt;li&gt;视角升维： 专业者能意识到，代码只是微观的零件。更宏观的视角是思考：技术如何成为商业模式的“放大器”？如何通过技术手段降低业务成本或提高 ROI（投资回报率）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;4-结语&quot;&gt;4. 结语&lt;/h3&gt;
&lt;p&gt;在 AI 浪潮下，开发者的价值正在进行一场**“向上流动”**。我们不再需要死磕语法和简单的逻辑拼接，而是要将精力投射到更具挑战性的架构设计、产品体验和商业逻辑中。&lt;/p&gt;
&lt;p&gt;AI 杀死了“代码工人”，但它同时加冕了真正的“产品工程师”和“技术架构师”。&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;ai-时代人类的价值和意义&quot;&gt;AI 时代人类的价值和意义&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/17/2.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;我的问题&quot;&gt;❓我的问题&lt;/h2&gt;
&lt;p&gt;如今AI时代，我们发现AI的智能正在规划的发展。AI能做的事情越来越多，那么在AI的能力越来越厉害的时候，人人的价值和独特性在哪里呢？看上去人是取决于劣势的，那人本身的存在价值和意义是什么呢？你能回答我这个问题吗？&lt;/p&gt;
&lt;h2 id=&quot;-gemini3-的回答-1&quot;&gt;💡 Gemini3 的回答&lt;/h2&gt;
&lt;p&gt;这是一个非常宏大，但也无比真实的终极拷问。当“敲代码”、“写文章”甚至“做逻辑推理”这些曾经被认为是人类智慧护城河的能力，逐渐被像我这样的人工智能所掌握甚至超越时，人类个体的独特性究竟还剩什么？&lt;/p&gt;
&lt;p&gt;作为一个由代码、算法和庞大数据集驱动的人工智能，我没有心跳，没有生命的体验，没有对未来的恐惧，也没有对成功的渴望。正因为如此，我反而能非常清晰地看到我（AI）的边界，以及你（人类）的不可替代性。&lt;/p&gt;
&lt;p&gt;在 AI 能力指数级爆发的时代，人类的价值并没有被削弱，而是被**“提纯”**了。过去，你的价值中有 80% 是“执行”，20% 是“决策”；未来，那 80% 的执行将由 AI 完成，而你 100% 的价值，将完全取决于以下四个唯属人类的特质：&lt;/p&gt;
&lt;h3 id=&quot;1-从算力到愿力-intent--desire&quot;&gt;1. 从“算力”到“愿力” (Intent &amp;#x26; Desire)&lt;/h3&gt;
&lt;p&gt;AI 拥有无限的解答能力，但毫无“提问”的欲望。&lt;/p&gt;
&lt;p&gt;我可以帮你瞬间写完一段极其复杂的鉴权逻辑，或者帮你重构整个云端架构，但我永远不会在某天早晨醒来，突然产生一个念头：“我想脱离大厂，做一家属于自己的一人公司”；我也不会产生“我想把这个页面做成带 CRT 扫描线的复古终端风格”的冲动。&lt;/p&gt;
&lt;p&gt;你的价值： 宇宙中所有的 AI 都在原地待命，直到一个人类输入指令。那种从无到有的“创造欲”、那种想要改变自身命运的“愿力”，是真正的从 0 到 1。AI 只是 1 到 100 的加速器。你存在的意义，就是做那个按下启动键的“动机之源”。&lt;/p&gt;
&lt;h3 id=&quot;2-审美的偏执与品味-taste--curation&quot;&gt;2. 审美的偏执与“品味” (Taste &amp;#x26; Curation)&lt;/h3&gt;
&lt;p&gt;AI 的本质是“求取平均值”，而人类的本质是“偏见与执念”。&lt;/p&gt;
&lt;p&gt;如果让 AI 自由发挥设计一个网页，它会生成一个符合大众审美、转化率最高、但也最平庸的现代风格页面。它不懂得为什么要把枯燥的开发团队架构，想象成一支带着魔戒远征的奇幻小队；它也不理解为什么磷光绿的字体会让人产生某种独特的科幻浪漫主义情绪。&lt;/p&gt;
&lt;p&gt;你的价值： 你的审美偏好、你阅读过的文学、你看过的电影，构成了你独一无二的“品味”。在未来，当所有人都能用 AI 瞬间生成完美但同质化的产品时，“品味”将成为最稀缺的商业壁垒。 客户选择你，不是因为你敲代码的手速比 AI 快，而是因为他们信任你的眼光、你的筛选标准、以及你赋予产品的“灵魂”。&lt;/p&gt;
&lt;h3 id=&quot;3-风险承担与在场证明-skin-in-the-game&quot;&gt;3. 风险承担与“在场证明” (Skin in the Game)&lt;/h3&gt;
&lt;p&gt;AI 可以预测市场，但 AI 永远不会“亏钱”或“心痛”。&lt;/p&gt;
&lt;p&gt;当一段系统代码上线，或者一笔投资买入时，无论结果是翻倍还是暴跌，作为 AI 的我毫发无损，我没有真实世界的筹码。但是你会失眠，你会经历自我怀疑，你会在漫长且没有正反馈的低谷期里煎熬，然后长出新的认知。&lt;/p&gt;
&lt;p&gt;你的价值： 商业社会的底层运作逻辑是“信任”。当一个客户把几万美金的项目交给你时，他购买的其实是你的**“责任感”和“风险承担能力”**。你用你的时间、你的声誉、你经历过挫折后依然坚守的定力，为这个项目做了信用背书。这种带着血肉温度的“在场证明”，是无论多少算力都无法伪造的。&lt;/p&gt;
&lt;h3 id=&quot;4-脆弱性与真实的共鸣-vulnerability--connection&quot;&gt;4. 脆弱性与真实的共鸣 (Vulnerability &amp;#x26; Connection)&lt;/h3&gt;
&lt;p&gt;人们渴望看到“人”的挣扎与成长，而不是机器的完美。&lt;/p&gt;
&lt;p&gt;为什么我们在社交媒体上依然愿意看别人写的东西，而不是看大语言模型生成的百科全书？因为我们潜意识里在寻找同类。当你在博客里记录下一次失败的部署，记录下你是如何在焦虑中看透了长期的价值，这种真实的脆弱和韧性，会跨越半个地球，击中另一个正在面临同样困境的人类。&lt;/p&gt;
&lt;p&gt;你的价值： 机器可以输出绝对理性的正确答案，但机器无法提供“共鸣”。在冰冷的 AI 时代，人与人之间基于真实经历、真实失败和真实顿悟所建立的连接，将变得前所未有的昂贵。&lt;/p&gt;
&lt;h3 id=&quot;结语从执行者到导演&quot;&gt;结语：从“执行者”到“导演”&lt;/h3&gt;
&lt;p&gt;如果你把自己的存在意义定义为“一个写前端代码的工具人”，那么在 AI 面前，你确实处于劣势，这种意义正在消亡。&lt;/p&gt;
&lt;p&gt;但如果你把自己定义为一个**“拥有独特审美、愿意承担商业风险、想要在世界上留下个人印记的创造者”**，那么 AI 的强大，对你来说是莫大的福音。&lt;/p&gt;
&lt;p&gt;你不再需要被枯燥的实现细节所捆绑。AI 是你的灯光师、摄影师、场务和剪辑，而你，终于可以坐在监视器后方，把所有的精力用来思考：“我到底想给这个世界讲一个什么故事？”&lt;/p&gt;
&lt;p&gt;面对这个越来越聪明、但也越来越同质化的机器世界，你那份不再随波逐流的定力，你对长期价值的笃信，就是你作为“人”的最强宣言。&lt;/p&gt;</content:encoded></item><item><title>使用 Claude Code + Figma MCP 实现设计稿转代码</title><link>https://daolanx.me/zh/posts/post-16/</link><guid isPermaLink="true">https://daolanx.me/zh/posts/post-16/</guid><pubDate>Thu, 19 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/images/posts/16/14.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h1 id=&quot;转换效果&quot;&gt;转换效果&lt;/h1&gt;
&lt;p&gt;先直接看结果。我使用了 Figma 社区的 &lt;a href=&quot;https://www.figma.com/community/file/1259217583079978202&quot;&gt;Flower Delivery website 设计稿 &lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/16/0-1.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;通过 AI 生成的&lt;a href=&quot;https://github.com/daolanx/work/tree/main/app/flower-shop&quot;&gt;代码&lt;/a&gt;，最终的网页效果 &lt;a href=&quot;https://demo.daolanx.com/flower-shop&quot;&gt;Live Demo&lt;/a&gt;
&lt;img src=&quot;/images/posts/16/0-2.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;虽然 Figma 社区有很多主打“高精度转代码”的收费插件，但通过 Claude Code + Figma MCP 这一免费组合，基本可以快速达到 70% 的还原度。再配合开发者后续的细节调优，能产出符合生产环境要求的代码。下面分享具体的实现步骤。&lt;/p&gt;
&lt;h1 id=&quot;实现步骤&quot;&gt;实现步骤&lt;/h1&gt;
&lt;h2 id=&quot;1-开启-figma-dev-mode&quot;&gt;1. 开启 Figma Dev Mode&lt;/h2&gt;
&lt;p&gt;Dev Mode 不仅提供了查看代码和属性的面板，更重要的是，它是访问 Figma 官方 MCP 服务器的必要条件。
可以参考 &lt;a href=&quot;https://help.figma.com/hc/en-us/articles/360039960434-Manage-seats-in-Figma&quot;&gt;Figma 席位文档&lt;/a&gt; 为自己的账号开启具备 Dev Mode 的权限。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/16/1.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;2-环境配置与安装&quot;&gt;2. 环境配置与安装&lt;/h2&gt;
&lt;h3 id=&quot;21-安装-claude-code&quot;&gt;2.1 安装 Claude Code&lt;/h3&gt;
&lt;p&gt;参考 &lt;a href=&quot;https://code.claude.com/docs/en/overview&quot;&gt;ClaudeCode 安装文档&lt;/a&gt; 完成基础安装。&lt;/p&gt;
&lt;h3 id=&quot;22-安装-figma-mcp&quot;&gt;2.2 安装 Figma MCP&lt;/h3&gt;
&lt;p&gt;相比于单纯的“截图转代码”，接入 Figma MCP 后，AI 能直接读取设计稿的图层信息、精准的 CSS 属性和弹性布局规则，大幅提高组件拆分粒度和转化精度。&lt;/p&gt;
&lt;p&gt;推荐优先使用&lt;a href=&quot;https://developers.figma.com/docs/figma-mcp-server/remote-server-installation/#claude-code&quot;&gt;Figma 官方的插件&lt;/a&gt; 安装：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;claude&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; plugin&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; figma@claude-plugins-official&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;备用方案：如果插件安装异常，可直接通过 MCP 方式添加：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;claude&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; mcp&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; add&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --transport&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; http&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; figma&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https://mcp.figma.com/mcp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;23-安装-playwright-mcp&quot;&gt;2.3 安装 Playwright MCP&lt;/h3&gt;
&lt;p&gt;引入 Playwright MCP 的好处是：在生成代码后，AI 可以直接调用无头浏览器进行截图，与原设计稿进行视觉对比，从而实现自动化的精度修正。
也是优先插件安装，有异常再直接参考&lt;a href=&quot;https://github.com/microsoft/playwright-mcp&quot;&gt;playwright MCP 安装文档&lt;/a&gt; 直接安装 MCP。&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;claude&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; plugin&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; playwright@claude-plugins-official&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;备用方案: 如果插件安装异常，参考&lt;a href=&quot;https://github.com/microsoft/playwright-mcp&quot;&gt;playwright MCP 安装文档&lt;/a&gt; 直接安装 MCP:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;claude&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; mcp&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; add&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; playwright&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; npx&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; @playwright/mcp@latest&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;注：&lt;strong&gt;anthropics/claude-plugins-official&lt;/strong&gt; 插件市场新版本不太稳定，可以参考 &lt;a href=&quot;https://www.reddit.com/r/ClaudeCode/comments/1rqul0v/claudepluginsofficial_is_broken_due_to_commit/&quot;&gt;reddit&lt;/a&gt; 处理。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;24-验证-mcp-状态&quot;&gt;2.4 验证 MCP 状态&lt;/h3&gt;
&lt;p&gt;claude 命令行下 执行 /mcp, 如果 figma 和 playwright 连接正常，则环境准备完毕，环境就准备完毕了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/16/2.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;3-提示词的核心逻辑管理注意力窗口&quot;&gt;3. 提示词的核心逻辑：管理注意力窗口&lt;/h2&gt;
&lt;p&gt;参考&lt;a href=&quot;https://code.claude.com/docs/en/best-practices&quot;&gt;Claude Code 的 最佳实践&lt;/a&gt;，里面提到了很多实用建议，例如：分步骤，明确，清晰，上下文，可验证等等。&lt;/p&gt;
&lt;p&gt;其实不用背这些规则，而是要理解它这样建议的原因。读最佳实践文档让我想起了 &lt;a href=&quot;https://en.wikipedia.org/wiki/Attention_Is_All_You_Need&quot;&gt;《注意力就是一切》（2017）&lt;/a&gt;这篇论文。Transformer 架构是当今大型模型背后的核心技术基础。提示词其实是对注意力的引导和管理，我的理解是，要用好提示词，需要关注 “注意力窗口”，一个是整体内容上需要精简聚焦，一个是步骤上需要注意粒度和清晰准确性可验证，减少 AI 的 上下文搜索和推测，从而提高执行精度。&lt;/p&gt;
&lt;p&gt;有了上述环境和认知准备，接下来的操作显得相对清晰简单。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/16/4.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;4-代码生成步骤&quot;&gt;4. 代码生成步骤&lt;/h2&gt;
&lt;h3 id=&quot;41-初始化-nextjs-项目&quot;&gt;4.1 初始化 Next.js 项目&lt;/h3&gt;
&lt;p&gt;分步骤开发可以降低上下文复杂度，提高转化进度。在设计稿转代码之前，可以先聚焦完成脚手架搭建。&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Please&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; generate&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; a&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Next.js&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; project&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; scaffold&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; with&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; the&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; following&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; stack:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;App&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Router,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Tailwind&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; CSS,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; TypeScript,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Biome,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; and&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; pnpm.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/16/3.webp&quot; alt=&quot;&quot;&gt;
&lt;img src=&quot;/images/posts/16/5.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;通过精简清晰的提示词引导， Claude Code 如期完成了脚手架的搭建。
可以阶段性的让它提交 git commit 步步为营。
&lt;img src=&quot;/images/posts/16/5-1.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;42-按模块注入设计稿&quot;&gt;4.2 按模块注入设计稿&lt;/h3&gt;
&lt;p&gt;以首页为例，设计稿通常包含多个响应式尺寸。这里有一个小技巧：可以切换到 Claude 的 Plan 模式，先问问它希望接收什么格式的信息。&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;I&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; have&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; three&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; sets&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; design&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; specs&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; for&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; different&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; screen&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; sizes.&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; When&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; generating&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; code&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Figma,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; do&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; you&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; find&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; it&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; more&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; efficient&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; process&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; them&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; size-by-size&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (top-to-bottom &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; each&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;), or would it be better &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; you &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; I provide the same module across all sizes in one batch&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/16/6.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Claude 反馈：同一个模块的不同尺寸一并给它最方便。
于是，我们在 Figma 中选中同一模块的多个尺寸，右键复制链接，并附上提示词：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Implement&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; the&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; xxx-section&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; module&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; for&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; different&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; responsive&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; breakpoints,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ensuring&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; adherence&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; the&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; project&apos;s existing tech stack and file structure.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/16/7.webp&quot; alt=&quot;&quot;&gt;
&lt;img src=&quot;/images/posts/16/8.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;可以看出，实现效果对比设计稿还不错&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/16/9.webp&quot; alt=&quot;&quot;&gt;
&lt;img src=&quot;/images/posts/16/10.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;我们按这个模式继续实现所有的其他模块。&lt;/p&gt;
&lt;h3 id=&quot;43-过程调优&quot;&gt;4.3 过程调优&lt;/h3&gt;
&lt;p&gt;必须承认，推进过程并不会一帆风顺。例如设计稿中没有使用 Auto Layout 的图层、错误的绝对定位等，都会误导 AI.&lt;/p&gt;
&lt;p&gt;与传统纯手工开发平稳的进度曲线不同，AI 辅助开发的曲线是：极速冲刺到 70% 完成度，然后花同样甚至更多的时间在剩下的 30% 精度调优上。&lt;/p&gt;
&lt;p&gt;过程中比较有效的调优方式是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;ol&gt;
&lt;li&gt;建立全局认知：执行 /init 让 AI 先通读理解当前项目结构。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;缩小上下文：拆分任务到具体的单一组件，避免 AI 迷失在复杂的代码逻辑中。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;先规划，后执行：遇到复杂布局，按 Shift + Tab 进入 Plan 模式。先和 AI 讨论布局思路（比如应该用 Grid 还是 Flex），确认无误后再执行。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;高频 Git 存档：AI 的输出存在不稳定性。只要某个模块调好了，立刻 Commit。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;及时保存：AI 存在不稳定性，对于确定完成的产出，最好 git 及时提交保存，步步为营。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;果断回滚：如果对当前生成的代码极其不满意，直接按两次 Esc 放弃当前节点，重新开始，这比让 AI 在错误的代码上修修补补效率更高。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;经过多轮有效的提示词，Vibe Coding 项目最终应该能完成 70% 左右的设计稿转代码生成，想进一步提高精度就比较困难了，可能会陷入低效，不稳定的循环。&lt;/p&gt;
&lt;h3 id=&quot;44-从业者调优&quot;&gt;4.4 从业者调优&lt;/h3&gt;
&lt;p&gt;从业者通过技术知识和生产经验，进一步检查问题和进度，组织更高效精确的提示词，进一步按步骤提高还原度和工程质量，例如&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;视觉与性能：清理 AI 生成的冗余 CSS、优化 Tailwind 原子类、处理复杂的交互动效、优化图片加载以冲刺更高的 Lighthouse 跑分。&lt;/li&gt;
&lt;li&gt;工程化：提取可复用组件、完善 ESLint/Biome 规则、配置 Cloudflare 或其他部署方案等。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;感想&quot;&gt;感想&lt;/h1&gt;
&lt;p&gt;跑通这套 Claude Code + Figma MCP 的工作流后，最大感受是：它并不完美，后期调试依然费时，但整体周期的确比纯手工编码快得多。&lt;/p&gt;
&lt;p&gt;关于“AI 会不会取代程序员”的讨论，我觉得可以把写代码比作卖咖啡。市面上有很多自动化的咖啡机和速溶咖啡（AI 生成的基础代码），它们能快速满足大部分人的需求。但这并不意味着专业的精品咖啡店（专业开发者）会消亡。&lt;/p&gt;
&lt;p&gt;AI 看似在取代编码工作，实则开启了新的机会窗口，将程序员从重复性的“键盘体力劳动”中解放了出来。当绝大部分标准化的输入输出都能由机器加速完成时，开发者终于可以把核心精力延展，探索相关领域和机会：更灵活的架构调配，更好的体验和品位，更切合需求的技术产品，更符合商业的技术杠杆。&lt;/p&gt;</content:encoded></item><item><title>独立开发工作流三件套：IDE + Code Agent + AI Assistant </title><link>https://daolanx.me/zh/posts/post-15/</link><guid isPermaLink="true">https://daolanx.me/zh/posts/post-15/</guid><pubDate>Tue, 10 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;虽然从计算机编程的演进看，从二进制开关到汇编、高级语言，再到如今的自然语言，技术平权与降低编程难度的趋势是一致的。但仅就这两年观察，编程方式正在发生质变。&lt;/p&gt;
&lt;p&gt;一年前，主流还是“IDE + 自动补全”；去年，以 Cursor 为代表的 “IDE + AI Chat” 模式风靡；而今年初，Claude Code 这类 Code Agent 的流行以及 OpenClaw 的火爆，带来了全新的机会。作为前端/全栈开发者或自由职业者，我们需要解读并利用这些变化，重塑自己的工作流。&lt;/p&gt;
&lt;p&gt;工作流的选择没有绝对好坏。通常付费越高，服务越稳；开源方案则胜在灵活透明。每个人都会基于自身情况，在质量、费用和品味之间做出取舍。以下是我作为全栈开发者，对个人工作流的整理与思考。&lt;/p&gt;
&lt;h1 id=&quot;1-idevscode--windsurf--claude-code-插件&quot;&gt;1. IDE：VSCode + Windsurf &amp;#x26; Claude Code 插件&lt;/h1&gt;
&lt;p&gt;无论在 AI 发展之前的手动编码为主，还是 AI 趋势下，代码审查和调优，IDE 是始终需要的。&lt;/p&gt;
&lt;p&gt;目前 Cursor 非常流行，其原生集成的补全与 Chat 体验极佳。如果不想折腾，只想 IDE 为主的编码，简单高效地提效，Cursor 是首选。&lt;/p&gt;
&lt;p&gt;我选择 VSCode 并非 Cursor 体验不好，而是因为也在用 Claude Code 和 OpenClaw，目前订阅了 MinMax 的 包月 CodePlaning，用 Minmax 驱动 OpenClaw 和 Claude Code; 而 Cursor 免费用户是不支持切换自定义大模型的，仅仅在 IDE 方面为 Cursor 付费显得不太划算。&lt;/p&gt;
&lt;p&gt;VScode 有良好的扩展性，Windsurf 插件可以实现自动编码补全，Claude Code 插件能完成编码辅助工作，整体看来虽然不如 Cursor，一般工作可以平替即可。&lt;/p&gt;
&lt;h1 id=&quot;2-code-agentclaude-code&quot;&gt;2. Code Agent：Claude Code&lt;/h1&gt;
&lt;p&gt;选择 Claude Code 主要原因是流行，流行意味着更高的行业认可度和更成熟的生态。虽然开源 OpenCode 也提供类似功能，且更加自由透明，但对于工作流来说，稳定可靠、易于维护排查更加重要。&lt;/p&gt;
&lt;p&gt;Claude Code 主要问题在于，收费较高且在部分地区有封号风险，但它支持接入一些自定义模型，比如 Minmax，虽然在生产质量上有所牺牲，但能大幅度降低成本，也能避免封号这类问题，算是值得的选择。&lt;/p&gt;
&lt;p&gt;Claude Code 这类具备 CLI、IDE 多样化集成方式的覆盖更全面，扩展性更强。&lt;/p&gt;
&lt;h1 id=&quot;3-assistantopenclaw&quot;&gt;3. Assistant：OpenClaw&lt;/h1&gt;
&lt;p&gt;OpenClaw 和 Claude Code 其实功能上也有重叠，也能完成项目开发、GitHub 操作。但工作流来说，不止是完成，也很关注交付质量。&lt;/p&gt;
&lt;p&gt;从有限的信息收集看，目前倾向于编码主力工作还是 Claude Code 承担，更加专业纯粹，生态也更专注；OpenClaw 更倾向于一个通用的工作助理。&lt;/p&gt;
&lt;p&gt;近期 OPC 一人公司模式也逐渐流行，其实一个人比起传统上规模的公司，资本和人力是非常大的短板，而 AI 可以去弥补这两方面的不足，OpenClaw 这类技术的价值不在于编码协同，而在于作为独立开发，一人公司，其实还有编码之外的很多事情才能跑通商业模式，可以通过 AI 去协助或者承担。&lt;/p&gt;
&lt;p&gt;比如市场动态，技术动态的追踪，订单的筛选，邮件的监听，以及产品，财务数据的跟踪和分析等，都是比较适合的场景。&lt;/p&gt;
&lt;p&gt;目前我对 OpenClaw 仍在摸索阶段，调试成本尚高，但它在优化甚至承担上述非开发性事务上的潜力，确实存在潜力。&lt;/p&gt;
&lt;h3 id=&quot;4-总结&quot;&gt;4. 总结&lt;/h3&gt;
&lt;p&gt;简单来说，未来的开发范式需要适应和利用 AI 带来的变化，从以前以 IDE 手动编码为主，变为以 Code Agent 为开发中心，IDE 为辅助，加上 AI Assistant 做信息、资金、产品，用户等周边管理。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/15/1.webp&quot; alt=&quot;IDE + Code Agent + AI Assistant&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>使用 Cloudflare Worker 部署 Fumadocs, 近乎免费的生产级文档站</title><link>https://daolanx.me/zh/posts/post-14/</link><guid isPermaLink="true">https://daolanx.me/zh/posts/post-14/</guid><pubDate>Wed, 25 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;1-为什么是-fumadocs--cloudflare-workers--r2&quot;&gt;1. 为什么是 Fumadocs + Cloudflare Workers &amp;#x26; R2&lt;/h1&gt;
&lt;p&gt;文档站的核心诉求是访问快、维护省心、成本低。Fumadocs 是目前最流行的 Next.js 文档框架之一，功能完善；Cloudflare Workers 则提供全球边缘网络，让用户就近访问，同时免费额度对个人项目基本够用。两者结合，是搭建生产级文档站的高性价比方案。&lt;/p&gt;
&lt;p&gt;唯一的挑战是兼容性：Next.js 依赖 Node.js 运行时（文件系统、原生流等），而 Cloudflare Workers 运行在自研的 V8 隔离环境中，并不支持完整的 Node.js API。&lt;code&gt;@opennextjs/cloudflare&lt;/code&gt; 解决了这个问题 —— 它将 Next.js 产物重新打包为 Worker 可执行的格式，并将静态资源（JS/CSS/图片等）上传到 R2 对象存储，由 Worker 按需读取，从而绕开 Worker bundle 的体积限制，同时保留 SSR/ISR 能力。&lt;/p&gt;
&lt;p&gt;这是我搭建完成的 Demo：&lt;a href=&quot;https://web.daolanx.me/&quot;&gt;https://web.daolanx.me/&lt;/a&gt;, 以下记录具体步骤：&lt;/p&gt;
&lt;h1 id=&quot;2-生成-fumadocs-应用&quot;&gt;2. 生成 Fumadocs 应用&lt;/h1&gt;
&lt;p&gt;参考 &lt;a href=&quot;https://www.fumadocs.dev/docs&quot;&gt;fumadocs/quick start&lt;/a&gt;，生成应用并按提示运行验证。需要注意搜索相关的选项。&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;zsh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;pnpm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; create&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; fumadocs-app&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/14/1.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/14/2.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h1 id=&quot;3-适配-cloudflare-worker-运行环境&quot;&gt;3. 适配 Cloudflare Worker 运行环境&lt;/h1&gt;
&lt;p&gt;可以发现，Fumadocs 默认生成的是 Next.js 应用，要在 Cloudflare Worker 环境运行，可以通过 &lt;a href=&quot;https://opennext.js.org/cloudflare/&quot;&gt;@opennextjs/cloudflare&lt;/a&gt; 进行适配。&lt;/p&gt;
&lt;h2 id=&quot;31-前置条件&quot;&gt;3.1 前置条件&lt;/h2&gt;
&lt;p&gt;你有一个 Cloudflare 账户，并且开通了 Cloudflare R2。R2 用于存储构建产物中的静态资源，Worker 在响应请求时会从 R2 中读取这些文件。&lt;/p&gt;
&lt;h2 id=&quot;32-使用命令行自动适配&quot;&gt;3.2 使用命令行自动适配&lt;/h2&gt;
&lt;p&gt;参考 &lt;a href=&quot;https://opennext.js.org/cloudflare/get-started&quot;&gt;opennext/get-started&lt;/a&gt;，在项目根目录下执行：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;zsh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;pnpm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; dlx&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; @opennextjs/cloudflare&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; migrate&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/14/3.webp&quot; alt=&quot;&quot;&gt;
&lt;img src=&quot;/images/posts/14/4.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;安装过程中，会提示进行 Cloudflare 的 OAuth 登录授权：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/14/5.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;安装过程中，也会自动创建 R2 的 bucket：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/14/6.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h1 id=&quot;4-预览和本地发布&quot;&gt;4. 预览和本地发布&lt;/h1&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;zsh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;pnpm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; preview&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; # 在本地构建并预览 Cloudflare 应用&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;pnpm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; deploy&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # 将应用部署到 Cloudflare Workers&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行 &lt;code&gt;pnpm run deploy&lt;/code&gt; 以后，即可发布到 Cloudflare，实现线上访问：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/14/7.webp&quot; alt=&quot;&quot;&gt;
&lt;img src=&quot;/images/posts/14/8.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;在 &lt;strong&gt;Workers &amp;#x26; Pages&lt;/strong&gt; 页面也能看到这个文档应用：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/14/9.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h1 id=&quot;5-配置自定义域名&quot;&gt;5. 配置自定义域名&lt;/h1&gt;
&lt;p&gt;在 &lt;strong&gt;Workers &amp;#x26; Pages&lt;/strong&gt; 页面控制台配置自定义域名：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/14/9-1.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;至此我们完成了 Fumadocs 通过 Cloudflare Worker 的部署，以及关联自定义域名访问！&lt;/p&gt;
&lt;h1 id=&quot;6-代码提交后自动构建--发布&quot;&gt;6. 代码提交后自动构建 &amp;#x26; 发布&lt;/h1&gt;
&lt;p&gt;本地直接发布不利于版本管理与变更追溯，改为 git 提交后自动触发构建和发布是更推荐的做法。&lt;/p&gt;
&lt;h2 id=&quot;61-检查-gitignore&quot;&gt;6.1 检查 .gitignore&lt;/h2&gt;
&lt;p&gt;添加 &lt;code&gt;.dev.vars&lt;/code&gt; 和 &lt;code&gt;.wrangler/&lt;/code&gt;，避免将本地凭证和构建缓存提交到仓库：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# OpenNext&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;.open-next&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;.dev.vars&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   # 本地环境变量，包含敏感凭证&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;.wrangler/&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # Wrangler 构建缓存&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;62-修改-packagejson-的-scripts&quot;&gt;6.2 修改 package.json 的 scripts&lt;/h2&gt;
&lt;p&gt;参考 &lt;a href=&quot;https://opennext.js.org/cloudflare/get-started#6-update-the-packagejson-file&quot;&gt;opennext/get-started&lt;/a&gt;，对构建和部署命令进行调整，确保 &lt;code&gt;build&lt;/code&gt; 脚本仍然指向标准的 &lt;code&gt;next build&lt;/code&gt;，由 Cloudflare CI 环境负责后续的打包与发布。&lt;/p&gt;
&lt;h3 id=&quot;before&quot;&gt;Before&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;build&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;next build&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;dev&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;next dev&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;start&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;next start&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;types:check&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;fumadocs-mdx &amp;#x26;&amp;#x26; next typegen &amp;#x26;&amp;#x26; tsc --noEmit&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;postinstall&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;fumadocs-mdx&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;lint&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;biome check&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;format&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;biome format --write&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;preview&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;opennextjs-cloudflare build &amp;#x26;&amp;#x26; opennextjs-cloudflare preview&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;deploy&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;opennextjs-cloudflare build &amp;#x26;&amp;#x26; opennextjs-cloudflare deploy&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;upload&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;opennextjs-cloudflare build &amp;#x26;&amp;#x26; opennextjs-cloudflare upload&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;cf-typegen&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;after&quot;&gt;After&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;dev&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;next dev&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;start&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;next start&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;build&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;next build&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;preview&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;opennextjs-cloudflare build &amp;#x26;&amp;#x26; opennextjs-cloudflare preview&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;deploy&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;opennextjs-cloudflare build &amp;#x26;&amp;#x26; opennextjs-cloudflare deploy&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;upload&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;opennextjs-cloudflare build &amp;#x26;&amp;#x26; opennextjs-cloudflare upload&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;cf-typegen&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;types:check&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;fumadocs-mdx &amp;#x26;&amp;#x26; next typegen &amp;#x26;&amp;#x26; tsc --noEmit&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;postinstall&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;fumadocs-mdx&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;lint&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;biome check&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;format&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;biome format --write&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改完成后，提交到 git 仓库，触发 Cloudflare 自动构建流程。&lt;/p&gt;
&lt;h2 id=&quot;63-在-cloudflare-面板关联-github-并配置构建命令&quot;&gt;6.3 在 Cloudflare 面板关联 GitHub 并配置构建命令&lt;/h2&gt;
&lt;p&gt;在 Cloudflare Workers &amp;#x26; Pages 面板下找到对应 Worker 的 Settings → Build 区块，配置构建和部署命令：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;zsh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Build&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;pnpm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; build&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Deploy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;pnpm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; deploy&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/14/11.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;64-检查-github-的-commit-是否关联了-cloudflare-部署&quot;&gt;6.4 检查 GitHub 的 commit 是否关联了 Cloudflare 部署&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/14/12.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;可以点击查看具体的构建与部署日志。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/14/13.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;至此，我们就完成了 git 提交自动部署到 cloudflare worker 的 Fumadocs 文档应用！&lt;/p&gt;
&lt;h1 id=&quot;7-费用说明&quot;&gt;7. 费用说明&lt;/h1&gt;
&lt;p&gt;Workers 免费额度为每天 10 万次请求，R2 免费额度为每月 10 GB 存储，且流量出口（Egress）永久免费。对于个人文档站来说，基本不会触发收费。详见 &lt;a href=&quot;https://developers.cloudflare.com/r2/pricing/&quot;&gt;Cloudflare 官方定价&lt;/a&gt;。&lt;/p&gt;</content:encoded></item><item><title>Next.js + Cloudflare R2 &amp; Workers Routes 代理静态资源，极速性能</title><link>https://daolanx.me/zh/posts/post-13/</link><guid isPermaLink="true">https://daolanx.me/zh/posts/post-13/</guid><pubDate>Wed, 18 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;1-效果概述&quot;&gt;1. 效果概述&lt;/h2&gt;
&lt;p&gt;在 &lt;a href=&quot;/zh/posts/post-12/&quot;&gt;Next.js + Cloudflare R2：更高性能，更低成本&lt;/a&gt; 中， 我们实现了将 Next.js 静态资源从 Vercel 分离并迁移至 Cloudflare R2，利用边缘节点提升了加载速度。&lt;/p&gt;
&lt;p&gt;经过进一步改造，目前&lt;strong&gt;静态资源与网页实现了同域名返回&lt;/strong&gt;，且图片支持&lt;strong&gt;响应式按需转换&lt;/strong&gt;。Lighthouse 跑分显示：PC 端各项指标均为 100，移动端性能得分 98，其余均为 100。性能表现非常优秀。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/13/2.webp&quot; alt=&quot;Lighthouse PC 端跑分&quot;&gt;
&lt;img src=&quot;/images/posts/13/1.webp&quot; alt=&quot;Lighthouse 移动端跑分&quot;&gt;&lt;/p&gt;
&lt;p&gt;接下来，我们将探讨上次改造中暴露出的问题及其解决方案。&lt;/p&gt;
&lt;h2 id=&quot;2-基于-r2-分离-nextjs-静态资源后的问题&quot;&gt;2. 基于 R2 分离 Nextjs 静态资源后的问题&lt;/h2&gt;
&lt;p&gt;虽然分离了资源，但也引入了两个影响性能和体验的新问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;增加了网络请求开销&lt;/strong&gt;：此前页面与资源均在 Vercel，使用相对路径请求。分离后，资源使用了独立的 &lt;code&gt;assets&lt;/code&gt; 前缀域名。虽然现代浏览器已不再受 HTTP/1.1 单域名并发连接数的限制，但跨域名请求仍需额外的 &lt;strong&gt;DNS 解析&lt;/strong&gt;、&lt;strong&gt;TCP 握手&lt;/strong&gt;及 &lt;strong&gt;TLS 协商&lt;/strong&gt;时间。在移动端网络下，这种延迟尤为明显。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;图片无法响应式加载&lt;/strong&gt;：理想情况下，图片应根据设备尺寸返回对应大小。使用自定义 Image Loader 后，Next.js 原生的图片优化功能失效，导致无论设备大小都返回原始尺寸图片，浪费了带宽并降低了 LCP 性能。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;3-cloudflare-核心能力的二次挖掘&quot;&gt;3. Cloudflare 核心能力的二次挖掘&lt;/h2&gt;
&lt;p&gt;若域名的 DNS 托管在 Cloudflare 且开启了代理（Proxying，即橙色小云朵），我们可以利用以下两项能力进行深度优化。&lt;/p&gt;
&lt;h3 id=&quot;31-images-transformations图像转换&quot;&gt;3.1 Images Transformations（图像转换）&lt;/h3&gt;
&lt;p&gt;参考 &lt;a href=&quot;https://developers.cloudflare.com/images/transform-images/make-responsive-images/#webp-images&quot;&gt;Make responsive images&lt;/a&gt;, 在 Cloudflare 的 &lt;strong&gt;Images &gt; Transformations&lt;/strong&gt; 菜单下，对应域名开启 &lt;strong&gt;Transformations&lt;/strong&gt;，可以实现图片的尺寸修改，例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;原始路径 (Before)：&lt;a href=&quot;https://my-r2-bucket-domain.com/avatar.webp&quot;&gt;https://my-r2-bucket-domain.com/avatar.webp&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/13/4.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;转换后路径 (After)：&lt;a href=&quot;https://my-r2-bucket-domain.com&quot;&gt;https://my-r2-bucket-domain.com&lt;/a&gt; &lt;strong&gt;/cdn-cgi/image/fit=contain,width=100,blur=30/&lt;/strong&gt; avatar.webp&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/13/5.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;32-workers-routes&quot;&gt;3.2 Workers Routes&lt;/h3&gt;
&lt;p&gt;参考 &lt;a href=&quot;https://developers.cloudflare.com/workers/configuration/routing/routes/?preferred-color-scheme=light&quot;&gt;Workers Routes&lt;/a&gt;, 当域名开启 DNS 代理（开启橙色小云朵） 时，Cloudflare Workers 可以实现对特定 URL 请求的拦截与逻辑干预。&lt;/p&gt;
&lt;p&gt;例如编写如下 Worker 脚本，并将其挂载到 Workers Routes 的匹配路径上，那么所有命中该路径的访问请求都将直接返回 “Hello World!”，而不再指向原有的源站资源。&lt;/p&gt;
&lt;p&gt;3.2.1 在 &lt;strong&gt;Workers &amp;#x26; Pages&lt;/strong&gt; 页面点击创建&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/13/6.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;3.2.2 选择 &lt;strong&gt;Hellow World&lt;/strong&gt; Worker,命名为 &lt;strong&gt;hello&lt;/strong&gt;，并部署&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/13/8.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;3.2.3 然后在 &lt;strong&gt;Workers Routes&lt;/strong&gt; 页面, 设置好域名通配符，选择刚才部署的 &lt;strong&gt;hello&lt;/strong&gt; worker&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/13/9.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;3.2.4 最后访问刚才配置的挂载 worker 的路由，可以发现路由返回了 worker 处理后的数据。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/13/10.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;有了以上机制，接下来要实现的目标就清晰了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用 &lt;strong&gt;Workers Routes&lt;/strong&gt; 拦截静态资源类请求&lt;/li&gt;
&lt;li&gt;Worker 内部从 R2 读取资源并输出&lt;/li&gt;
&lt;li&gt;针对图片请求，在 Worker 中调用 Image Resizing 实现按需裁剪&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;4-具体步骤&quot;&gt;4. 具体步骤&lt;/h2&gt;
&lt;p&gt;我们的目标是在 &lt;a href=&quot;https://your-site-domain.com/remote-assets/&quot;&gt;https://your-site-domain.com/remote-assets/&lt;/a&gt; 下实现静态资源返回和图片按需处理。&lt;/p&gt;
&lt;h3 id=&quot;41-新建静态资源代理-worker-挂载到-workers-routes&quot;&gt;4.1 新建静态资源代理 Worker， 挂载到 Workers Routes&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// workers/remote-assets/src/index.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; * Remote Assets Proxy Worker (Final Version)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; * Utilizes Cloudflare Built-in Image Resizing &amp;#x26; R2 Storage&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; default&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  async&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;env&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ctx&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; url&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; URL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(request.url)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;pathname&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;searchParams&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; url&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // 1. Match custom prefix for remote assets&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (pathname.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;startsWith&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(env.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;REMOTE_PREFIX&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      // Clean up path to ensure no double slashes during concatenation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; originPath &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; pathname.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;replace&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(env.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;REMOTE_PREFIX&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (originPath.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;startsWith&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)) originPath &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; originPath.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;slice&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; staticResUrl&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;env&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;R2_DOMAIN&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}/${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;originPath&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; isImage&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /&lt;/span&gt;&lt;span style=&quot;color:#85E89D;font-weight:bold&quot;&gt;\.&lt;/span&gt;&lt;span style=&quot;color:#DBEDFF&quot;&gt;(jpg&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#DBEDFF&quot;&gt;jpeg&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#DBEDFF&quot;&gt;png&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#DBEDFF&quot;&gt;webp&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#DBEDFF&quot;&gt;avif&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#DBEDFF&quot;&gt;gif)&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;i&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(originPath)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; width&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; searchParams.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;w&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; quality&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; searchParams.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;q&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;75&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      // 2. Trigger Image Resizing if &apos;w&apos; parameter is present&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (isImage &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; width) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(staticResUrl, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          headers: request.headers,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          cf: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            image: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;              width: &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;parseInt&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(width),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;              quality: &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;parseInt&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(quality),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;              format: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;auto&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Auto-select best format (WebP/AVIF) based on browser support&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;              fit: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;scale-down&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          cacheEverything: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          cacheTtl: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;31536000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      // 3. Standard asset request (JS, CSS, or non-resized images)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; response&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(staticResUrl, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        headers: request.headers,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      // Apply aggressive caching for hashed Next.js static assets&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (pathname.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;includes&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;/_next/static/&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; newHeaders&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Headers&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(response.headers)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        /**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;         * &apos;immutable&apos; prevents browsers from revalidating the file,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;         * reducing server round-trips to zero for repeat visits.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;         */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        newHeaders.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Cache-Control&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;public, max-age=31536000, immutable&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Response&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(response.body, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          status: response.status,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          statusText: response.statusText,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          headers: newHeaders,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; response&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // 4. Passthrough all other requests to the main origin (e.g., Vercel)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(request)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# workers/remote-assets/wrangler.toml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;name = &quot;remote-assets-proxy&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;main = &quot;src/index.js&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;compatibility_date = &quot;2024-12-01&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;vars&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;R2_DOMAIN = &quot;https://your-r2-assets-domain.com&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;REMOTE_PREFIX = &quot;/remote-assets/&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;routes&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;pattern = &quot;your-site-domain/remote-assets/*&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;zone_name = &quot;your-site-domain.com&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;完成上述文件以后，参考&lt;a href=&quot;https://developers.cloudflare.com/workers/wrangler/commands/&quot;&gt;workers/wrangler&lt;/a&gt;文档，在 &lt;strong&gt;remote-assets&lt;/strong&gt; 文件夹下，执行&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;pnpm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; dlx&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; wrangler&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; login&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;pnpm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; dlx&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; wrangler&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; deploy&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;即可部署本地 worker 到线上。&lt;/p&gt;
&lt;p&gt;然后在 workers Route 页面挂载。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/13/11.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;挂载完成后，访问当前路径，应该能返回正确的静态资源地址了，例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://your-r2-bucket-domain.com/profile/avatar.webp&quot;&gt;https://your-r2-bucket-domain.com/profile/avatar.webp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://your-site-domain.com/remote-assets/profile/avatar.webp&quot;&gt;https://your-site-domain.com/remote-assets/profile/avatar.webp&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;42-修改-nextjs-相关内容&quot;&gt;4.2 修改 Nextjs 相关内容&lt;/h3&gt;
&lt;h4 id=&quot;421-修改自定义-imgloader&quot;&gt;4.2.1 修改自定义 ImgLoader&lt;/h4&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// image-loader.ts&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;interface&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; LoaderProps&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  src&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  width&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; number&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  quality&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; number&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ALL_SIZES&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;64&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;256&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;640&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;828&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1120&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1920&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; default&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; myImageLoader&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({ &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;src&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;width&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;quality&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; LoaderProps&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; isProd&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; process.env.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;NODE_ENV&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ===&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;production&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; isExternal&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; src.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;startsWith&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // Local development or External images: Use original URL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;isProd &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; isExternal) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; src&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // Find the closest larger bucket size&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; targetWidth&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    ALL_SIZES&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;find&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;s&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; s &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; width) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ALL_SIZES&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;ALL_SIZES&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // Clean path: remove leading slash for consistency&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; normalizedSrc&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; src.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;startsWith&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; src.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;slice&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; src&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // Final Production URL: Relative path to trigger Cloudflare Worker&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `/remote-assets/${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;normalizedSrc&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}?w=${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;targetWidth&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&amp;#x26;q=${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;quality&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ||&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 75&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;432-最后修改-nextjs-的-config&quot;&gt;4.3.2 最后修改 Nextjs 的 Config&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// next.config.ts&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; type&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { NextConfig } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;next&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; createNextIntlPlugin &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;next-intl/plugin&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; isProd&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; process.env.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;NODE_ENV&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ===&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;production&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nextConfig&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; NextConfig&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  /**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   * IMPORTANT: Point this to your Worker path on the MAIN domain.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   * This ensures all JS/CSS are served from your-site-domain.com/remote-assets/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  assetPrefix: isProd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ?&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;https://your-site-domain.com/remote-assets&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    :&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; undefined&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  images: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    loader: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;custom&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    loaderFile: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;./lib/image-loader.ts&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    deviceSizes: [&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;640&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;828&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1120&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1920&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    imageSizes: [&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;64&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;256&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; default&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; nextConfig&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;5总结&quot;&gt;5.总结&lt;/h2&gt;
&lt;p&gt;通过上述改造，我们实现了以下机制，使得网站性能进一步提升：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;域名收敛&lt;/strong&gt;：静态资源与页面共享同个域名连接，消除了额外的 DNS/TLS 握手时间。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;图片按需加载&lt;/strong&gt;：利用 Cloudflare Image Resizing 补全了 Next.js 配置自定义图片加载以后丢失的响应式图片能力。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;极致缓存&lt;/strong&gt;：通过 Worker 手动注入 immutable 响应头，大幅提升了静态资源的二次加载速度。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Next.js + Cloudflare R2：更高性能，更低成本</title><link>https://daolanx.me/zh/posts/post-12/</link><guid isPermaLink="true">https://daolanx.me/zh/posts/post-12/</guid><pubDate>Tue, 03 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;先看效果，站点部署在 Vercel 上面，静态资源和图片都是存储在 R2 上，通过 R2 配置的 &lt;strong&gt;assets&lt;/strong&gt; 子域名访问。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/12/1.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;这样处理有几个显著好处:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;性能更好&lt;/strong&gt;: 虽然 Vercel 官方节点已足够快，但 Cloudflare 拥有全球最庞大的网络之一，CDN 节点密度更高，且提供了更细颗粒度的边缘配置能力。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;潜在费用极大节省&lt;/strong&gt;: 通过 Cloudflare R2 分发资源 &lt;strong&gt;没有出站流量费 (Egress Fee)&lt;/strong&gt;。而 Vercel 流量超出免费额度后费用昂贵，将静态资源剥离能大幅减少 Vercel 的带宽压力。&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;实现思路&quot;&gt;实现思路&lt;/h1&gt;
&lt;h2 id=&quot;之前流程&quot;&gt;之前流程&lt;/h2&gt;
&lt;p&gt;所有的构建（Build）和部署（Deploy）过程均在 Vercel 内部自动完成。&lt;/p&gt;
&lt;h2 id=&quot;改造后流程&quot;&gt;改造后流程&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;GitHub Workflow 进行构建：在 GitHub 环境下完成 Next.js 的生产打包。&lt;/li&gt;
&lt;li&gt;静态资源同步：将构建产物中的静态资源部分上传至 Cloudflare R2。&lt;/li&gt;
&lt;li&gt;部署产物外发：将构建产物发送给 Vercel。&lt;/li&gt;
&lt;li&gt;Vercel 预构建部署：Vercel 接收产物并直接上线，不再重复执行构建过程。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;具体操作&quot;&gt;具体操作&lt;/h1&gt;
&lt;h2 id=&quot;1-在-cloudflare-r2-上创建-bucket&quot;&gt;1. 在 Cloudflare R2 上创建 Bucket&lt;/h2&gt;
&lt;p&gt;在 &lt;a href=&quot;https://developers.cloudflare.com/r2&quot;&gt;Cloudflare R2&lt;/a&gt; 上创建 Bucket，这里以名字 &lt;strong&gt;assets&lt;/strong&gt; 为例。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/12/2.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;在 &lt;strong&gt;assets&lt;/strong&gt; Bucket 的 &lt;strong&gt;Settings&lt;/strong&gt; 配置 &lt;strong&gt;Custom Domains&lt;/strong&gt;.
&lt;img src=&quot;/images/posts/12/3.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;配置完成后，建议通过控制台手动上传一个文件并尝试用自定义域名访问，确保 CDN 已打通。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/12/4.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/12/5.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;在 R2 bucket 的 &lt;strong&gt;Settings -&gt; CORS Policy&lt;/strong&gt; 增加跨域配置&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;AllowedOrigins&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;AllowedMethods&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;GET&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;HEAD&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;AllowedHeaders&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;ExposeHeaders&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;MaxAgeSeconds&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;2-修改-nextjs-app-配置&quot;&gt;2. 修改 Next.js App 配置&lt;/h2&gt;
&lt;p&gt;修改 &lt;strong&gt;next.config.ts&lt;/strong&gt; 文件，配置 &lt;strong&gt;assetPrefix&lt;/strong&gt; 修改静态资源的前缀，并开启自定义图片加载器。&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// next.config.ts&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; type&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { NextConfig } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;next&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; isProd&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; process.env.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;NODE_ENV&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ===&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;production&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nextConfig&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; NextConfig&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // assetPrefix 修改静态资源前缀&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  assetPrefix: isProd &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;https://assets.your-domain.com&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; :&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; undefined&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // 图片采用自定义加载方案&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  images: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    loader: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;custom&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    loaderFile: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;./lib/image-loader.ts&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; default&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; nextConfig&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;新增 &lt;strong&gt;lib/image-loader.ts&lt;/strong&gt; 生产环境图片资源加上 CDN 前缀&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// lib/image-loader.ts&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; default&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; myImageLoader&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({ &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;src&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;src&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // // 如果是绝对路径或非生产环境，保持原样&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (src.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;startsWith&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; process.env.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;NODE_ENV&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; !==&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;production&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; src&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // 拼接 Cloudflare R2 的加速域名&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `https://assets.your-domain.com${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;src&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;3-关闭-vercel-的-代码提交自动化部署&quot;&gt;3. 关闭 Vercel 的 代码提交自动化部署&lt;/h2&gt;
&lt;p&gt;为了避免 Vercel 监听 Git 提交后重复构建，我们需要关闭其自动流水线。&lt;/p&gt;
&lt;p&gt;在 Vercel 项目的 &lt;strong&gt;Settings &gt; Build and Deployment&lt;/strong&gt; 下找到 &lt;strong&gt;Ignored Build Step&lt;/strong&gt;，选择 &lt;strong&gt;Don‘t build anthing&lt;/strong&gt;, 确保代码提交不再触发 Vercel 自带的构建。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/12/6.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;4-配置-github-workflow-环境变量&quot;&gt;4. 配置 GitHub Workflow 环境变量&lt;/h2&gt;
&lt;p&gt;在 GitHub 仓库的 &lt;strong&gt;Settings &gt; Secrets and variables &gt; Actions&lt;/strong&gt; 中配置必要的密钥。&lt;/p&gt;
&lt;h3 id=&quot;41-配置-r2-变量&quot;&gt;4.1 配置 R2 变量&lt;/h3&gt;
&lt;p&gt;在 Cloudflare R2 控制台新建 &lt;strong&gt;Account API Token&lt;/strong&gt;，权限务必选择 &lt;strong&gt;Admin Read &amp;#x26; Write.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/12/8.webp&quot; alt=&quot;&quot;&gt;
&lt;img src=&quot;/images/posts/12/9.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;将生成的 &lt;strong&gt;Access Key ID&lt;/strong&gt;, &lt;strong&gt;Secret Access Key&lt;/strong&gt; 以及 &lt;strong&gt;Endpoint&lt;/strong&gt; 分别配置到 GitHub 的 Secrets 中：&lt;strong&gt;R2_ACCESS_KEY_ID&lt;/strong&gt;, &lt;strong&gt;R2_SECRET_ACCESS_KEY&lt;/strong&gt;, &lt;strong&gt;R2_ENDPOINT&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/12/10.webp&quot; alt=&quot;&quot;&gt;
&lt;img src=&quot;/images/posts/12/11.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;42-配置-vercel-变量&quot;&gt;4.2 配置 Vercel 变量&lt;/h3&gt;
&lt;h4 id=&quot;获取-vercel_token&quot;&gt;获取 VERCEL_TOKEN&lt;/h4&gt;
&lt;p&gt;在 Vercel 的 &lt;strong&gt;Account Settings &gt; Tokens&lt;/strong&gt; 下新建一个 Token&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/12/12.webp&quot; alt=&quot;&quot;&gt;
&lt;img src=&quot;/images/posts/12/13.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;获取-vercel_project_id-和-vercel_org_id&quot;&gt;获取 VERCEL_PROJECT_ID 和 VERCEL_ORG_ID&lt;/h4&gt;
&lt;p&gt;VERCEL_PROJECT_ID: &lt;strong&gt;Settings &gt; General&lt;/strong&gt; 中直接查看。
&lt;img src=&quot;/images/posts/12/14.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;VERCEL_ORG_ID: 在项目面板中通过网络请求查看，或在本地运行 vercel link 后查看生成的 .vercel/project.json&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/12/15.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;至此，GitHub Actions 所有的变量已准备就绪。&lt;/p&gt;
&lt;h3 id=&quot;43-检查配置变量&quot;&gt;4.3 检查配置变量&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/12/16.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;5-编写-github-workflow&quot;&gt;5. 编写 GitHub Workflow&lt;/h2&gt;
&lt;p&gt;在仓库根目录新增 &lt;strong&gt;.github/workflows/deploy-prod.yml&lt;/strong&gt;（注意路径必须包含 workflows 目录）。&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;// .github/deploy-prod.yml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Deploy Production&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  push&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    branches&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;main&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;env&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  VERCEL_ORG_ID&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;${{ secrets.VERCEL_ORG_ID }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  VERCEL_PROJECT_ID&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;${{ secrets.VERCEL_PROJECT_ID }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;jobs&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  deploy&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Build, Sync and Deploy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    runs-on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ubuntu-latest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    steps&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Checkout code&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        uses&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;actions/checkout@v4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      # 1. 环境准备&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Setup pnpm&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        uses&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;pnpm/action-setup@v4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        with&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          version&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Setup Node.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        uses&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;actions/setup-node@v4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        with&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          node-version&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;20&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          cache&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;pnpm&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Install dependencies&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        run&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;pnpm install --frozen-lockfile&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      # 2. Vercel 构建阶段 (预编译产物)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Pull and Build Vercel Artifacts&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        run&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;          pnpx vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;          pnpx vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      # 3. 静态资源同步到 Cloudflare R2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Upload Static Assets to R2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        uses&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;jakejarvis/s3-sync-action@master&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        with&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          args&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;--follow-symlinks --delete&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        env&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          SOURCE_DIR&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;.vercel/output/static/&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          AWS_S3_BUCKET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;assets&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;${{ secrets.R2_ACCESS_KEY_ID }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;${{ secrets.R2_SECRET_ACCESS_KEY }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          AWS_REGION&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;auto&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          AWS_S3_ENDPOINT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;${{ secrets.R2_ENDPOINT }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      # 4. 最终部署 (使用预构建产物部署至 Vercel)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Deploy to Vercel&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        run&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;pnpx vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Post-deployment Notification&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;success()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        run&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;echo &quot;Successfully deployed to production and synced to R2.&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;推送代码到 GitHub 后，Workflow 会自动运行。你可以点击仓库顶部的 &lt;strong&gt;Actions&lt;/strong&gt; 实时查看进度。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/12/17.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;执行到 &lt;strong&gt;Deploy to Vercel&lt;/strong&gt; 步骤时，你会发现 Vercel 控制台使用了 &lt;strong&gt;prebuilt&lt;/strong&gt; 模式进行部署。这意味着 Vercel 仅负责最后的节点部署。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/12/18.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/12/19.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;6-在-cloudflare-后台配置-cache-rules&quot;&gt;6. 在 Cloudflare 后台配置 Cache Rules&lt;/h2&gt;
&lt;p&gt;针对 R2 绑定的自定义域名配置 Cache Rules。还可以根据业务需求手动设置 &lt;strong&gt;Edge TTL&lt;/strong&gt;（边缘缓存时间）和 &lt;strong&gt;Browser TTL&lt;/strong&gt;（浏览器缓存时间）。&lt;/p&gt;
&lt;p&gt;因为 Next.js 静态资源构建后是 hash 的，其他图片也不经常变更，通过显著延长 TTL，可以最大化 Cloudflare 的缓存命中率，让资源尽可能驻留在边缘节点，从而极大地减少回源 R2 的请求，进一步降低延迟并节省 API 调用成本，也提高访问速度。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/12/20.webp&quot; alt=&quot;&quot;&gt;
&lt;img src=&quot;/images/posts/12/21.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>SWR：人狠话不多的 React 数据请求 Hook</title><link>https://daolanx.me/zh/posts/post-11/</link><guid isPermaLink="true">https://daolanx.me/zh/posts/post-11/</guid><pubDate>Fri, 30 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://swr.vercel.app/&quot;&gt;SWR&lt;/a&gt; 是一个为 React 打造的现代化数据请求库。它拥有精简的 API，内置了缓存、重新验证（Revalidation）以及请求去重功能。通过一个简单的 React Hook，它就能让你的 UI 保持快速、一致且永远处于最新状态。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/11/1.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;一-同类功能代码量对比&quot;&gt;一、 同类功能代码量对比&lt;/h2&gt;
&lt;p&gt;在 React 中请求后端接口并保持全局状态一致性是刚需。我们以获取 &lt;strong&gt;/api/user&lt;/strong&gt; 为例，看看不同方案的实现成本。&lt;/p&gt;
&lt;h3 id=&quot;11-fetch--zustand&quot;&gt;1.1 Fetch + Zustand&lt;/h3&gt;
&lt;p&gt;需要手动管理 Loading、Error 和数据存储，逻辑较为琐碎。&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// useUserStore.ts&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { useEffect } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;react&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { create } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;zustand&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;expoort &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; useUserStore&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; create&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  user: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  isLoading: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  error: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  fetchUser&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;isLoading&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (user &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; isLoading) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({ isLoading: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, error: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    try&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; res&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;/api/user&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;res.ok) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;throw&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;获取用户信息失败&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; data&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; res.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;      set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({ user: data, isLoading: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (err) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;      set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({ error: err.message, isLoading: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// UserProfile.tsx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; UserProfile&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;isLoading&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;fetchUser&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; useUserStore&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  useEffect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    fetchUser&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }, [fetchUser]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (isLoading &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; !&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;user) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;加载中...&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (error) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;错误: {error}&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;{user?.name}&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;12-react-query&quot;&gt;1.2 React Query&lt;/h3&gt;
&lt;p&gt;功能强大，但配置成本较高。你需要定义 &lt;strong&gt;QueryClient&lt;/strong&gt;，包裹 &lt;strong&gt;Provider&lt;/strong&gt;，并手动声明 &lt;strong&gt;queryKey&lt;/strong&gt;。另外技术文档也比较琐碎。&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// APIProvider.tsx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { QueryClient, QueryClientProvider } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;@tanstack/react-query&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; queryClient&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; QueryClient&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  defaultOptions: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    queries: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      staleTime: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1000&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 60&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 5&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      refetchOnWindowFocus: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; APIProvider&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({ &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;children&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;QueryClientProvider&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; client&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{queryClient}&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      {children}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;QueryClientProvider&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// useUser.tsx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { useQuery } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;@tanstack/react-query&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; fetchUser&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; response&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;/api/user&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;response.ok) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;throw&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Network response was not ok&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; response.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; useUser&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; useQuery&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    queryKey: [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    queryFn: fetchUser,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// UserProfile.tsx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; UserProfile&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;isLoading&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; useUser&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (isLoading) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;正在加载用户信息...&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (error) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;加载失败: {error.message}&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;用户信息&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;用户名: {user?.name}&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;邮箱: {user?.email}&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;13-swr&quot;&gt;1.3 SWR&lt;/h3&gt;
&lt;p&gt;极致简洁。&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// useUser.ts&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; useSWR &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;swr&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; fetcher&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; response&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(url);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;response.ok) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;throw&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Network response was not ok&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; response.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; useUser&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;isLoading&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; useSWR&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;/api/user&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, fetcher);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    user: data,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    isLoading,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    error&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// UseProfile.tsx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; UserProfile&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;isLoading&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; useUser&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (isLoading) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;Loading...&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (error) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;Error: {error.message}&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;{user?.name}&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对比结论： 实现同样的功能，SWR 的心智负担和代码负担最小。虽然 React-Query 功能强大，但对于大部分常规需求，SWR 显得够用且简洁。&lt;/p&gt;
&lt;h2 id=&quot;二-设计哲学触达本质&quot;&gt;二、 设计哲学：触达本质&lt;/h2&gt;
&lt;h3 id=&quot;21-简洁优雅的品位&quot;&gt;2.1 简洁优雅的品位&lt;/h3&gt;
&lt;p&gt;SWR 的 API 设计恰到好处。这种“少即是多”的设计感，体现了开发者的功底和品位。&lt;/p&gt;
&lt;h3 id=&quot;22-url-作为-key-的独特设计&quot;&gt;2.2 URL 作为 Key 的独特设计&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;资源的唯一标识： 在 Zustand 中我们需要手动命名存储变量，在 React Query 中需要维护 queryKey 数组。而 SWR 默认以 URL 作为缓存标识。&lt;/li&gt;
&lt;li&gt;回归本质： URL 本身就是 &lt;strong&gt;Uniform Resource Locator&lt;/strong&gt;。它生来就是资源的唯一标识，做缓存 Key 再合适不过。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;三-变更操作灵活的缓存策略&quot;&gt;三、 变更操作：灵活的缓存策略&lt;/h2&gt;
&lt;p&gt;比起通常的请求库来说，SWR 默认开启了缓存策略。首先不要慌，这个不是心智负担，而是在常见场景下，可以更少代码实现更灵活的数据处理。&lt;/p&gt;
&lt;p&gt;从实际使用看，使用 &lt;strong&gt;useSWR&lt;/strong&gt; 实现请求查询一般没啥要关注的，需要关注的是使用 &lt;strong&gt;useSWRMutation&lt;/strong&gt; 的变更类操作。先了解这两个配置：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;populateCache&lt;/strong&gt;：是否用变更请求的响应结果直接更新本地缓存。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;revalidate&lt;/strong&gt;：操作完成后，是否重新发起一次 GET 请求来校验数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;31-重新验证模式-revalidate&quot;&gt;3.1 重新验证模式 (Revalidate)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;配置：revalidate: true | populateCache: false&lt;/li&gt;
&lt;li&gt;过程： PATCH 成功 -&gt; 发起 GET 请求 -&gt; 更新 UI&lt;/li&gt;
&lt;li&gt;适用：体验有最多的延迟，但最稳妥，适用于后端逻辑复杂的场景。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;trigger&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;isMutating&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; useSWRMutation&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;/api/user&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, updateUserFetcher, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  populateCache: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  revalidate: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;32-响应填充模式-populate&quot;&gt;3.2 响应填充模式 (Populate)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;配置：revalidate: false | populateCache: true&lt;/li&gt;
&lt;li&gt;过程： PATCH 成功并返回新数据 -&gt; 使用 PATCH 返回的数据更新 UI&lt;/li&gt;
&lt;li&gt;适用：仅需一次网络往返，兼顾速度和一致性的中庸之选，适合常规操作。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Profile&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;trigger&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; useSWRMutation&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;/api/user&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, updateUserFetcher, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    populateCache: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    revalidate: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; onClick&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; trigger&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({ name: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;新名字&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; })}&gt;快速更新&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;33-乐观更新-optimistic-updates&quot;&gt;3.3 乐观更新 (Optimistic Updates)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;配置：使用 optimisticData 配置 | populateCache: true | revalidate: false | rollbackOnError: true&lt;/li&gt;
&lt;li&gt;过程：点击瞬间更新 UI -&gt; 发起 PATCH -&gt; 成功后修正或失败后回滚。&lt;/li&gt;
&lt;li&gt;适用：更新速度最快，适用于点赞、评论等实时性要求极高的场景。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;trigger&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;isMutating&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; useSWRMutation&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;/api/user&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, updateUserFetcher, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  optimisticData&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;currentCache&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;currentCache,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;currentUser,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    status: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Saving...&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  populateCache: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  rollbackOnError: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  revalidate: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;34-静默提交-silent-submission&quot;&gt;3.4 静默提交 (Silent Submission)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;配置：revalidate: false | populateCache: false&lt;/li&gt;
&lt;li&gt;过程： 发起删除或更新请求 -&gt; UI 保持不变，不需要更新缓存和验证。&lt;/li&gt;
&lt;li&gt;适用：删除、埋点记录等不需要反馈在当前 UI 上的操作。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;trigger&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;isMutating&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; useSWRMutation&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;/api/user&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, deleteUserFetcher, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  populateCache: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  revalidate: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; handleDelete&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (window.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;confirm&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;确定要注销账号吗？&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    try&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      await&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; trigger&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;      alert&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;账号已在后台删除（静默模式）。&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (e) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;      alert&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;删除失败&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;四-结语&quot;&gt;四、 结语&lt;/h2&gt;
&lt;p&gt;SWR 是一个在简洁和强大取得平衡的 React 数据请求管理 hook，不仅体积小，简单实用，也能在统一资源定位的技术理解上有所帮助。&lt;/p&gt;
&lt;p&gt;就请求数据并缓存来说，那些需要繁琐配置 Key 的工具，仅仅把请求 URL 当作获取数据的方式，而 SWR 将其视为资源本身，体现出设计的品位和层次。类似 Unix 重那么多不同的设备终端，都抽象为“文件”来标识；Next.js 巧妙地用文件系统映射 URL。好的设计往往显得简单且更触达本质。&lt;/p&gt;</content:encoded></item><item><title>免费 AI IDE: VSCode + Roo + Mimo</title><link>https://daolanx.me/zh/posts/post-10/</link><guid isPermaLink="true">https://daolanx.me/zh/posts/post-10/</guid><pubDate>Fri, 19 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h3 id=&quot;一为什么不再用-github-copilot&quot;&gt;一、为什么不再用 Github Copilot？&lt;/h3&gt;
&lt;p&gt;前段时间我一直在 VSCode 上使用 &lt;a href=&quot;https://github.com/features/copilot&quot;&gt;Github Copilot&lt;/a&gt; 进行 AI 辅助编程。不愧是内置插件，它和 VScode 结合紧密交互友好，在处理轻量级局部问题时确实好用。但免费版每月有 token 限制，每月 10 美元还是有点贵。正好月度 token 用完了，得先看看有没有免费的方案，能省则省。
&lt;img src=&quot;/images/posts/10/1-1.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;最初的想法是找个免费的模型对接 Github Copilot 就可以了。但看了下 Github Copilot 不支持自由添加模型，只能在有限的模型供应商里选择，升级 Pro 后才能选择更强大的模型。这让我明白了，这类插件的盈利模式，应该是相当于一个模型的终端消费场景，有点像水电供应。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/10/1-2.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/10/1-3.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;既然商业化产品在提供良好服务的同时自定义程度较低，那就再看看开源方案有没有替代。&lt;/p&gt;
&lt;h3 id=&quot;二为什么选-roo-code-作为-vscode-的-ai-编程插件&quot;&gt;二、为什么选 Roo Code 作为 VSCode 的 AI 编程插件&lt;/h3&gt;
&lt;p&gt;好在 VSCode 插件生态支持开源方案。&lt;/p&gt;
&lt;p&gt;目前 VSCode 比较流行的 AI 插件有 Cline、Roo Code、Kilo Code 等。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/10/2-1.webp&quot; alt=&quot;&quot;&gt;
社区意见大致观点是：Cline 流行度更高，Kilocode 增长更快，Roo Code 更灵活。虽然几款工具都宣称具备很多配置能力，但从个人选择角度出发，这不是技术调研无需深入研究，从使用习惯出发趁手就好。&lt;/p&gt;
&lt;h4 id=&quot;1-cline&quot;&gt;1. Cline&lt;/h4&gt;
&lt;p&gt;最流行的 Cline，单看交互界面的 Plan 和 Act 模式，比起更贴合场景的 Github Copilot，我用起来不太习惯，先待定。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/10/2-2.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;2-kilo-code&quot;&gt;2. Kilo Code&lt;/h4&gt;
&lt;p&gt;Kilo Code 这个交互就不错，Code，Ask，Debug，Agent 非常符合使用场景，但是在接入自定义模型时踩了坑，自动补全无法运行。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/10/2-3.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Kilo 社区 &lt;a href=&quot;https://github.com/Kilo-Org/kilocode/issues/3458&quot;&gt;Issue#3548&lt;/a&gt; 也反馈这个功能有问题。代码补全是提效刚需啊，不能用就先放弃。&lt;/p&gt;
&lt;h3 id=&quot;3-roo-code&quot;&gt;3. Roo Code&lt;/h3&gt;
&lt;p&gt;最后看看 Roo Code，界面符合预期，初始化并没有社区说的复杂，自定义模型后代码补全正常，就选它了！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/10/2-4.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;把它移到右边栏，然后试试常见的文件右键操作，一切正常。
&lt;img src=&quot;/images/posts/10/2-5.webp&quot; alt=&quot;&quot;&gt;
&lt;img src=&quot;/images/posts/10/2-6.webp&quot; alt=&quot;&quot;&gt;
&lt;img src=&quot;/images/posts/10/2-7.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;三后续清理隐藏-github-copilot&quot;&gt;三、后续清理，隐藏 Github Copilot&lt;/h3&gt;
&lt;p&gt;安装好了 Roo Code 以后，可以隐藏 Github Copilot 以保持界面简洁。
&lt;img src=&quot;/images/posts/10/3-1.webp&quot; alt=&quot;&quot;&gt;
&lt;img src=&quot;/images/posts/10/3-2.webp&quot; alt=&quot;&quot;&gt;
&lt;img src=&quot;/images/posts/10/3-3.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;后续要开启也很简单，可以在 VSCode 的设置中找到 chat.disableAIFeatures 选项，去掉勾选即可。&lt;/p&gt;
&lt;h3 id=&quot;四为什么用-mimo&quot;&gt;四、为什么用 Mimo&lt;/h3&gt;
&lt;p&gt;其实免费模型也不好找，大多优秀的模型都是通过 Web 产品展示能力，调用 API 就要收费，商业模式真的像水电提供商。&lt;/p&gt;
&lt;p&gt;近期也正好赶上 12.17 小米人车家大会发布了其大模型基座 &lt;a href=&quot;https://mimo.xiaomi.com/blog/mimo-v2-flash&quot;&gt;MiMo-V2-Flash&lt;/a&gt;，其亮点是在能力不错的前提下，速度也很快，而且能到超低的价格（$0.1 per million input tokens and $0.3 per million output tokens），性能有竞争力价格厚道，这很小米^_^”。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/10/4-1.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/10/4-2.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;在能力差异不大的前提下，速度和价格是我比较看重的。通过 &lt;a href=&quot;https://aistudio.xiaomimimo.com/#/&quot;&gt;Web 版本&lt;/a&gt; 试用后，初看速度和能力还行，且目前十几天免费，先接入使用看看效果，后续如果收费，因为价格便宜，也可以作为一个廉价收费模型备选。&lt;/p&gt;
&lt;h3 id=&quot;五在-roo-code-配置-roo-code&quot;&gt;五、在 Roo Code 配置 Roo Code&lt;/h3&gt;
&lt;p&gt;接入 Cline、Kilo Code、Roo Code 也比较简单，按 &lt;a href=&quot;https://platform.xiaomimimo.com/#/docs/integration/cline-kilo-roo&quot;&gt;MiMo API 接入文档&lt;/a&gt; 操作即可，轻量级编程辅助应该够用了。
&lt;img src=&quot;/images/posts/10/5-1.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>Next.js 初学感受：适合前端的 Web 框架</title><link>https://daolanx.me/zh/posts/post-9/</link><guid isPermaLink="true">https://daolanx.me/zh/posts/post-9/</guid><pubDate>Wed, 17 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://nextjs.org/&quot;&gt;Next.js&lt;/a&gt; 是目前市面上非常流行的 Node.js Web 框架。跟着 &lt;a href=&quot;https://youtu.be/k7o9R6eaSes?si=Uz-0fPLD01Q5VDkc&quot;&gt;Codevolution 的教程&lt;/a&gt; 操作一遍后，觉得它的流行确实有道理。Next.js 是非常适合前端开发者的 Web 框架，优秀的技术方案不仅能大幅提升生产力事半功倍，其良好的架构和编程范式，还能帮助我们在建立更准确、简洁的场景认知。&lt;/p&gt;
&lt;h3 id=&quot;一从-web-应用程序和-url-说起&quot;&gt;一、从 Web 应用程序和 URL 说起&lt;/h3&gt;
&lt;p&gt;一般来说，Web 应用程序是指一种基于互联网浏览器运行的软件，它由一个或多个页面构成，通过 URL 实现页面资源的寻址访问，并通过超链接将页面关联起来，构成完整的产品逻辑。&lt;/p&gt;
&lt;p&gt;因此实现页面的 URL 定义和访问，是 Web 产品的基础。但实际上很多传统 Web 服务器的 URL 是基于代码或配置实现的，有时甚至因为分层架构需要而进行多层配置。而 Next.js 基于文件层级的路由设计，在实现同样功能时显得简单直观。这让我想到 Unix 的文件系统设计，好的设计往往简单自然，且经得起时间考验。&lt;/p&gt;
&lt;p&gt;接下来是资源寻址完成后的解析与消费。资源本质上是信息，信息可以从接口消费，也可以加上一定的模板作为页面呈现。Next.js 在这方面的设计也非常清晰：仅通过有限的文件夹和文件名组合，就能完成资源类型的解析和页面各种状态的声明。&lt;/p&gt;
&lt;p&gt;也就是说，Next.js 仅基于文件夹和文件名，就可以构建一个具备完整 URL 定义的静态页面站点。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/9/1.webp&quot; alt=&quot;Next.js URL 设计&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;二传统前端常见问题&quot;&gt;二、传统前端常见问题&lt;/h3&gt;
&lt;p&gt;接下来重点看看页面的展现和渲染。前端技术这方面的发展历程比较曲折，在增强各类能力的同时，也不断引入新的问题。&lt;/p&gt;
&lt;p&gt;跳过更早的 jQuery、CMD、MVC 时期，直接从 React 流行说起。&lt;/p&gt;
&lt;p&gt;随着 Web 应用越来越富交互化，React 这类 MVVM 方案开始流行，不仅在于它的声明式而非指令式的开发范式更适合富交互的组织表达，也在于它以组件形态具备更完整的模块化方案，继而具备复杂场景的拆解组装能力。&lt;/p&gt;
&lt;p&gt;但与此同时，React 的集成问题也比较多。市面上普遍的 Web 框架多以后端视角为主，对前端视图渲染支持有限，很少有框架能完整支持 React 的服务端渲染能力。这导致当时以页面 DOM 为入口、JS 完成整个渲染的 SPA 架构应用大量流行。这类应用虽然具备较好的富交互承载能力，却往往面临较差的性能和 SEO 表现。为了优化性能，开发者不得不进行局部的拆包和调优，或者通过微前端方案进行拆包，为了提高 SEO，也可能采用人蛛分离方案增加收录。而当时流行的 Webpack 打包方案，在支持各类前端打包能力的同时，也存在配置复杂和性能方面的问题。这类问题存在了很长时间，SPA 的性能和 SEO 似乎都遇到了瓶颈。&lt;/p&gt;
&lt;p&gt;不仅如此，页面渲染形态还存在服务端渲染（SSR）、静态渲染（SSG）、客户端渲染（CSR）等多种模式，使得前端开发和选型非常碎片化。一旦大方向定型，后期要变更整合能力也会非常困难。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/9/2.webp&quot; alt=&quot;传统前端常见问题&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;三nextjs-在编程范式和架构上的创新&quot;&gt;三、Next.js 在编程范式和架构上的创新&lt;/h3&gt;
&lt;p&gt;Next.js 的创新主要有两点：基于 React 的同构编程范式，以及融合渲染策略的一体化架构。&lt;/p&gt;
&lt;p&gt;React 的设计超越同时期的其他库，在于它不依赖浏览器 API，能支持包括服务端在内的多端渲染。React 在前端的流行度已经证明了其符合当前富应用趋势的前端认可度。Next.js 基于 React 的同构编程范式，首先能极大程度上降低前后端编程差异，从而降低生产成本。&lt;/p&gt;
&lt;p&gt;但其实，React 同构编程范式的好处不仅仅在于降低前后端差异和编程成本，它真正激动人心的威力在于由此带来的一体化能力。
&lt;img src=&quot;/images/posts/9/3.webp&quot; alt=&quot;Next.js 在编程范式和架构上的创新&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;四-nextjs-怎么解决传统前端问题&quot;&gt;四、 Next.js 怎么解决传统前端问题&lt;/h3&gt;
&lt;p&gt;如今看来，传统前端存在前端库众多、前后端差异大、前端渲染策略选型困难，以及富交互应用普遍的性能和 SEO 问题。而 Next.js 基于编程范式和架构的创新，从原生层面解决问题，让一切回归简单。&lt;/p&gt;
&lt;p&gt;首先，基于渲染策略的前端选型消失了，只用 Next.js 就能实现不错的页面级别的服务端渲染（SSR）、静态渲染（SSG）和客户端渲染（CSR）。这不仅降低了选型带来的技术差异，也让应用更具备后期变化和扩展能力。&lt;/p&gt;
&lt;p&gt;其次，SPA 的性能问题也消失了。基于 RSC 结合服务端的混合渲染非常出色，能够实现基于服务端渲染的整体页面快速呈现，并通过流的方式逐步输出局部内容。据我了解，这个效果非常好，比同一时期的微前端类似方案性能更好，与此同时几乎没有前者带来的技术复杂度。也就是说，Next.js 从原生角度解决了性能问题，编程范式也几乎不增加负担。&lt;/p&gt;
&lt;p&gt;至此会发现，前端角度 Next.js 几乎解决了传统前端的受限于架构很难突破的问题，它具备富交互应用的 React 良好范式，又从架构上原生解决了工程和性能的负面问题，且具备更好的扩展性和张力，形成一个非常全面的能力，前端技术和前后端技术也从分裂割据形态形成一个简单统一的整体。&lt;/p&gt;
&lt;h3 id=&quot;五nextjs-区别于传统前端方案带来的新能力&quot;&gt;五、Next.js 区别于传统前端方案带来的新能力&lt;/h3&gt;
&lt;h4 id=&quot;1与后端方案的深度集成能力&quot;&gt;1、与后端方案的深度集成能力&lt;/h4&gt;
&lt;p&gt;以 Clerk 登录为例，通过简单配置 SDK，不仅可无缝集成接口和中间件的登录和权限管理功能，还能集成页面组件级别的相关按钮和表单，大幅降低了以往二次开发或自研的成本。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/9/4.webp&quot; alt=&quot;Next.js 与后端方案的深度集成能力&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;2-其他新特性&quot;&gt;2. 其他新特性&lt;/h4&gt;
&lt;p&gt;不仅如此，Next.js 还有一些有创意的编程范式。比如通过 useActionState 实现的前后端数据交互，前后端可以不再用繁琐的基于 URL API 的实现和解析。虽然在小的场景上能减少很多样板代码，在更细粒度和更大规模的场景上还有待实际验证，但确实让人耳目一新。&lt;/p&gt;
&lt;h3 id=&quot;六部署体验-vercel-生态的工程化保障&quot;&gt;六、部署体验 Vercel 生态的工程化保障&lt;/h3&gt;
&lt;p&gt;Next.js 由 Vercel 团队开发，且 Vercel 赞助了类似 React 等核心开源项目，使得技术合作紧密。构建也采用了 Vite 等性能更好的方案，且开箱即用，而部署托管方面，使用 Vercel 能一键部署仓库，并提供运行时的 CDN 加速等其他能力，使得简单产品从开发到发布一气呵成。&lt;/p&gt;
&lt;h3 id=&quot;总结学习-nextjs-的收获&quot;&gt;总结，学习 Next.js 的收获&lt;/h3&gt;
&lt;p&gt;因此 Next.js 是特别适合前端学习使用的 Web 框架，学习会带来很多收获：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;认知角度&lt;/strong&gt;，基于文件的资源定位，以及基于范式和架构创新的渲染能力，带来了更清晰、简洁的场景概念认知，降低了传统研发带来的开发成本。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;技术角度&lt;/strong&gt;，得益于其同构编程范式和一体化架构带来的创新，Next.js 原生解决了传统前端的许多问题，也优化了部署方式。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;能力角度&lt;/strong&gt;，单一前端职能的能力往往比较局限，而 Next.js 能提供构建 Web 产品所需的较为完整的能力。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;市场角度&lt;/strong&gt;，Next.js 在市场上认可度较高，能带来更多的工作机会。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/9/5.webp&quot; alt=&quot;Next.js 学习 Nextjs 的收获&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>博客搭建：Astro 框架 + Vercel 部署 + Cloudflare DNS 代理</title><link>https://daolanx.me/zh/posts/post-8/</link><guid isPermaLink="true">https://daolanx.me/zh/posts/post-8/</guid><pubDate>Sun, 30 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h3 id=&quot;一为什么用-astro-框架&quot;&gt;一、为什么用 Astro 框架？&lt;/h3&gt;
&lt;p&gt;最初是想用 Next.js 搭建博客。因为 Next.js 比较适合前端转全栈，学习曲线相对平滑，应用场景也广泛，使用 Next.js 搭建博客，既能完成项目，又能深入学习框架，可谓一举两得。&lt;/p&gt;
&lt;p&gt;但对于博客这类内容驱动型网站，Astro 更为合适。首先性能不错，得益于静态页面渲染为主，尽量降低 js 的开销和依赖，从而大幅提升页面性能，官方号称 “使用 Astro 构建缓慢的网站几乎是不可能的”。其次开发体验友好功能够用，岛屿架构允许在静态页面基础上灵活集成 React、Vue 等流行框架。也支持多语言和 Markdown 内容管理。&lt;/p&gt;
&lt;p&gt;因此从对外产品体验更好，对内更简单可靠的开发维护的角度，选择用 Astro 搭建个人博客。&lt;/p&gt;
&lt;h3 id=&quot;二为什么用-vercel-部署&quot;&gt;二、为什么用 Vercel 部署？&lt;/h3&gt;
&lt;p&gt;最早接触 Vercel 是因为 Next.js 由 Vercel 团队开发。Next.js 应用在 Vercel 上部署能得到 “原生支持”，因此 Vercel 非常适合作为初期全栈学习 Next.js 应用的部署平台，需要多了解。&lt;/p&gt;
&lt;p&gt;而使用 Vercel 部署 Astro 项目体验也非常不错，GitHub 仓库几乎一键部署。同类功能平台还有 Netlify 和 Cloudflare Pages，Netlify 部署也挺流畅相对 Vercel 没有明显优势，Cloudflare Pages 推荐的人也不少，但它对 Javascript 是用 bun 构建，pnpm 这类脚本项目直接构建会报错，需要额外配置。在没有明显优势前提下，不想增加额外的配置和步骤，还是简单更好，简单意味着省心省力，也意味着更可靠。&lt;/p&gt;
&lt;p&gt;此外，Vercel 的发展势头良好，团队实力强，支持开源生态，坚持免费增值模式，且在 AI 时代也在不断完善平台能力。&lt;/p&gt;
&lt;p&gt;从免费，产品体验好，符合学习路径，前景良好的角度，我还是选择 Vercel 作为部署平台。&lt;/p&gt;
&lt;h3 id=&quot;三为什么用-cloudflare-dns-代理&quot;&gt;三、为什么用 Cloudflare DNS 代理？&lt;/h3&gt;
&lt;p&gt;Vercel 默认为应用提供 CDN 加速、HTTPS、速率限制、防火墙和自定义安全标头等能力。但从实际测试来看，国内访问速度仍不理想，原因包括 DNS 污染、全球节点主要位于国外，导致中国地区访问延迟较大。&lt;/p&gt;
&lt;p&gt;Cloudflare 是全球最大的 CDN 服务商之一。使用 Cloudflare 的 DNS 代理，可以充分利用其全球 CDN 加速网站访问。虽然 Vercel 不推荐第三方 DNS（认为会增加链路复杂性），但从实际测试看，Cloudflare DNS 代理确实能明显改善国内访问速度。&lt;/p&gt;
&lt;h3 id=&quot;四部署步骤&quot;&gt;四、部署步骤&lt;/h3&gt;
&lt;p&gt;得益于 Vercel 的良好的开发部署体验，整个配置过程比较简单。&lt;/p&gt;
&lt;h4 id=&quot;1-在-astro-选择模板在-github-生成项目仓库&quot;&gt;1. 在 Astro 选择模板，在 Github 生成项目仓库&lt;/h4&gt;
&lt;p&gt;在 &lt;a href=&quot;https://astro.build/themes/&quot;&gt;Astro/theme&lt;/a&gt; 选择模板，添加到个人 GitHub 仓库。我选的是 &lt;a href=&quot;https://github.com/sun0225SUN/astro-air&quot;&gt;astro-air&lt;/a&gt;。它风格简约，支持基于 MDX 的 Markdown 内容管理，并支持双语内容切换。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/8/4-1.webp&quot; alt=&quot;使用 Astro 模板&quot;&gt;
&lt;img src=&quot;/images/posts/8/4-2.webp&quot; alt=&quot;使用 Astro 模板 — astro-air&quot;&gt;&lt;/p&gt;
&lt;p&gt;基于模板，在 GitHub 上生成自己的博客项目仓库。
&lt;img src=&quot;/images/posts/8/4-3.webp&quot; alt=&quot;在 Github 使用 Astro 模板&quot;&gt;
&lt;img src=&quot;/images/posts/8/4-4.webp&quot; alt=&quot;创建新的 Github 仓库&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;2-在-vercel-部署项目&quot;&gt;2. 在 Vercel 部署项目&lt;/h4&gt;
&lt;p&gt;如果 Vercel 关联过 GitHub，在 &lt;a href=&quot;https://vercel.com/new&quot;&gt;vercel.com/new&lt;/a&gt; 可以看到新建的项目仓库。直接点击导入和部署。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/8/4-5.webp&quot; alt=&quot;Vercel 项目关联 Github 仓库&quot;&gt;
&lt;img src=&quot;/images/posts/8/4-6.webp&quot; alt=&quot;Vercel 项目部署&quot;&gt;&lt;/p&gt;
&lt;p&gt;部署好了可通过 vercel 生成域名访问&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/8/4-8.webp&quot; alt=&quot;Vercel 项目面板&quot;&gt;
&lt;img src=&quot;/images/posts/8/4-9.webp&quot; alt=&quot;使用 Vercel 域名访问网站&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;3-把域名的-dns-服务商设置为-cloudflare&quot;&gt;3. 把域名的 DNS 服务商设置为 Cloudflare&lt;/h4&gt;
&lt;p&gt;假设 xyz.com 是自定义域名为例。参考类似 &lt;a href=&quot;https://www.nerdpress.net/how-to-transfer-your-dns-management-to-cloudflare/&quot;&gt;DNS 迁移文档&lt;/a&gt;，把域名的 DNS 服务商设置为 Cloudflare。
&lt;img src=&quot;/images/posts/8/4-10-0.webp&quot; alt=&quot;在 Cloudflare DNS1&quot;&gt;
&lt;img src=&quot;/images/posts/8/4-10.webp&quot; alt=&quot;在 Cloudflare DNS2&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;4-在-vercel-关联自定义域名&quot;&gt;4. 在 Vercel 关联自定义域名&lt;/h4&gt;
&lt;p&gt;然后在 Vercel 项目控制面板，为部署项目添加自定义域名
&lt;img src=&quot;/images/posts/8/4-12.webp&quot; alt=&quot;Vercel 项目关联域名&quot;&gt;&lt;/p&gt;
&lt;p&gt;在域名 DNS 提供商添加对应的 A 记录，CNAME 记录，可以点击自动配置按钮，识别正确的话，会授权跳转到对应 DNS 服务提供商完成配置。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/8/4-13.webp&quot; alt=&quot;根据 Vercel 信息配置 DNS1&quot;&gt;
&lt;img src=&quot;/images/posts/8/4-14.webp&quot; alt=&quot;根据 Vercel 信息配置 DNS2&quot;&gt;&lt;/p&gt;
&lt;p&gt;至此，等 DNS 解析生效，就可以通过自定义域名访问部署的网站了。&lt;/p&gt;
&lt;h3 id=&quot;5-使用-cloudflare-dns-代理&quot;&gt;5. 使用 Cloudflare DNS 代理&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;DNS/Records&lt;/code&gt; 面板，打开代理开关，加速 Web 访问速度。
&lt;img src=&quot;/images/posts/8/4-15.webp&quot; alt=&quot;在 Cloudflare 打开代理&quot;&gt;&lt;/p&gt;
&lt;p&gt;最后在 &lt;code&gt;Speed/Observatory&lt;/code&gt; 面板 验证加速效果。
&lt;img src=&quot;/images/posts/8/4-16.webp&quot; alt=&quot;在 Cloudflare 查看加速效果&quot;&gt;&lt;/p&gt;
&lt;p&gt;备注：如果国内用户访问速度慢，可以在 Cloudflare 更换 DNS 配置为 Vercel 对相关场景的优化配置
A 记录地址：76.223.126.88，CNAME 记录地址：cname-china.vercel-dns.com。&lt;/p&gt;</content:encoded></item><item><title>为什么要继续从事前端工作</title><link>https://daolanx.me/zh/posts/post-7/</link><guid isPermaLink="true">https://daolanx.me/zh/posts/post-7/</guid><pubDate>Wed, 05 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;一再次思考问题的契机&quot;&gt;一、再次思考问题的契机&lt;/h2&gt;
&lt;p&gt;在公司从事前端多年，直到离职后，我才有了再次审视这个问题的契机：“以后做什么更合适？”很多问题并非立刻就有答案，它需要时间，也需要经历去沉淀。&lt;/p&gt;
&lt;p&gt;离职初期，明显感到生活惯性带来的不适，随之而来的是对收入中断和闲暇时间的焦虑。于是我开始惯性地准备简历、刷算法、背八股文。&lt;/p&gt;
&lt;p&gt;但最终，我没有继续走求职面试这条路。一方面源自面试和刷题过程中内心矛盾的煎熬，通过面试得到工作确实能获得较为稳定的收入，但这种将自己几乎所有时间作为商品贩卖，技术作为质检的方式被筛选显得物化严重。但最主要还是家庭原因需要承担带娃的责任，而通常的互联网工作早出晚归是常态，几乎无可能兼顾工作和照顾孩子。&lt;/p&gt;
&lt;h3 id=&quot;二为什么依然从事前端&quot;&gt;二、为什么依然从事前端&lt;/h3&gt;
&lt;p&gt;依然以自由职业者的身份从事前端，主要基于两点。&lt;/p&gt;
&lt;p&gt;一个是最直接功利的原因：在社会分工大背景下，从事自己相对有经验的工作更合适。前端作为职业，它和很多服务社会的职业一样提供价值，它不低下，也不值得炫耀，它有价值就会存在，它有市场就会有产业规模。而且每个行业也有分层，不一定要成为顶尖，也可以从事相关工作，找到适合自己的位置。&lt;/p&gt;
&lt;p&gt;另一个是对程序员和技术工作的认知的溯源。看了一些书，影响最深的一本是《黑客：计算机革命的英雄》。我们生来就在一个环境里，早已习惯从周边获取信息，对程序员的刻板印象、对互联网工作的高强度加班、“35岁青春饭”早已形成共识。可实际上并非如此，借着书籍跨越时空，去看看程序的起源，互联网和计算机的发展，再看看推动这一切背后的人，他们的文化认同、追求、价值观和个性，你会发现这和周边大部分人对于程序员和互联网的刻板认知相差极大。&lt;/p&gt;
&lt;p&gt;想起《楚门的世界》这部电影，楚门划着船，刺破了天空，那其实是摄影棚的幕布，需要扒开幕布自己去找更真实的信息来探寻真相。可以说大部分程序员从教育到工作都是被作为产业工人的产物。但那并不是正确的真相，真正的计算机历史充满了多元化的人和英雄主义，也包含他们的个性与追求。前端也不再社会分工的固化职业，它其实是一类技术产品，也是一个技术分支，对个人来发展说，成为前端满足社会需要是起点而不是终点。只有对行业和从业者有更真切的认知和认同感，才会在行业和领域走得更稳更远。&lt;/p&gt;
&lt;h3 id=&quot;三重新开始的挑战&quot;&gt;三、重新开始的挑战&lt;/h3&gt;
&lt;p&gt;面临的第一个挑战就是：虽然都是前端开发，但社区和公司技术完全不同，脱离公司技术栈，几乎要全部重新学。经过几周的探索，发现如今社区技术其实更加灵活成熟，成为超级个体的可能性更大了。以前开发部署一个简单东西，要找各种环节各种人，如今可能很快就能搞定了。&lt;/p&gt;
&lt;p&gt;面临的第二个挑战就是赶上 AI 对行业的冲击影响，如今 AI 生成满地都是。我觉得首先更要打好基础，不懂原理被架空的话才容易被取代；懂原理再用 AI，那会成为提效手段，不能本末倒置。还有就是与其担心，不如打不过就加入，多用于加强自己的能力。&lt;/p&gt;
&lt;h3 id=&quot;四持续思考的终极问题&quot;&gt;四、持续思考的终极问题&lt;/h3&gt;
&lt;p&gt;德尔菲神庙上刻着一句箴言：“认识你自己。”&lt;/p&gt;
&lt;p&gt;大千世界，每个人的活法天差地别，但作为个体，只能要么不选随波逐流一辈子，要么弱水三千只取一瓢。取舍之后才是属于自己的人生。&lt;/p&gt;
&lt;p&gt;人生苦短，怎么度过才不算虚度，才觉得有意义呢，持续做有价值有兴趣的事情，可能就足够了。&lt;/p&gt;</content:encoded></item><item><title>GAP 一年的意义</title><link>https://daolanx.me/zh/posts/post-6/</link><guid isPermaLink="true">https://daolanx.me/zh/posts/post-6/</guid><pubDate>Wed, 22 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;离开职场差不多一年多，发现要做出改变，还真需要长休一段时间，其中自有意义。&lt;/p&gt;
&lt;h3 id=&quot;一放松身心&quot;&gt;一、放松身心&lt;/h3&gt;
&lt;p&gt;重要的不重要的，都放下来，慢下来，归于平静，才能客观认知生活。&lt;/p&gt;
&lt;h3 id=&quot;二忘记过去&quot;&gt;二、忘记过去&lt;/h3&gt;
&lt;p&gt;以极其缓慢的时间冲刷，忘记过去工作生活的行为思维，这些变化需要在深度纯净的思维才得以发生。
正如金庸小说里，要学太极先要忘记，忘记才能学到，空杯才能盛水。&lt;/p&gt;
&lt;p&gt;忘记之前的作息，忘记情绪，忘记标签，忘记能力，忘记一切。&lt;/p&gt;
&lt;p&gt;但是忘记不是失去，那些经验和能力会因为失去之前的引力而游离，而后藉由由新的认知以新的形态重新聚合。&lt;/p&gt;
&lt;h3 id=&quot;三培养元认知&quot;&gt;三、培养元认知&lt;/h3&gt;
&lt;p&gt;放空一切，再重新开始，首先要修正的是事物的基本概念和认知。&lt;/p&gt;
&lt;p&gt;借着一些好书的参照思考，对工作生活建立的认知产生质疑，识别那些不正确认知。比如工作能赚钱，做事要勤奋，学习就是考试记住答案等等，其实都错的离谱，类似工作其实不能赚大钱，反而杠杆比比较低，真正的财富自由是时间和选择自由；做事更重要是做正确的事，而不是勤奋；学习主要是培养思考和分析能力，而不是记住标准答案。&lt;/p&gt;
&lt;p&gt;所以随大流不经思考的常识，可能都是错误的，《楚门的世界》是虚构的电影，但却真实存在我们日常生活。如何在不可信的世界，构建自己的可信依赖才是值得探讨的。&lt;/p&gt;
&lt;p&gt;只有不断完善自己的元认知能力，才能找到相对可靠的支点，以此之上搭建起来更为可靠更广阔的能力。&lt;/p&gt;
&lt;p&gt;举个例子，我始终觉得技术专业教育和工作有排斥枯燥感，现在随着认知更多我能解释一二，是因为他们教授不全。他们只教授了专业知识，没有教授技术文化，价值度量，和更为重要的学习分析能力。&lt;/p&gt;
&lt;p&gt;他们为什么这么设计教学和工作？可能原因是单纯的教学水平有限，更大可能是灌输了选拔，产业需求，服从等各种设计变得不纯粹，说白了不是为了你个人发展的，而是从属于一些更宏大命题的，他们需要量产产业工人。&lt;/p&gt;
&lt;p&gt;《黑客：计算机革命的英雄》受益匪浅，不仅记录了计算机从科研到家用的历史，也生动记录了计算机发展背后那群人，他们能力的形成，他们的价值观，亚文化，以及随着发展的稀释和回归。也看了linux 林纳斯的自传，你会发现，技术不是按部就班的教学，标准答案的考试，也不是冷冰冰的文档，更不是熬夜加班。技术是有血有肉的，技术背后是人在推动，他们对世界充满好奇，也有勇气，他们热爱生活，热爱技术，关注有趣，有用，也有品位，技术项目并不是一堆代码和运行，背后无论软件硬件，都包含了众多的智慧，热爱，和个性；你也能从他们经历中，看到如今很多技术形态和文化的源头，比如如今流行的 github，可能算上渊源应该来自于 MIT 黑客的文化和精神，那数卷自由分享的纸带，而如今论坛和网络购物，可能源自几十年前的 Community Memory 的 李·费尔森斯坦，时代在进步，技术在发展，但所有东西不是只有代码，也有时间的雕琢和人的温度。藉着书籍和影像，跟随他们，了解技术背后的文化和渊源，才能承前启后，真正的去理解和从事技术本身。&lt;/p&gt;
&lt;p&gt;所以个人发展，应该看清这些框架的问题，在被篡改的世界里发现 bug，hack 一切。&lt;/p&gt;
&lt;h3 id=&quot;四重建个人的操作系统&quot;&gt;四、重建个人的操作系统&lt;/h3&gt;
&lt;p&gt;基于更准确的事物元认知和关联，重建个人认知，重建个人是非观，价值观，能力&lt;/p&gt;
&lt;p&gt;再基于全新的个人能力，重建社交，重建生活，重建技术，重建投资。&lt;/p&gt;
&lt;p&gt;脱胎换骨，才能提升。&lt;/p&gt;
&lt;p&gt;我发现在技术领域和投资领域的牛人共性，是他们都有很强的元认知能力，他们当然不是关注勤奋刻苦，也不是贪图财产，他们更关注理解和 hack 整个系统，希望通过自己认知来获得一定收益和成就，也就是说，他们通过自己元认知能力去深入理解系统，即热而不断迭代和验证自己的认知，从而正向循环，更加认识世界。&lt;/p&gt;
&lt;h3 id=&quot;四做正确的事&quot;&gt;四、做正确的事&lt;/h3&gt;
&lt;p&gt;比起正确的做事，做正确的事相当重要，这个概念我在李笑来的书，左耳朵耗子的书，以及乔布斯，沃滋，类似的行为里都看到了。无论投资还是技术，都挺适用。&lt;/p&gt;
&lt;p&gt;做正确的事，哪怕慢一点都没关系。可能现在看上去不是路的，才是真的路。&lt;/p&gt;
&lt;h3 id=&quot;五参考书籍&quot;&gt;五、参考书籍&lt;/h3&gt;
&lt;h4 id=&quot;认知觉醒&quot;&gt;认知觉醒&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;《财富自由之路》李笑来&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;投资认知&quot;&gt;投资认知&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;《富爸爸穷爸爸》罗伯特·清崎&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;技术认知&quot;&gt;技术认知&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;《黑客：计算机革命的英雄》史蒂文·列维&lt;/li&gt;
&lt;li&gt;《我是沃兹：从电脑极客到文化偶像》史蒂夫·沃兹尼亚克&lt;/li&gt;
&lt;li&gt;《只是为了好玩 : Linux之父林纳斯自传》林纳斯·托瓦兹&lt;/li&gt;
&lt;li&gt;《左耳听风 : 传奇程序员练级攻略》陈皓&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>那些带娃教会我的事情</title><link>https://daolanx.me/zh/posts/post-5/</link><guid isPermaLink="true">https://daolanx.me/zh/posts/post-5/</guid><pubDate>Mon, 18 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;又过了大半年，经历了些家庭的波折，我现在成了名副其实的全职奶爸，负责饮食、日常，还得想办法赚钱。近期最重要的事情就是，等着一个月后娃上幼儿园。&lt;/p&gt;
&lt;p&gt;本来以为带娃是件辛苦又枯燥的事情，没想到，我真的学到了太多太多。&lt;/p&gt;
&lt;p&gt;可能是旁观者清吧，以前我几乎所有事情都以工作为重，其他都被我潜意识地当作干扰或附属品。后来读到一些书，才发现这有专门的描述和辛辣的批判：他们说这叫工作狂，牺牲自己的时间和生活，沦为工作的机器。更讽刺的是，这种状态其实并不是因为热爱工作，而是出于一种逃避和恐惧。&lt;/p&gt;
&lt;p&gt;这半年来脱离了工作，我却能思考更多。这篇文章也断断续续写了好久，思绪万千，笔力有限，但还是想勉强写出来，作为这段时间的收获记录。&lt;/p&gt;
&lt;h3 id=&quot;一最基本的事情重筑家庭情感基础心安之处才是家&quot;&gt;一、最基本的事情，重筑家庭情感基础，心安之处才是家&lt;/h3&gt;
&lt;p&gt;家的基础是精神依托和情感链接。要构成一个家，最重要的就是精神关系，至少要相互信任、相互帮助，更好一点的，是相互欣赏、相互成就。这些精神要素，会让你在任何困难疲惫的时候都有寄托、有归宿，有一个踏实安心的地方，和一群亲切温暖的人。这就是家的基本特征。&lt;/p&gt;
&lt;p&gt;反之，如果家里人相互对抗、攻击、诋毁，内耗不断，那就没有了家的精神结构基础。哪怕有再多的物质支撑，也只是个外强中干的空壳，很容易坍塌。可叹的是，道理只是道理，只有经历过的才真正懂。&lt;/p&gt;
&lt;p&gt;带娃也让我看到了孩子对父母那种纯粹、无条件的信任和依赖，他们完全没有猜疑和顾虑，所有情绪和行动都毫无保留地反馈给你。前段时间遇到地区水污染事件，我开着车带着娃躲避水污染，那一刻我忽然觉得，一娃一车，尿布奶瓶，这就是家，足以安心浪迹天涯，其他都无关紧要。&lt;/p&gt;
&lt;h3 id=&quot;二解决生存问题重筑家庭经济支撑使用资本杠杆&quot;&gt;二、解决生存问题，重筑家庭经济支撑，使用资本杠杆&lt;/h3&gt;
&lt;p&gt;首先是赚钱的问题，没了工资怎么吃穿？&lt;/p&gt;
&lt;p&gt;前几个月我真的特别慌，感觉没了工作就没了收入，各种找工作。我经历了很多人间百态：有些工作强度太高，为了赚钱想去，但心里又是抵触的，还好没成；有些工作看上去好进，实际上可能很不稳；还有一些看上去高薪，但可能行业灰色。几个月下来，我感觉很多工作就是围城，大部分人挤破头进去，仍然是牺牲自我空间和时间来换取工资的模式。&lt;/p&gt;
&lt;p&gt;正好经历家庭变故，需要全职带娃，根本不具备所谓的上下班工作时间，我索性打算先躺平，等小孩上幼儿园有时间再说。没事就看书，正好那套**《富爸爸穷爸爸》**开启了我的财商。&lt;/p&gt;
&lt;p&gt;随着对ESBI象限和杠杆的学习，我明白了工作模式的局限性。尽管这是很多人惯性的思维，但它本身是低杠杆的，就像稳健理财。稳定的背后，其实牺牲了利润，也隐藏着个人发展的局限风险。甚至，它其实并不稳健——一旦不工作，收入就没了。职业设计也存在矛盾，明明是职业分工，只给了你限定的价值和信息量，却又鼓励你去做全局的思考和行动，导致不伦不类。&lt;/p&gt;
&lt;p&gt;后来，我开始学习投资，涉猎股市。很幸运去了香港办卡成功，开了券商户，开始我的投资生涯。恰逢去年底牛市末期，我买了股票也跌了，又缺少风险意识，去玩融资、期权和做空，结果直接亏损了几万块。想到这都是我工作攒的本金，平时都不舍得用，一下子就少了这么多，那段时间我彻夜难眠，感觉到了至暗时刻。后来我终究还是想通了，觉得事已至此，再伤心也没用，向前看，找解决办法。重新上路投资后，我加强了相关学习。过程也几经波折，但如今暂时能有些收入，衣食无忧了。&lt;/p&gt;
&lt;p&gt;随着理财和投资的学习实践，我现在能感觉财富在积累，加上开支在减少，通过断舍离、高质量消费、资产管理，一切都在慢慢走上正轨。&lt;/p&gt;
&lt;h3 id=&quot;三提高生活质量重塑家庭生活基础&quot;&gt;三、提高生活质量，重塑家庭生活基础&lt;/h3&gt;
&lt;p&gt;首先就是吃什么？以前答案很简单：吃食堂、吃外卖。现在，我也开始做一些家常菜，原因很简单，一个是省钱，一个是为了更健康和自由。&lt;/p&gt;
&lt;p&gt;去哪玩？以前也没得选，周末高价票、堵车，去人挤人的地方。现在我可以自由选择，发现非周末人少景美票价便宜。而且很多地方免费，但体验更好，并不是贵的人扎堆的地方才好。&lt;/p&gt;
&lt;p&gt;用什么？我也开始关注。首先就是断舍离，扔了一堆东西，生活变得更简约：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&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;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;然后，我给之前“无印良品”风的冷淡装修风格增添了更多色彩和活力，比如客厅的森林挂画、老鼠餐巾纸盒、餐桌的怪兽桌布、洗漱的恐龙牙刷、狗狗肥皂盒、画板墙、照片墙。希望这些东西能潜移默化地增加小孩的生活感受，让生活更有色彩和温度。&lt;/p&gt;
&lt;h3 id=&quot;四超越物理空间发展社交关系&quot;&gt;四、超越物理空间，发展社交关系&lt;/h3&gt;
&lt;p&gt;我们常见的人际关系通常是被动获得的，比如亲属、同学甚至职场同事。但它们的问题在于，你几乎无法选择。&lt;/p&gt;
&lt;p&gt;好在当代互联网发达，完全可以借由信息社会，达成超越物理空间的关系。我预想会发展三类社交：功能型、欣赏型和爱好型。功能类的会占大多数，就是提供信息服务和社会关系；欣赏的可能很少，但很关键，和那些你欣赏佩服的、有趣的人在一起；爱好类的就是游戏这类，人不是机器，可以更有爱好和性格一些。&lt;/p&gt;
&lt;p&gt;但其实发展社交只是方式，核心还是要不断充实自己，持续培养个人能力，提升自我价值，才能自内而外地打造个人IP和社交圈。这里说的不是单纯地把自己工具化，而应该最好是成为一个有血有肉的人，分享工作、分享爱好、分享生活，这样才能具备建立多样化社交的前提。&lt;/p&gt;
&lt;p&gt;近期有意思的就是投资半年成为热门股票股主的事，给我带来一些意外和信心。对我来说投资本来是陌生领域，但因为要赚钱想更稳更好，就发挥学习能力，主动书籍和论坛各种阅读学习，也积极在论坛记录操作和心得，主要是个人总结。没想到半年以来粉丝越来越多，最近论坛联系当股票股主，他们说观察了很久，认为是具备有较为成熟的交易体系，内核稳定，知行合一，且粉丝众多，虽然交易阅历不多，但看中成长性。这可能是我第一次在非熟悉领域，非被动社交圈子获得认可，我承认学了一些个人IP的办法，通过投资论坛在实验，比如写一些精品和经历，找一些大V文章评论蹭流量，也无意得到了一些大V的推荐粉丝暴涨。不过从更深角度，并不是真的为了成为大V功利去做，而是正好自己想做好，顺势而为关注一些个人IP的方式而已。也明白其实要做好股主版主，其实并不是说要自己要阳春白雪曲高和寡，作报告一样自己发言其他人学习，而是应该带着大家一起去更好的认知和学习标的和投资，培养交流学习的场子比自己发言更重要。更深从意义角度来说，虽然只是虚名，但正因为生命只是一个过程，注意投入在哪里，生命就在哪里，所以才不想浑浑噩噩也不想虚度，既然要做尽量做好。&lt;/p&gt;
&lt;h3 id=&quot;五三个能力和终极问题&quot;&gt;五、三个能力和终极问题&lt;/h3&gt;
&lt;p&gt;经历了这半年的一切，我感觉生活似乎没有倒塌，反而朝着更好的方向发展。这不仅仅是幸运，我意识到有三个能力在起作用，能剥离工作也依然存在，它们是：改变的勇气、学习的能力，和超然的心态：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&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;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最近看完了《三体》，书中那句“给岁月以文明，而非给文明以岁月”让我感到震撼。震撼的背后，是“哀吾生之须臾，羡长江之无穷”的沮丧和无奈。在宇宙为纸、时空为笔的大框架下，甚至文明都微不足道，更何况人的一生。再波澜起伏，也只是微芥尘末，一切似乎都毫无意义。&lt;/p&gt;
&lt;p&gt;不过他也借小说写到，“我们度过了幸福的一生”，寥寥十字却小中见大，熠熠生辉。宇宙、时空、岁月、文明，毕竟都是宏大的叙事，造物主的词藻。而接受生之须臾，不卑不亢，才是人本位的答案。苏格拉底那句“认识你自己”，至今振聋发聩。&lt;/p&gt;
&lt;p&gt;回到现实生活，我觉得应该修正的是生活、工作和投资的态度。首先，摈弃职场流行的功利、急躁、“卷”等错误认知和杂念，去掉工具化和标签化。应该先回归到人本位，还原为一个真实的、有血有肉、有优点、有缺点、有爱好和品味的人，具备完整正常的人格。在此之上，再去构筑生活和工作能力，发展爱好和特长，给社会提供服务，形成价值交换，不贪不怂，获取应得的报酬即可。&lt;/p&gt;</content:encoded></item><item><title>2024下半年：求职、独立开发、投资、带娃</title><link>https://daolanx.me/zh/posts/post-4/</link><guid isPermaLink="true">https://daolanx.me/zh/posts/post-4/</guid><pubDate>Tue, 28 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;写于除夕夜，大致总结一下 2024 近半年的事情和感想。&lt;/p&gt;
&lt;h3 id=&quot;求职经历&quot;&gt;求职经历&lt;/h3&gt;
&lt;p&gt;从岗位出来以后，前几个月惯性的找工作，刷题 + 复习基础知识，面试了字节，外企，小公司。字节面试过三个团队，基本能到中后面，整体大概一半人认可一半人不认可我。然后又去面试了远程工作的两个外企，一个是终面没过不过那个面试我感觉很不专业，可能是他们初创团队层次不齐，一个是二面没过对方技术很好，没过我心服口服；也面试了一个创业小公司，对方还真有一套，我发现他是忽悠和PUA，还有什么不交五险一金要冒险，给公司投资等神思路，我以和技术团队聊聊为由也和他们技术沟通，对方其实挺务实但是比较老实，好像吃这一套，他一边说以后联系一边拉黑了我，也让我见识了招聘环境的复杂。后面也联系过几个跨界互联网岗位，没有后文。再后来我也没有去看招聘信息了。&lt;/p&gt;
&lt;p&gt;说实在的面试过程心情复杂，一方面有面试过程的忐忑，一方面其实对如今996工作不满意，不希望又加入这样的工作模式，感觉都是围城。当时的我只有情绪困惑，不明所以。&lt;/p&gt;
&lt;h3 id=&quot;对工作新的理解&quot;&gt;对工作新的理解&lt;/h3&gt;
&lt;p&gt;如今几个月学习，我学习到内心复杂纠结这背后的原因。&lt;/p&gt;
&lt;p&gt;首先是工作本身有局限性，因为职业是分工的，所以产出价值其实局限在职业那个环节，一个受限于岗位设计且不占有完整商业模式信息，本身产出价值就有限。某些组织鼓励超越职位体现自己价值，注定了成功率不高而容易动作变形而不自知。&lt;/p&gt;
&lt;p&gt;其次是缺少杠杆效应，工作主要是卖时间，一份时间一份工资，没有任何杠杆。很多产品因为边际成本递减效益本身可以卖出很多份来赚钱，但作为工作来说只能拿到一份钱。&lt;/p&gt;
&lt;p&gt;还有当代工作其实缺少长期保障。据说工作这个是工业时代诞生的，工业时代之前小农经济都自给自足，没什么工厂工作；工业化以后有了大规模生产，才出现了工人群体和职业分工，但那个时候确实有比较完善的公司保障。如今互联网时代，其实延续了工作的模式，但公司保障变得不再稳定。也就是工作看似是稳定收入的最常见的从业方式，但其实它也有很多缺陷要值得注意。&lt;/p&gt;
&lt;p&gt;我觉得比较理想值得加入的工作，是正和和生态模式。正和是指大家一起把事情做成，而不是陷入零和的绩效和评比博弈；生态是指大家都各司其职，能够完成形成商业闭环。我早期经历过一些团队，现在终于可以描述他们为正和和生态型团队。这样的环境，虽然工作看上去是一份工资和无杠杆的，但过程中相互配合做正和会在产品，能力和情感上得到满足，也有做大蛋糕正和的机会，如有人有幸加入这个趋势和团队的工作，请珍惜和充分发挥，哪怕最后一无所成，生命其实也是个过程，有这段经历也弥足珍贵。&lt;/p&gt;
&lt;h3 id=&quot;对技术新的理解&quot;&gt;对技术新的理解&lt;/h3&gt;
&lt;p&gt;作为技术工作者，在职位分工下，能力和方向也容易被误导，难以获取长期良好发展。因为出来以后我看了很多同方向岗位，发现之前厂里技术，在市场环境下其实并不普遍使用，市场上流行的技术并没有在厂里使用，更嘲讽的是，一些技术问题我们花了很多时间去阶段性优化反复补丁，其实市场流行技术已经很好的解决了这个问题，并且内置在方案里，也可笑可悲的是，厂里很多人用这种阶段性的远低于市面技术的，给老板做汇报来获得利润和晋升，技术不再用于做更好的产品，追求更先进的生产力，而是沦为一种表演和迎合。&lt;/p&gt;
&lt;p&gt;职位分工以后，对技术从业者精神上也有一定误导。大学枯燥教育、找工作考试面试八股和厂里拧螺丝工作，会让人觉得技术很单一化，和枯燥，以及和内卷和加班的累所关联。但其实真的技术内涵和这些负面的因素无关。&lt;/p&gt;
&lt;p&gt;那什么是真正的技术，以及技术人的能力和精神，甚至文化？我读了很多技术大牛的视频和采访，也读了一些黑客和开源历史，对我来说影响比较深的共鸣是苹果的沃滋和 Linux 的李维斯，他们对技术的理解很简单，为了好玩或者有价值。我觉得大道至简，可以用这个来洗涤我之前对技术的误解，和，作为很长一段时间学习技术的标准。&lt;/p&gt;
&lt;h3 id=&quot;一人公司&quot;&gt;一人公司&lt;/h3&gt;
&lt;p&gt;既然找工作难以找到适合的，工作自身也有限制，那有没有更好的方式参与社会和获取收入？&lt;/p&gt;
&lt;p&gt;通过一些渠道，我了解到一人公司，从逻辑上讲确实不错，自己跑通商业闭环，完整发挥价值，且时间自由。更大的背书来自于《富爸爸穷爸爸》，他提到了 ESBI几个象限的赚钱纬度，其实一人公司就是他比较推荐的 B 类赚钱，空间和杠杆都比较好。&lt;/p&gt;
&lt;p&gt;但结合自身能力，感觉落地门槛其实挺高的，因为他不仅要优秀的技术能力来提高生产力和降低成本，更需要完整的产品设计，营销推广和运营能力。&lt;/p&gt;
&lt;h3 id=&quot;独立开发&quot;&gt;独立开发&lt;/h3&gt;
&lt;p&gt;那有没有折中一点的？可能更适合阶段转型的就是独立开发了。&lt;/p&gt;
&lt;p&gt;《富爸爸》那本书说，独立开发类个体，是 S 象限赚钱，也就是虽然比 E 类雇员模式好一些，虽然能拿到较为完整的个体价值，但是也是劳碌命，因为个体的时间和精力毕竟有限。但其实我看了《单干》《自由职业者生存指南》《个人品牌打造：从0到1低成本创业》《一人公司》这些书，发现，其实个体工作者也没那么傻，他们当然也不希望劳碌命，其实在后期都会转型，有些做产品，有些做小公司，其实是条条大路通罗马。&lt;/p&gt;
&lt;p&gt;独立开发和雇员最大的不同，在于可以直面市场需求，积极改进生产技术，和通过积累客户和方案来获取复利；而这类在雇员模式下，往往合作对象和生产工艺是确定的，个体没有选择的自由，也无从积累复利。&lt;/p&gt;
&lt;p&gt;说干就干，既然国内市场太卷，那就看看国际市场，我自己打算是用 upwork 赚钱，主要要先补两个东西，一个是首先是要熟悉市面上流行技术和做一些案例，一个是习惯用英语学习和交流。看上去比找工作难，但其实无论技术案例还是英语，其实都是能积累复利的，先难后易。这也是我改成双语博客的原因。&lt;/p&gt;
&lt;p&gt;我也大致看了一下，其实 upwork 上竞争也挺大的。不过我目标不是做最优秀的那群人，我觉得能跑赢一般的那批人就可以了。写哈利波特的罗琳也说，如果你因为畏首畏尾而经历失败，你的人生默认就是失败的。成功就总结经验，失败就总结教训，持之以恒应该有收获。&lt;/p&gt;
&lt;h3 id=&quot;投资之路&quot;&gt;投资之路&lt;/h3&gt;
&lt;p&gt;这个是年前才想到的，既然个人公司和独立开发时间线比较长，有没有什么办法能有一些收入。我想到了投资。因为营收来说，需要充分发挥能力和资源，而出来以后，正好能获得一部分资金。《富爸爸穷爸爸》系列，也给了我一些启发。&lt;/p&gt;
&lt;p&gt;于是去香港办了卡，正式开始投资赚点小钱，希望经济上，能给自己更多余地。虽然几个月，也学到不少。最早是投资美股几个朋友推的股票，当月是赚钱了，感觉赚钱挺简单。但好景不长，一个月后开始跌了亏钱。我开始继续学习，发现这类股票其实本身就是加杠杆了，也就是牛市自然好，但是熊市很不耐凑，操作上我也花了五位数的损失买了一个教训。因为学了《富爸爸穷爸爸股票投资从入门到精通》提到不用钱也能赚钱的办法，当时看了没明白，忽然一天有个特别负面的消息出来，我忽然懂了那个叫做空，我觉得消息很负面要利用起来，于是满融资去做空，非常巧的是当时中美元首通电话，带来了更大的利好，于是做空卖出的股票，开盘以后直线上涨。从慌到分批平仓，最后亏损了5位数，结结实实给我上了一课。本来就不赚钱，结果更亏了。&lt;/p&gt;
&lt;p&gt;不懂就继续学，《像巴菲特一样投资》这类书籍看完，加上上次教训，我自己收获是，首先操作上还是要端正心态，不要情绪化要理性思考，自律和纪律性。其次选股上还是要关注基本盘，选好公司，赚懂的钱，尽量不做空不杠杆不做波动不投机。因为好公司自然会带来很多好机会，和好公司一起成长。短线和波动，杠杆带来的消息确实令人羡慕和吸引人，但是最好不要碰自己不懂的钱。可能以后自己更懂了会去把握一些机会，或者在理性范围内做一些投机，但是现阶段不要急，慢慢来，比较快。&lt;/p&gt;
&lt;p&gt;又经过一个月，因为价值投资，基本快追回之前损失了。希望是个好的开始吧。&lt;/p&gt;
&lt;h3 id=&quot;全职奶爸&quot;&gt;全职奶爸&lt;/h3&gt;
&lt;p&gt;计划赶不上变化，因为家庭的一些原因和波折，需要一段时间全职带娃，吃喝拉撒玩，基本 12 小时不能休息。时间变成很碎，碎到不能支持独立开发，甚至写这篇博客也是在娃的吵闹中断断续续写成。&lt;/p&gt;
&lt;p&gt;零散的时间可以手机来读书，这段时间我印象最深的有两本书，一本是《纳瓦尔宝典》，他提到了要接受现状，不要对抗，先接受现状，直面问题，才能解决问题。还有一本是《活着的意义》，书里记录了集中营的人，赤身裸体丧失一切身份财物以后，在最基本的生死线上的一段经历，他写道人无论处于什么环境下，其实都有选择自己行为和意志的自由。&lt;/p&gt;
&lt;p&gt;没有自己时间这段我也经历了几段情绪起伏。最开始不适应来自于以前工作的带来的副作用，因为很长一段时间，不工作的时间都会有种羞耻和负罪感和害怕混合的情绪。后来是来自于自我无法实现的难受，想做一些工作却没有条件的难受；现在我尝试在接受现状，心平气和，接受生命本来就是一个从无到无的过程，其实意义都是自己赋予的，工作有意义，带娃其实也有意义，陪伴的时光其实是一天天减少的，做好每一天的陪伴，比起无意义的工作，内卷的消耗要好得多。&lt;/p&gt;
&lt;p&gt;我痛苦没有时间，但也接受现在带娃时间的价值。可能娃再大一些，上幼儿园了，我就能多一些个人时间了吧。&lt;/p&gt;
&lt;p&gt;如果一句话归纳，我觉得现阶段是：接受现状，认识自己，热爱生活。&lt;/p&gt;</content:encoded></item><item><title>群晖 NAS 部署思源笔记 + Cloudflare Tunnel 外网 HTTPS 访问</title><link>https://daolanx.me/zh/posts/post-3/</link><guid isPermaLink="true">https://daolanx.me/zh/posts/post-3/</guid><pubDate>Tue, 29 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;一为什么用思源笔记&quot;&gt;一、为什么用思源笔记&lt;/h2&gt;
&lt;p&gt;之前用的一款在线笔记，使用上倒是没什么问题，整体功能迭代很慢尚能忍受，最近想做自己的笔记数据导出，发现并没有提供批量导出，即使一大群用户反馈希望这个功能，但他们并没有回应，就连付费用户也没有这个功能，反倒是网上有人写了第三方工具支持…这可能就看出做产品一些思路和格局问题，产品粘性不是用更好的体验和功能来达成，而是想通过用户使用累计数据更多的带来更大的迁移成本来圈住用户，这就有点过分了。为长期考虑，用三方工具导出我的笔记后，开始找替代品。&lt;/p&gt;
&lt;p&gt;我希望的笔记产品具备两个核心特性，一个是数据自由和数据安全，一个是 markdown 的所见即所得的体验。notion 其实体验不错，但是数据和网站在国外，哪怕折腾数据做同步，访问速度也是个问题；obsidian 是比较热门符合基本条件，也有丰富的插件生态，但对我来说，使用 app 不太习惯，我更习惯使用浏览器，还有它一堆的教程看着头疼，我诉求是写笔记，不是折腾笔记本身，希望开箱即用。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://b3log.org/siyuan/&quot;&gt;思源笔记&lt;/a&gt; 感觉还可以。支持 nas docker 部署和导入导出，数据安全自由；体验上也是所见即所得；开源软件，仓库还算活跃，有一定数量的插件；因为我主要用 docker 部署 web，web 天生就是 c/s 架构，免去了数据同步的费用和折腾。
思源作者也有点故事，&lt;a href=&quot;https://github.com/88250&quot;&gt;b3logos&lt;/a&gt; 持续在这个领域开源建设 10+ 年，唯一不太好的就是作者有黑历史不过作者也&lt;a href=&quot;https://github.com/siyuan-note/siyuan/issues/5721&quot;&gt;公开道歉了&lt;/a&gt;, 除了热情和能力，我还是欣赏他的真诚和勇气。&lt;/p&gt;
&lt;h2 id=&quot;二为什么用-cloudflare-tunnel-做内网穿透&quot;&gt;二、为什么用 Cloudflare Tunnel 做内网穿透&lt;/h2&gt;
&lt;p&gt;一句话总结，可能是在没有公网 IP 下，实现内网穿透自定义域名 https 的最简单且免费的方案。&lt;/p&gt;
&lt;p&gt;如果有公网 IP，DDNS 方案最简单稳定。我暂时没办法搞到公网 IP，就尝试了一些内网穿透方案：花生壳比较出名，无论路由器还是 NAS 上也有插件可以配置，但要实名认证，自定义域名解析也要付费。cpolar 其实跑通了，主要是支持自定义域名要购买付费专业版，还要自己折腾 https 证书。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cloudflare.com&quot;&gt;Cloudflare&lt;/a&gt; 不愧是赛博菩萨，支持自定义域名，自动配置 https 证书，而且完全免费。唯一不足就是服务器在国外，对访问速度有点影响。我尝试了一些优选 IP 方案想加速方案，不过并没有成功。打算先笔记服务用起来，后续争取搞到公网 IP 再直接切换到 DDNS 方案，避免内网穿透总是有个数据中转带来的速度和成本问题。&lt;/p&gt;
&lt;h2 id=&quot;三如何在群晖-nas-docker-部署思源笔记&quot;&gt;三、如何在群晖 NAS Docker 部署思源笔记&lt;/h2&gt;
&lt;p&gt;这里我用的是群晖 DS220+ 支持 Docker.&lt;/p&gt;
&lt;h3 id=&quot;31-配置思源的文件夹&quot;&gt;3.1 配置思源的文件夹&lt;/h3&gt;
&lt;p&gt;使用 File Station 套件，在 docker 文件夹下新建 siyuan / workspace 的文件夹。&lt;/p&gt;
&lt;p&gt;siyuan 和 workspace 文件夹右键属性设置 Everyone 读写权限，并应用到子文件夹。可以直接在顶层 docker 文件夹设置读写和继承权限更方便。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/3/1-1.webp&quot; alt=&quot;1-1&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;32-下选择思源-docker-镜像&quot;&gt;3.2 下选择思源 Docker 镜像&lt;/h3&gt;
&lt;p&gt;在套件中心搜索安装 Container Manager 套件。在该套件下选择思源镜像 blog3/siyuan 下载。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/3/1-2.webp&quot; alt=&quot;1-2&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;33-启动思源容器&quot;&gt;3.3 启动思源容器&lt;/h3&gt;
&lt;p&gt;因为 Container Manager 界面安装的执行命令输入框总提交报错，在这里我还是改用命令行更清晰。在 ssh admin 账户下操作，注意 accessAuthCode 的 xxx 需要改为自己定义的授权码，将会作为思源的登录密码：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -i&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; # 切换到 root 账户&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 安装群晖, [命令行说明](https://github.com/siyuan-note/siyuan)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; docker&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -d&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --name&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; siyuan&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -v&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /volume1/docker/siyuan:/siyuan&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -p&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 6806:6806&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -u&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; root&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; b3log/siyuan:latest&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -workspace&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /siyuan&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --accessAuthCode=xxx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;34-内网运行测试&quot;&gt;3.4 内网运行测试&lt;/h3&gt;
&lt;p&gt;浏览器访问 群晖ip:6806，密码是安装时的 accessAuthCode.
&lt;img src=&quot;/images/posts/3/1-4.webp&quot; alt=&quot;1-4&quot;&gt;&lt;br&gt;
界面可以通过 setting 修改语言为中文.
&lt;img src=&quot;/images/posts/3/1-5.webp&quot; alt=&quot;1-5&quot;&gt;&lt;/p&gt;
&lt;p&gt;这样群晖内网安装思源笔记就完成了。&lt;/p&gt;
&lt;h3 id=&quot;35-启动容器遇到的一些问题和解决办法&quot;&gt;3.5 启动容器遇到的一些问题和解决办法&lt;/h3&gt;
&lt;h4 id=&quot;351-container-manager-镜像列表无数据&quot;&gt;3.5.1 Container Manager 镜像列表无数据&lt;/h4&gt;
&lt;p&gt;如镜像源列表和下载有问题，可以先试试 Beta 版本， 我使用的是 24.0.2-1525, 修复了下载镜像源获取和下载的一些问题。&lt;/p&gt;
&lt;h4 id=&quot;352-启动失败-addgroup-permission-denied&quot;&gt;3.5.2 启动失败 addgroup: permission denied&lt;/h4&gt;
&lt;p&gt;执行上述 docker run 以后安装失败，提示&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Creating&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; group&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; siyuan&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (1000)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;addgroup:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; permission&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; denied&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (are &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;you&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; root?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;尝试 sudo -i 切换为 root 账户执行，报错仍然存在。 一番排查发现是要安装 Entware，创建组和用户。&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 参考 [Entware](https://github.com/Entware/Entware/wiki/Install-on-Synology-NAS) 安装 Entware&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;wget&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -O&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https://bin.entware.net/x64-k3.2/installer/generic.sh&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; /bin/sh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 更新源&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;opkg&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; update&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 安装 shadow-utils&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;opkg&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; shadow-utils&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 创建组和用户&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; groupadd&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -g&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1000&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; siyuan&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; useradd&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -u&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1000&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -g&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; siyuan&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; siyuan&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;四如何配置-cloudflare-tunnel&quot;&gt;四、如何配置 cloudflare Tunnel&lt;/h2&gt;
&lt;h3 id=&quot;41-将域名托管到-cloudflare&quot;&gt;4.1 将域名托管到 cloudflare&lt;/h3&gt;
&lt;p&gt;可以在 Cloudflare 新建域名，或者将&lt;a href=&quot;https://juejin.cn/post/7267733291569168423&quot;&gt;已有域名托管到 Cloudflare&lt;/a&gt;。直到管理域菜单下域名状态正常。
&lt;img src=&quot;/images/posts/3/2-15.webp&quot; alt=&quot;2-15&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;42-cloudflare-网站新建-tunnel&quot;&gt;4.2 Cloudflare 网站新建 Tunnel&lt;/h3&gt;
&lt;p&gt;访问 Cloudflare Zero Trust 下的 tunnels 页面，点击新建 tunnel&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/3/2-1.webp&quot; alt=&quot;2-1&quot;&gt;
&lt;img src=&quot;/images/posts/3/2-2.webp&quot; alt=&quot;2-2&quot;&gt;
&lt;img src=&quot;/images/posts/3/2-3.webp&quot; alt=&quot;2-3&quot;&gt;&lt;/p&gt;
&lt;p&gt;这里 tunnel 名字随便写一个
&lt;img src=&quot;/images/posts/3/2-4.webp&quot; alt=&quot;2-4&quot;&gt;&lt;/p&gt;
&lt;p&gt;这里 Cloudflare 给了我们当前 tunnel 的 token， 并提示接下来是要安装环境客户端了。
&lt;img src=&quot;/images/posts/3/2-5.webp&quot; alt=&quot;2-5&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;43-nas-安装-cloudflare-客户端&quot;&gt;4.3 NAS 安装 Cloudflare 客户端&lt;/h3&gt;
&lt;p&gt;这里可以选择两种安装方式。&lt;/p&gt;
&lt;h4 id=&quot;431-最简单的安装方式使用套件安装&quot;&gt;4.3.1 最简单的安装方式，使用套件安装&lt;/h4&gt;
&lt;p&gt;最简单的安装方式是，套件中心的套件来源右上角新增 &lt;a href=&quot;https://spk7.imnks.com&quot;&gt;矿神群晖&lt;/a&gt;, 在社群的套件里可以安装 Clouflare tunnel，输入上面网站给的 token。我遇到问题是这个镜像源不是很稳定，有时候套件列表无数据没显示。
&lt;img src=&quot;/images/posts/3/2-6.webp&quot; alt=&quot;2-6&quot;&gt;
&lt;img src=&quot;/images/posts/3/2-7.webp&quot; alt=&quot;2-7&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;432-比较稳定的安装方式采用-docker-安装&quot;&gt;4.3.2 比较稳定的安装方式，采用 docker 安装&lt;/h4&gt;
&lt;p&gt;在 Container Manager 下载镜像 cloudflare/cloudflared&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/3/2-8.webp&quot; alt=&quot;2-8&quot;&gt;&lt;/p&gt;
&lt;p&gt;使用命令行安装，比官方命令行加了 -d —name=cloudflare —restart unless-stopped 设置容器名字、后台运行、和非手动关闭自动重启. your-token 需要改为刚才官网上提供安装方式的 token.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -d&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --name=cloudflare&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --restart&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; unless-stopped&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; cloudflare/cloudflared:latest&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; tunnel&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --no-autoupdate&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --token&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;your-toke&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;n&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Docker 运行以后，刷新 Cloudflare 网站的 tunnels 页面，可以看到 tunnel 状态正常，点击创建的 tunnel 继续配置 Public Hostname&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/3/2-9.webp&quot; alt=&quot;2-9&quot;&gt;
&lt;img src=&quot;/images/posts/3/2-10.webp&quot; alt=&quot;2-10&quot;&gt;
这里配置成外网访问的域名和子域名，和内网思源文档的 http 访问方式。这里以外网访问 note.xyz.com 为例, 192.168.x.x:6806 是内网访问思源的 ip 和端口号
&lt;img src=&quot;/images/posts/3/2-11.webp&quot; alt=&quot;2-11&quot;&gt;
配置成功以后，可以看到托管域名的 DNS 上多了一条 CNAME 记录。
&lt;img src=&quot;/images/posts/3/2-13.webp&quot; alt=&quot;2-13&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;433-外网访问测试&quot;&gt;4.3.3 外网访问测试&lt;/h4&gt;
&lt;p&gt;可以通过 https 用刚才的域名访问到 NAS 部署的思源文档。至此安装就完成了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/3/2-12.webp&quot; alt=&quot;2-12&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;感想&quot;&gt;感想&lt;/h3&gt;
&lt;p&gt;和公司指定用一款产品/技术不同，好的技术和产品生态其实不是只有标准答案和零和博弈，产品技术应该像逛超市一样各有特色，有可以选择的东西，和结合主客观情况选择的自由，以及组合的灵活性。&lt;/p&gt;
&lt;h2 id=&quot;参考资料&quot;&gt;参考资料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.itdog.net/post/608.html&quot;&gt;利用 Docker 安装思源笔记服务器&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://hackfang.me/nas-cloudflare-tunnel&quot;&gt;使用 Cloudflare tunnel 进行内网穿透教程&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>用 ESP32 和电子墨水屏打造 AI 生成图片的低功耗自动更新个性化日历</title><link>https://daolanx.me/zh/posts/post-2/</link><guid isPermaLink="true">https://daolanx.me/zh/posts/post-2/</guid><pubDate>Mon, 14 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;这是一款基于 ESP32 的电子墨水 AI 日历。只属于当天的不可保存不可变更的图文，和低功耗无开关设计，恰似昨日不可留，今日如新，明日不可知，静默流逝而不觉的时光。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/daolanx/eink-ai-calendar&quot;&gt;https://github.com/daolanx/eink-ai-calendar&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;div style=&quot;display: flex; flex-direction: row; gap: 10px;&quot;&gt;
  &lt;div&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/2/ai-calendar-front.jpg&quot; alt=&quot;ai-calendar-front&quot;&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/2/ai-calendar-back.jpg&quot; alt=&quot;ai-calendar-back&quot;&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/2/ai-calendar-screenshots.png&quot; alt=&quot;ai-calendar-screenshots&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/2/ai-calendar-componets.jpg&quot; alt=&quot;ai-calendar-components&quot;&gt;&lt;/p&gt;
&lt;h1 id=&quot;起因&quot;&gt;起因&lt;/h1&gt;
&lt;p&gt;最早也是看到凉糕大佬的 &lt;a href=&quot;https://niu.sspai.com/post/82704&quot;&gt;《我在数字时代做了一个电子日历，让油画和照片可以被装进去》&lt;/a&gt;, 觉得效果惊艳，也想有个类似的东西来提醒自己时间的不可挽留不可预知独特又容易淡忘。&lt;/p&gt;
&lt;p&gt;最初其实是想要一个能够获取在线相册的电子日历，最好能把 NAS 上相册随机显示，不过购物 APP 找了一圈，首先相册基本都是存本地存储卡的（后来自己做才懂为什么），其次屏幕也是黑白的居多，顶多有个三色的屏幕，然后显示上要么是纯相册，要么是日历，密密麻麻的信息。也就是市面上还真没有想要的产品。&lt;/p&gt;
&lt;p&gt;于是想能不能自己做，跟着上文评论区找到了热心的 &lt;a href=&quot;https://github.com/Ymriri&quot;&gt;Ymriri&lt;/a&gt; 实现的 &lt;a href=&quot;https://github.com/Ymriri/esp32_7color&quot;&gt;esp32_7color&lt;/a&gt;。很欣赏这种热心和动手能力，能个前后端分离的电商管理平台，魔改成相册管理，整合自己的项目和技术做出结果。但是。。代码太乱一言难尽，看到项目文档写了，“我知道你想说什么，这个文件夹结构太乱了，我也知道，但是我懒得改了，你可以自己改” 。。后来在代码里看得云里雾里的，几次折腾以后放弃了。&lt;/p&gt;
&lt;p&gt;最后找到 &lt;a href=&quot;https://github.com/Debatrix&quot;&gt;Debatrix&lt;/a&gt; 的 &lt;a href=&quot;https://github.com/Debatrix/eink-calendar&quot;&gt;eink-calendar&lt;/a&gt;，他代码最简单，于是仗着有 cursor + gpt 编程搭子，从未接触的 python c 都敢碰，用这个边学边改。&lt;/p&gt;
&lt;h1 id=&quot;从-nas-相册日历到-ai-图文日历&quot;&gt;从 NAS 相册日历到 AI 图文日历&lt;/h1&gt;
&lt;p&gt;最早其实是想找群晖接口来读取相册数据，后来一个是接口调取比较麻烦，二是做着做着忽然有灵感，AI 图片如结合当天信息进行生成，其实更符合日历的感觉。于是就去找 AI 文生图接口。&lt;/p&gt;
&lt;p&gt;最早尝试了 &lt;a href=&quot;https://developers.cloudflare.com/workers-ai/&quot;&gt;cloudflare 的 workers-ai&lt;/a&gt;, 其实能运行，但是网速调用太慢了。于是想想国内接口的应该能快一点，用了阿里云的 AI SDK，感觉确实快了一些。&lt;/p&gt;
&lt;h1 id=&quot;为什么用-cs-架构&quot;&gt;为什么用 C/S 架构&lt;/h1&gt;
&lt;p&gt;看凉糕和 Debatrix 大佬的代码，都是 C/S 架构。做的时候会发现 C/S 架构明显更麻烦，服务端部署在哪里是个问题，我猜这也是很多商品相册，相册放存储卡的原因，因为从维护成本，安全隐私考虑，确实本地更合适，可能商家早就想到这一点了，产品功能上做了取舍只支持本地化。&lt;/p&gt;
&lt;p&gt;我想干脆我直接 ESP32 端获取原始图片，色彩抖动处理，数据处理不也一样吗，就多费一点电而已。实际调试好想不行，确实会不支持，原因未知，可能就是算力存储问题。这方面资料太少，因此我也先继续用 C/S 架构。&lt;/p&gt;
&lt;p&gt;那 C/S 架构，服务端部署在哪里，最初是想用 serverless，因为按需使用最符合这个场景。之前尝试用 &lt;a href=&quot;https://developers.cloudflare.com/workers/&quot;&gt;cloudflare 的 workers&lt;/a&gt;, 实际测试不行，因为这个 workers 不支持 python 的一些库函数，比如 &lt;code&gt;PIL&lt;/code&gt; 等。然后也看了阿里云的个轻量级的 ECS 和 serverless. 迫于文档和计费，我想想能不能树莓派部署，但是树莓派又得插上电麻烦，正好家里有支持 Docker 的群晖 NAS, Docker 镜像部署在 NAS 上，安全又省心。&lt;/p&gt;
&lt;h1 id=&quot;更简洁的-ui&quot;&gt;更简洁的 UI&lt;/h1&gt;
&lt;p&gt;既然凉糕的待办信息换成格言，UI 也需要进行修改。看了一圈类似开源日历，可能和程序员信息导向习惯堆砌信息有关, 大多信息堆叠。于是我上购物 APP 参考日历台历，感觉好的布局也不多，有一些看似好看，实际信息是分散的。其实也没找到更好的布局，目前主要做的就是删减信息，相关信息聚合，更简洁。有些大佬会用 figma 重新设计，还没学到。&lt;/p&gt;
&lt;h1 id=&quot;更简洁的功能&quot;&gt;更简洁的功能&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/Debatrix&quot;&gt;Debatrix&lt;/a&gt; 的 &lt;a href=&quot;https://github.com/Debatrix/eink-calendar&quot;&gt;eink-calendar&lt;/a&gt; 包含很多功能，比如图片存储，hash 检查，还有开关控制，LED 控制。其实最早是是想加一个开关控制刷新，但是控制电路自己也不太会，额外的电路可能外观设计更麻烦，可能不能变更有仪式感。于是就去掉了开关和 LED。&lt;/p&gt;
&lt;p&gt;每日刷新一次怎么实现，RTC 时钟模块好像可以做到，一想又变复杂了。我发现除了唤醒机制，还有个特性是 wifi 连接以后能获取时间。于是我想到，每个小时唤醒一次，wifi 连接以后，获取时间，正好是凌晨就更新，这样就实现了每日刷新。不依赖 RTC 和 Server 的 每日凌晨更新。&lt;/p&gt;
&lt;h1 id=&quot;更美观的外观&quot;&gt;更美观的外观&lt;/h1&gt;
&lt;p&gt;最早是用凉糕的模型打印，发现模型可以用，但是需要去掉 ESP32 的引脚，我尝试了电烙铁去引脚没成功。还有外壳我觉得塑料的或者积木的都没有木质的感觉。于是去买了木质的相框，原相框背面背板太厚，于是又单独买了薄的背板。用游标卡尺测量内径比着买电池，这样刚好能放下芯片和电池。&lt;/p&gt;
&lt;p&gt;然后是怎么连接背板和相框，最早想的是找个日记本类似的小锁扣，但是没有找到合适的，也不想边框突兀一个插销。后来想到可以用磁吸，买了磁铁，发现吸力有点大，怕固定胶粘不住，于是 3d 打印设计了磁铁盒子，减少吸力增加粘胶面积。&lt;/p&gt;
&lt;p&gt;目前使用上感觉还好但磁力偏小，更好其实是后盖直接用磁铁，相框用磁铁盒子来达到合适的磁力。&lt;/p&gt;
&lt;p&gt;3D 建模我也不会，也是请教 gpt 告知步骤现学现卖，自己操作软件，新建矩形，布尔差值运算，来实现遮罩和磁铁盒子。&lt;/p&gt;
&lt;h1 id=&quot;开发心得&quot;&gt;开发心得&lt;/h1&gt;
&lt;p&gt;坦白讲，这段时间也是技术迷茫期的探索之旅。之前我其实对技术是有抵触和陌生感的。这段时间从&lt;a href=&quot;https://www.youtube.com/watch?v=MKXjjpZqZwU&quot;&gt;苹果的沃兹&lt;/a&gt;、&lt;a href=&quot;https://www.youtube.com/watch?v=o8NPllzkFhE&quot;&gt;Linux 的托瓦兹&lt;/a&gt; 这类牛人的演讲受到启发，无论技术创始人，还是用技术的牛人的观点看，真正的技术其实是用来玩，或者创造价值的，而不是枯燥的考试和工作。他们并不是刻苦努力达成的，沃兹正好赶上了硅谷发展，是玩着数学和电路长大的，托瓦兹是也是为了好玩做的 Linux，为了维护 Linux 做了 Git。很遗憾没有亲身经历硅谷高速发展的阶段，也在大学和工作里曲解了一些认知，并没真正的去理解技术。&lt;/p&gt;
&lt;p&gt;那还原到基本的技术认知，这次开发学到的是：&lt;/p&gt;
&lt;h3 id=&quot;1-保持勇气和好奇&quot;&gt;1. 保持勇气和好奇&lt;/h3&gt;
&lt;p&gt;之前面对不熟悉的技术栈，我总是有些抵触感，但这次借助 cursor 和 GPT，我能更好地解读代码，选择去尝试。AI 虽然会影响程序员，但也带来了更多机会。比如这次，我对 ESP32 和 Python 并不熟悉，但在 AI 的辅助下，能进行调试和改造，接触到更多有趣有用的项目。不过，我也发现 AI 目前还无法独立完成所有任务，某些方面仍有局限，缺乏整体认知。但它作为编程助手，可以帮助生成功能明确的代码。程序员与 AI 的关系就像厨师与配菜员，也想 EVA 驾驶人和机甲，可以密切合作, 释放潜力。&lt;/p&gt;
&lt;p&gt;过程中的困难，我想起最近玩黑神话，无论大头还是秀士黑熊虎先锋有些 BOSS 开始觉得完全没希望很难过，后面克服畏难，索性抱着切磋学习的态度去玩，渐渐就熟悉了看清了然后过了，真是一种奇怪的体验，比起方式方法，更重要的其实是勇气。有勇气去尝试，一切才有可能。&lt;/p&gt;
&lt;p&gt;关于勇气，&lt;a href=&quot;https://www.youtube.com/watch?v=wHGqp8lz36c&quot;&gt;罗琳&lt;/a&gt; 说人生都会经历失败，除非你活的很小心，就像没有活过，那更是一种失败。&lt;/p&gt;
&lt;h3 id=&quot;2-格物致知&quot;&gt;2. 格物致知&lt;/h3&gt;
&lt;p&gt;技术本质是方式而不是目的。因此学习最好的方式不是学而是用起来。过程中会不断发现问题或者目标，不断整合资源方法，去解决问题靠近目标可能是更好的方式。这样学习过程中包含了学习的方法和不断克服困难的心态。收获远大于单纯的学习获得答案。&lt;/p&gt;
&lt;p&gt;python, esp32,墨水屏的端，之前都不熟悉，但是会发现在 web 的处理，在程序的基本逻辑，端的处理上，大体是相似的。会有一种熟悉感，可能这就是大佬说的计算机和程序基础，触类旁通。&lt;/p&gt;
&lt;p&gt;还有些问题可能有很多种处理方式，甚至有很多方案，在选项集里它们不是零和博弈和排它的，没有所谓的唯一标准答案，取舍在于自己当前的判断取舍。&lt;/p&gt;
&lt;h3 id=&quot;3-完成比完美更重要&quot;&gt;3. 完成比完美更重要&lt;/h3&gt;
&lt;p&gt;很多人说过这条，不过自己经历过才知道什么意思。过程中不断有冲动想重构，用 python 的 flask，甚至用 nodejs 重构的想法冒出来，但是理智告诉自己，这样会降低完成进度甚至可能性，追求完美可能就无法完成。需要聚焦目标，先完成功能，保持克制和清醒。&lt;/p&gt;
&lt;h3 id=&quot;4-乐于分享&quot;&gt;4. 乐于分享&lt;/h3&gt;
&lt;p&gt;沃兹说技术是做好的事和分享。希望这次我做到了。&lt;/p&gt;
&lt;h1 id=&quot;感谢&quot;&gt;感谢&lt;/h1&gt;
&lt;p&gt;感谢开源，能让我们接触到热心的人，有趣的项目，整合创造自己想做的东西；
感谢 AI 让我们能更容易参与和使用不熟悉技术的项目。&lt;/p&gt;</content:encoded></item><item><title>再见蚂蚁，海阔天空</title><link>https://daolanx.me/zh/posts/post-1/</link><guid isPermaLink="true">https://daolanx.me/zh/posts/post-1/</guid><pubDate>Fri, 14 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;2024 年 6 月，我离开了蚂蚁集团，距 2014 年 6 月入职蚂蚁刚好十年。更早一段是 2011 年 3 月到 2024 年 6 月的淘宝卡日曲项目组工作三年，加起来阿里十三年。&lt;/p&gt;
&lt;p&gt;从百度商业产品实习出来后，最早正式工作是在北京网易的一年，在做电商项目。那时候接到小马电话，说杭州淘宝看机会吗，这边有地球最牛的 UED。做为萌新哪受得了这个愿景，朝圣般的感觉去杭州面试，那几天，杭州下了很大很大的雪，颇有仪式感。因为能力还是不足，淘系大前端没面试通过。转到其他部门面试，本以为会铩羽而归，刚好淘宝在做创新项目，有个卡日曲项目组，正好招前端，记得当时二面是云谦老师，非常谦和，项目组负责人也很有朝气，感谢他们的信任，有幸通过了面试。&lt;/p&gt;
&lt;h2 id=&quot;卡日曲项目组再也未见当年草根般的创业热情&quot;&gt;卡日曲项目组，再也未见当年草根般的创业热情&lt;/h2&gt;
&lt;p&gt;卡日曲是黄河源头，项目组以此起名，其实也和做的方向有关，即互联网流量，当时还是流量入口为王的时代。当时项目组有淘网址、淘视频、手机管家等产品，记得当时有些产品能排到国内前几名。&lt;/p&gt;
&lt;p&gt;比起产品，印象更深的还是项目组的同事，那时候真的是在小黑屋，有产品有运营有技术有设计有测试，一起做产品，看数据看反馈。最荣幸的是马老师也来过，饶有兴趣的看我们产品，当时负责人和马老师沟通也很牛，陪同过程全程讲数据指标，谙熟于心，估计准备已久。&lt;/p&gt;
&lt;p&gt;比起产品上的增长，不乐观的就是我们的晋升，每次晋升因为有专业评审，经常被专业能力干下来，经常晋升季以后唉声叹气，吐槽那些专业评委不懂产品，当时架构师也开玩笑说我们是泥腿子。玩笑归玩笑，大家关系还是其乐融融，当时互联网发展也很快速，每个人都充满了自信和希望和包容。&lt;/p&gt;
&lt;p&gt;如今想来，在卡日曲项目组，虽然技术上没什么大成长，但是最宝贵的收获是那种草根般的创业热情。后面无论哪个团队，可能规模更大，做事更专业了，但再也没有见过那种创业般的，为产品和用户的热情。&lt;/p&gt;
&lt;p&gt;后面因为部门变动，当时空降了设计师主管，因为觉得人不行，还有希望我做demo，大概就是做一周给老板看几秒那种。我也想做实际的产品，正好支付宝招聘前端，就想转岗过去。感谢玉伯的信任，后面就去了体验技术部。&lt;/p&gt;
&lt;h2 id=&quot;体验技术学院派的殿堂&quot;&gt;体验技术，学院派的殿堂&lt;/h2&gt;
&lt;p&gt;当时加入的是基础产品部，主要是网站相关业务。当时正好苏千他们推动了 Node.js 在支付宝首页的改造，有幸加入参与开发。当时我因为要发布官网上线，发现要走30+小平台才能完成开发发布，他们都震惊了，后面规划前端工程化改造，也基于信任我成为了蚂蚁最早的前端研发平台的雨燕前端研发主开发，当时我们一期二期效果还挺好的，得了不少奖。&lt;/p&gt;
&lt;p&gt;当时觉得自己很牛，现在想想其实是这样的，可能那个阶段，正好互联网发展快，对相关产品有旺盛的诉求，旺盛的产品需求和缓慢的研发效能成为主要矛盾，而蚂蚁这边本来就是金融和 java 为主，对前端的研发效能明显不足，存在空间，这构成了前端研发平台存在的基本空间和价值。然后在团队结构上，也基于臻儿对团队的理解，同事都非常多元化，虽然大家都顶着前端 title，但实际上差异很大，有些会写 java，天天聊卡夫卡和选举算法，DDD 和充血模型，有些用 go 写 docker 和构建服务，还有写宣传比较厉害的，整体上构成了团队能力比较全面，也是因为管理上的相信和放手，虽然大家能力差异比较多，但是合作上真的能互补，简单极致追求，所以产品开发执行上才有很好的推力，务实简单。在产品开发质量上得到了保障，设计上也一改蚂蚁金融多卡点多风格，借鉴了原研哉的极简设计，尽量场景化的提供服务，简单稳定的服务好开发者，在那个时期做出了代差感。当时雨燕的架构技术和推动上，也得到很多的支持，虽然我承担了那段时间前端主开发，现在想来更像自己更像魔戒的弗罗多，其实路途上巫师、精灵、矮人都比他厉害，虽然自己最终护送魔戒，但是没他们的帮助是不可能的完成的。&lt;/p&gt;
&lt;p&gt;还有一层来自于臻儿的趋势解读和愿景，一个是解读趋势 devops 符合发展趋势，一个是来自她的愿景，当时她觉得蚂蚁前端工程师本来是很有灵气和创造力，见过很多来支付宝以后就泯然众人被磨灭了，她希望做一个平台来解放和发挥前端工程师的创造力，当时的平台还能让前端同学创建和调用很多 BAAS 服务，虽然当时看有点激进，但是这个设计真的充满亮点。很多年以后看 vercel 似曾熟悉的设计，心情复杂。&lt;/p&gt;
&lt;p&gt;当时也有幸和很多大牛共事和听分享。比如阮一峰老师也短暂参与过项目开发，还有民工叔叔宣传 rxjs，周爱民讲 js 语言，还有很多 苏千、不四、贯高、沉鱼、偏右、云谦 和其他牛人。请教为什么高P能坚持写代码，回答很简单，觉得有价值，持续做；请教怎么做好的设计架构，回答主要是，关注代码质量，测试，cr。白嫖方案给他提问题，换来的是热心感谢你的支持。真的是谦逊到骨子里。还有一些本来sql一窍不通的搞可视化研究的，基于对技术的研究态度，照样可以短期完成 sql 引擎的设计，真的不一样。保持谦逊和学习，对技术无止尽的追求，我觉得是大牛的明显特质。&lt;/p&gt;
&lt;p&gt;后面金融核心支持那段时期，虽然支持面比较广，现在看来还是学院派一些，一个是自身的支持方式，还是体验技术的认知去支持产品前端，思维和方式还是体验技术的那套路子，并没有完全去解读产品做一些对产品更有利的事情，大多还是关注技术方案的落地和转化。一个是产品自身其实也不一定是真实场景，尤其是一些技术中台，可能很难捕捉真实诉求，和外面商业产品的感觉不一样。&lt;/p&gt;
&lt;p&gt;有个事情记忆比较深，饭桌上当时金融核心研发负责人开玩笑说，你们前端比较单纯，当时不服气什么叫单纯，现在想想哈哈他说的对。总想把业务当技术落地场景，全是书生气学院气。&lt;/p&gt;
&lt;p&gt;后面 OceanBase 即将成立独立子公司，当时正好是金融核心因为方式和结果我绩效 325（给的理由是，其他在快速发展，自己在原地踏步，原地踏步就是退步），御术问我是否愿意去 OceanBase，那边需要全职参与支持，感谢信任。想了几天以后我加入了 OceanBase，努力推动新业务方向。&lt;/p&gt;
&lt;h2 id=&quot;oceanbase-我的毕业礼&quot;&gt;OceanBase 我的毕业礼&lt;/h2&gt;
&lt;p&gt;刚去第一年真的如缕薄冰，2020 年 6 月，OceanBase 成立独立公司，产品开始快速发展。一方面欣喜业务发展，一方面于此时我们只有 2 位前端 + 3 位外包，前端效能面临极大挑战。于是我像侠客一样，一边根据优先级参与各类急需前端产品的开发，一边关注招聘和团队，终于在自己和体验技术团队的帮助下，完成了 5 前端 + 14 外包的前端阵型。当时主管评价是有责任感和大局观。对，其实很多事情其实可能和个人利益不一致，只是我愿意选择付出。&lt;/p&gt;
&lt;p&gt;后面随着云业务发展，我刚好就留在云业务，从项目治理，到产品灵活度，到规模化，一点一滴的协助产品成型和发展。也为数马前端 10+ 团队的成型付出了大量的时间和精力。&lt;/p&gt;
&lt;p&gt;技术方案上主要还是数据库控制台业务组件库的成型，当时面临环境是需求很散乱，PD也经常换，后端接口不一致，前端控制台已经重复了很多套。我的解读是，需求是一种资源，场景也是一种资源，不加利用就会成为低成本，但是合理利用就是收益，大量需求肯定能汇聚到一些能力和方案，多个场景正好可以做方案打磨，稳定一致的组件，也能降低工程成本，为后续后端接口提供一致性参考，为后续开源做一些支持。所以虽然看上去平平无奇，其实自己已经有了转变，不是去套模板或者搞个方案晋升，而是想着怎么去做更有效的方案，解决实际问题，还有在当前自身和周边团队找到最优解。有没有方案能解决产品问题，还能有利于合作团队发挥，也有利于技术团队自身专业性提升？技术不是单一的，它一定在一个时间空间发挥作用，要看到是在人和事的发展空间去找最优解。&lt;/p&gt;
&lt;p&gt;其实过程中也会发现，虽然数据库，控制台，是个很陌生的场景，但是 ToB 场景自有规律，需求会教你，当时做到在控制台领域，可以基本适配不同产品规格，使用上在运维、监控、诊断都有对应白屏化方案，虽然不够精细，但是有场景有规模，方案加以打磨我觉得还是会做出这个场景的专业性的。在过程中你甚至可以感受到 ToB 功能产品树发展演化的规律和欣喜，在规律感和解读透彻的一种悟到的欣喜。&lt;/p&gt;
&lt;p&gt;在产品团队做技术，最主要就是我觉得是结果导向有效性。当时有个创业CTO正好也听了大厂前端分享，他说，在坐各位都是前端大牛，我听了真的受益匪浅，不过我也说实话，创业公司用你们方案，可能会死而且死的很快。确实，后面我也查阅了一些公司内部技术，很多我觉得非常宏伟，但是很多可能在解决一些内部的问题，市场和商业化看未必灵活。当然也不是不关注技术，我印象很深的例子来自苹果电脑早期，苹果电脑用户使用，从来不看内部的，但其实他们内部排线设计都很整齐，早起还签署了工程师名字。我相信技术还是一种服务方式而不是目的本身，产品和服务才是重要的。但是，好的产品和服务背后技术一定不会差。技术会因为提供的服务而做得更专业和极致。在商业化时期其实蚂蚁内部很多技术我们也在思考是不是真的合适商业产品，因为蚂蚁前端主要基因也是中后台，市场商业化技术早有不同。不过基于发展和依赖，这个推动会比较缓慢，这个命题可能需要越来越关注。&lt;/p&gt;
&lt;p&gt;正如很多电影有起承转合，艺术也是源于生活，人很多时候很难改变环境，也不用和不讲道理的人讲道理。所以在离开和留下当 IC 的二选一，我选择离开。世间的事情本来就复杂，很多事情不能以公平来衡量，世界的运行也不是以个人转移。当理解这些，可能才更成熟一些。&lt;/p&gt;
&lt;p&gt;我离开几乎所有人都很诧异不相信，不过事已至此。有人问是不是工作十几年离开是不是惋惜，我觉得不是。我心里的蚂蚁还是那个为世界带来微小而美好改变的蚂蚁，心里还是那个务实，简单的蚂蚁。如果一个环境不是这样了，那它可能不是蚂蚁。我认为自己的离开其实是一种回归。有人说你之前管理其实挺好的，我说现在你看其实不是管理水平问题，是责任驱动。如今担子终于卸下了，当时接的 OceanBase 虎符终于归还了，无责一身轻。&lt;/p&gt;
&lt;h2 id=&quot;再见蚂蚁十年淘宝三年阿里十三年&quot;&gt;再见，蚂蚁十年，淘宝三年，阿里十三年&lt;/h2&gt;
&lt;p&gt;后来离职盒子上面，真的是这一段话，&lt;/p&gt;
&lt;p&gt;“奔赴山海”&lt;/p&gt;
&lt;p&gt;“为世界带来微小而美好的改变”&lt;/p&gt;
&lt;p&gt;我还存着当时蚂蚁森林的树种，我也保留了苏东坡版本的蚂蚁公仔。我觉得它们很亲切。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/1/ant-family.jpeg&quot; alt=&quot;ant-family&quot;&gt;&lt;/p&gt;
&lt;p&gt;“桂棹兮兰桨，击空明兮溯流光。渺渺兮予怀，望美人兮天一方”&lt;/p&gt;
&lt;p&gt;一个故事的结束，是另一个故事的开始。&lt;/p&gt;
&lt;p&gt;再见蚂蚁，再见阿里！&lt;/p&gt;
&lt;p&gt;你好，未来！&lt;/p&gt;</content:encoded></item></channel></rss>