[{"data":1,"prerenderedAt":112},["ShallowReactive",2],{"post-\u002Fposts\u002Flocal-first-notes":3,"surround-\u002Fposts\u002Flocal-first-notes":103},{"id":4,"title":5,"body":6,"cover":88,"date":89,"description":90,"draft":91,"extension":92,"meta":93,"minutes":84,"navigation":94,"path":95,"seo":96,"stem":97,"tags":98,"updated":101,"__hash__":102},"posts\u002Fposts\u002Flocal-first-notes.md","本地优先软件笔记：数据先是你的，然后才是云的",{"type":7,"value":8,"toc":79},"minimark",[9,13,18,21,27,30,33,36,49,52,55,58,61,64,76],[10,11,12],"p",{},"十几年来我们默认接受了一件奇怪的事：想打开自己写的文档，得先问过别人家的服务器。本地优先（local-first）这个词给出的答案很直接，把事实源搬回用户设备，让云降级成可选的同步通道。",[14,15,17],"h2",{"id":16},"七条理想一条主线","七条理想，一条主线",[10,19,20],{},"Ink & Switch 那篇著名的论文列了七条理想属性：快、多端、离线可用、协作、长期可用、隐私、用户掌控。七条背后其实是同一条主线：",[22,23,24],"blockquote",{},[10,25,26],{},"软件的可用性不应该依赖任何一台不属于用户的机器。",[10,28,29],{},"拿这条主线去审视手头的工具，结果往往难看。断网之后还能正常工作的应用，一只手数得过来。",[14,31,32],{"id":32},"架构上的翻转",[10,34,35],{},"传统云应用的读写路径是「客户端请求服务器，服务器返回真相」。本地优先把它翻转过来：",[37,38,39,43,46],"ol",{},[40,41,42],"li",{},"所有读写先落在本地存储，界面永远即时响应",[40,44,45],{},"后台任务把变更同步出去，网络状态只影响同步进度",[40,47,48],{},"冲突由数据结构自己消化，而不是弹窗让用户二选一",[10,50,51],{},"第三条是技术上最难的部分，也是 CRDT 这些年从论文走向工程的原因：把「并发修改如何合并」的规则编码进数据类型本身，合并结果与操作顺序无关。",[14,53,54],{"id":54},"博客也算一种本地优先",[10,56,57],{},"这个站点本身就是一次小规模实践。文章是我磁盘上的 Markdown 文件，Git 仓库是完整的历史，任何托管商消失都不影响我拥有全部数据。构建产物是纯静态文件，换一家托管只是改一个部署目标。",[10,59,60],{},"云在这套流程里的角色被压缩到最小：一个远端备份，一个分发节点。它很有用，但它不是主人。",[14,62,63],{"id":63},"值得追踪的方向",[65,66,67,70,73],"ul",{},[40,68,69],{},"浏览器端持久化能力的演进，OPFS 已经够用了",[40,71,72],{},"CRDT 库的工程化成熟度，尤其是文本与富文本类型",[40,74,75],{},"端到端加密与同步服务的组合方式",[10,77,78],{},"本地优先不是复古，而是把三十年前个人计算的默认假设，用今天的同步技术重新实现一遍。",{"title":80,"searchDepth":81,"depth":81,"links":82},"",3,[83,85,86,87],{"id":16,"depth":84,"text":17},2,{"id":32,"depth":84,"text":32},{"id":54,"depth":84,"text":54},{"id":63,"depth":84,"text":63},"https:\u002F\u002Fpicsum.photos\u002Fseed\u002Fmoonstream-harbor\u002F1600\u002F1000","2026-02-14","云应用把「打开自己的文件」变成了需要网络许可的操作。本地优先是对这件事的系统性纠偏：本地是事实源，同步只是增强。这篇是我的阅读与实践笔记。",false,"md",{},true,"\u002Fposts\u002Flocal-first-notes",{"title":5,"description":90},"posts\u002Flocal-first-notes",[99,100],"本地优先","架构",null,"Py5PjHZHACRWZ1VEsVWpJu3451_t7F2ZiGbbpindJLo",[104,108],{"title":105,"path":106,"stem":107,"children":-1},"satisfies、as const 与类型收窄的日常","\u002Fposts\u002Ftypescript-satisfies-in-practice","posts\u002Ftypescript-satisfies-in-practice",{"title":109,"path":110,"stem":111,"children":-1},"开站：为什么是 Moonstream","\u002Fposts\u002Fhello-moonstream","posts\u002Fhello-moonstream",1786707084540]