<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>SSR | 锤子前端</title><description>Now vibe coding, so learning hammer FE ?</description><link>https://channel.meo-ai.com</link><item><title>《修复 React 中的 Hydration Mismatch 错误与 UI 闪烁》</title><link>https://channel.meo-ai.com/posts/731</link><guid isPermaLink="true">https://channel.meo-ai.com/posts/731</guid><pubDate>Thu, 29 Oct 2026 22:20:12 GMT</pubDate><content:encoded>&lt;span class=&quot;post-title-source&quot;&gt;《修复 React 中的 Hydration Mismatch 错误与 UI 闪烁》&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;标签：&lt;br /&gt;&lt;a href=&quot;/search/result?q=%23%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91&quot; title=&quot;#前端开发&quot;&gt;#前端开发&lt;/a&gt; &lt;a href=&quot;/search/result?q=%23NextJS&quot; title=&quot;#NextJS&quot;&gt;#NextJS&lt;/a&gt; &lt;a href=&quot;/search/result?q=%23HydrationMismatch&quot; title=&quot;#HydrationMismatch&quot;&gt;#HydrationMismatch&lt;/a&gt; &lt;a href=&quot;/search/result?q=%23LocalStorage&quot; title=&quot;#LocalStorage&quot;&gt;#LocalStorage&lt;/a&gt; &lt;a href=&quot;/search/result?q=%23SSR&quot; title=&quot;#SSR&quot;&gt;#SSR&lt;/a&gt; &lt;a href=&quot;/search/result?q=%23UI%E9%97%AA%E7%83%81&quot; title=&quot;#UI闪烁&quot;&gt;#UI闪烁&lt;/a&gt; &lt;a href=&quot;/search/result?q=%23React19&quot; title=&quot;#React19&quot;&gt;#React19&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;总结：&lt;br /&gt;文章以 Next.js 全栈电影应用为例，系统讲解了服务端渲染（&lt;mark class=&quot;highlight&quot;&gt;SSR&lt;/mark&gt;）中 Hydration Mismatch 错误的成因与多种修复方案：包括挂载前渲染 null/骨架屏、用 &lt;code&gt;useEffect&lt;/code&gt; 读取 localStorage 保持 &lt;mark class=&quot;highlight&quot;&gt;SSR&lt;/mark&gt;、用内联脚本在 hydration 前同步状态以避免闪烁，以及 React 19.3 新增的 &lt;code&gt;use(browser())&lt;/code&gt; API。&lt;br /&gt;&lt;br /&gt;文章要点：&lt;br /&gt;1. Hydration 是水合过程：React 用 &lt;mark class=&quot;highlight&quot;&gt;SSR&lt;/mark&gt; 时的同一组件树在客户端内存中重新渲染，并校验其输出与服务端 HTML 是否一致，不一致就报 Hydration Mismatch 错误。&lt;br /&gt;2. 错误常见诱因是渲染依赖了仅客户端可用的信息，比如 localStorage——服务端拿不到，导致两端输出不同。&lt;br /&gt;3. 通用解法一：&lt;code&gt;useEffect&lt;/code&gt; 中设置 &lt;code&gt;isClient&lt;/code&gt;，挂载前返回 &lt;code&gt;null&lt;/code&gt; 或骨架屏，保证两端输出一致；代价是真实内容不进服务端 HTML，对 SEO 和大型布局不友好。&lt;br /&gt;4. 通用解法二：给元素加 &lt;code&gt;suppressHydrationWarning&lt;/code&gt;，但它只压警告不修问题，且仅一层有效，不宜滥用。&lt;br /&gt;5. 在侧边栏场景中，作者改用 &lt;code&gt;useEffect&lt;/code&gt; 里读 localStorage 并 &lt;code&gt;setState&lt;/code&gt;，先用默认值 &lt;code&gt;false&lt;/code&gt; 完成 &lt;mark class=&quot;highlight&quot;&gt;SSR&lt;/mark&gt;，挂载后再更新状态，既保留 &lt;mark class=&quot;highlight&quot;&gt;SSR&lt;/mark&gt; 又消除不匹配（但会有短暂闪烁）。&lt;br /&gt;6. 防闪烁的进阶方案：用 &lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt; 注入内联脚本，在 hydration 前直接把 localStorage 状态应用到 DOM，并把值挂到 &lt;code&gt;window&lt;/code&gt; 上供 React 初始渲染读取，保证脚本和 React 修改同一批 class。&lt;br /&gt;7. 主题切换用 &lt;code&gt;useIsClient&lt;/code&gt; 延迟渲染（官方推荐做法，小控件可接受）；移动端遮罩层则用 CSS &lt;code&gt;group-[.left-0]:block&lt;/code&gt; 跟随侧边栏 class，避免条件渲染造成的不匹配。&lt;br /&gt;8. React 19.3 引入的 &lt;code&gt;use(browser())&lt;/code&gt; 可以跳过组件的服务端渲染并留下最近的 &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt; 兜底，是 &lt;code&gt;useEffect&lt;/code&gt; 方案的替代写法。&lt;br /&gt;&lt;br /&gt;URL：&lt;a href=&quot;https://www.gmoniava.com/blog/hydration-mismatch-errors-and-how-to-fix-them&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; title=&quot;https://www.gmoniava.com/blog/hydration-mismatch-errors-and-how-to-fix-them&quot;&gt;https://www.gmoniava.com/blog/hydration-mismatch-errors-and-how-to-fix-them&lt;/a&gt;&lt;a class=&quot;tgme_widget_message_link_preview&quot; href=&quot;https://gmoniava.com/blog/hydration-mismatch-errors-and-how-to-fix-them&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; title=&quot;Understanding hydration mismatch errors and what to do about them.&quot;&gt;
  
  &lt;div class=&quot;link_preview_site_name accent_color&quot;&gt;Gmoniava&lt;/div&gt;
  &lt;img class=&quot;link_preview_image&quot; alt=&quot;What Is a Hydration Mismatch Error and How Do You Fix It?&quot; src=&quot;/static/https://cdn4.telesco.pe/file/qmMf-k8xSJajmqkr2p5SzyaN5xBif4nPyK_mUjzV2mOGaho7_JF3xRmgxC71y0tLeeaSA8kVb5iWz6GciEdPPZ2gzV5629YN-uDIIDgIfJHeWvSvDaQA1pTvfcjOaIMTyHpV5vg8DDMtwGyXQXFq8V1RCIvhevyFPEHWNguZZki6BexABOUUsb7iYBfkfvpjAyppwgmOr6oSwfVs_TYbeVyk4uzEn-QbSAHGwpkoSOW7sIoG0oTD30GoXDhxFoIJO8toAOlVvppiELv3IsIgGekpDQj3nFJq9qNVdaSHWXUH4R1SJydZDSG9J9MSFRpiTXqNb_CiiONmx9NkEXQ3ig.jpg&quot; width=&quot;1200&quot; height=&quot;630&quot; loading=&quot;eager&quot; /&gt;
  &lt;div class=&quot;link_preview_title&quot;&gt;What Is a Hydration Mismatch Error and How Do You Fix It?&lt;/div&gt;
  &lt;div class=&quot;link_preview_description&quot;&gt;Understanding hydration mismatch errors and what to do about them.&lt;/div&gt;
&lt;/a&gt;</content:encoded></item><item><title>《水合不匹配的隐藏代价：一个错误如何让LCP从绿变红》</title><link>https://channel.meo-ai.com/posts/671</link><guid isPermaLink="true">https://channel.meo-ai.com/posts/671</guid><pubDate>Mon, 06 Jul 2026 01:22:00 GMT</pubDate><content:encoded>&lt;span class=&quot;post-title-source&quot;&gt;《水合不匹配的隐藏代价：一个错误如何让LCP从绿变红》&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;标签：&lt;a href=&quot;/search/result?q=%23%E5%89%8D%E7%AB%AF&quot; title=&quot;#前端&quot;&gt;#前端&lt;/a&gt; &lt;a href=&quot;/search/result?q=%23React&quot; title=&quot;#React&quot;&gt;#React&lt;/a&gt; &lt;a href=&quot;/search/result?q=%23HydrationMismatch&quot; title=&quot;#HydrationMismatch&quot;&gt;#HydrationMismatch&lt;/a&gt; &lt;a href=&quot;/search/result?q=%23LCP&quot; title=&quot;#LCP&quot;&gt;#LCP&lt;/a&gt; &lt;a href=&quot;/search/result?q=%23CoreWebVitals&quot; title=&quot;#CoreWebVitals&quot;&gt;#CoreWebVitals&lt;/a&gt; &lt;a href=&quot;/search/result?q=%23SSR&quot; title=&quot;#SSR&quot;&gt;#SSR&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;总结：&lt;br /&gt;&lt;br /&gt;文章揭示了React SSR中一个被严重低估的性能杀手：单个水合不匹配（Hydration Mismatch）就能让LCP从绿色直接跌到红色。核心逻辑由三个事实拼接而成——水合不匹配会触发整个DOM重新挂载；使用&lt;code&gt;font-display: swap&lt;/code&gt;时，Web字体加载后文本会膨胀；而LCP只测量新加入DOM的元素，忽略已有元素的尺寸变化。三者叠加后，字体加载后的文本膨胀本应不影响LCP，但DOM重挂载让浏览器将其视为&quot;新元素&quot;，导致LCP时间被记录到水合完成时（通常5秒以上），严重拖垮核心指标。&lt;br /&gt;&lt;br /&gt;文章要点：&lt;br /&gt;&lt;br /&gt;1. 水合不匹配会强制重挂载整个DOM：当服务端和客户端渲染结果不一致时，React不会局部修补，而是找到最近的&lt;code&gt;Suspense&lt;/code&gt;边界并重新挂载整个子树；没有边界时整页重挂&lt;br /&gt;2. 字体加载导致文本膨胀是正常现象：使用&lt;code&gt;font-display: swap&lt;/code&gt;时，系统字体切换到Web字体的过程中，相同字符的物理尺寸往往不同，文本会轻微变大或变小&lt;br /&gt;3. LCP只关心&quot;新元素&quot;：浏览器记录LCP候选者时，仅在新元素加入DOM时重新评估；已有元素单纯尺寸变化不会触发更新，这是设计上的关键细节&lt;br /&gt;4. 三者叠加就是灾难：文本先因字体加载膨胀，再因水合不匹配被删除并重新插入DOM，浏览器将其识别为&quot;更大的新元素&quot;，LCP时间瞬间跳到水合完成时刻&lt;br /&gt;5. 修复方案很直接：优先避免水合不匹配；如果实在无法避免，将不匹配元素包裹在&lt;code&gt;Suspense&lt;/code&gt;边界内，限制重挂载范围，防止LCP候选元素被波及&lt;br /&gt;&lt;br /&gt;URL：&lt;a href=&quot;https://3perf.com/blog/hydration-mismatch/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; title=&quot;https://3perf.com/blog/hydration-mismatch/&quot;&gt;https://3perf.com/blog/hydration-mismatch/&lt;/a&gt;&lt;a class=&quot;tgme_widget_message_link_preview&quot; href=&quot;https://3perf.com/blog/hydration-mismatch&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; title=&quot;A single hydration mismatch on a page can take Largest Contentful Paint from green to red&quot;&gt;
  
  &lt;div class=&quot;link_preview_site_name accent_color&quot;&gt;PerfPerfPerf&lt;/div&gt;
  &lt;img class=&quot;link_preview_image&quot; alt=&quot;Hidden Cost of Hydration Mismatches&quot; src=&quot;/static/https://cdn4.telesco.pe/file/aTL3DTn_Dpb9ZSSAJ2dA9PvD1Gam9D5nCr6VwlE2tLEaGYxqFYH4l5SsbMU_9IIZROWlB-JKoqIvnBHjZBB-Ml6jwLAbseR_Nfr6owi9ZvCvjhxttbeG4WFfq1SZl3K4fQFqvWGhmUS66WkXTRSrt1wcJ5l2NDWOdeV2ON7ftALDQQ_DE-J0VvdstgzQH5_lkliVTHijgBTqABKbb_P5nLt-M3lBIU8R1wO0uLPvZ3_XKg7JVFz_slvjZYS2cZ8GWJKup36jSVrklb3lxYPVLqUFXmx3eOosyqHo8lEXHkGyUAjQxcXw1zviFBFse9SY8QCG3TjUYPRbMkNGFbdo2w.jpg&quot; width=&quot;1200&quot; height=&quot;630&quot; loading=&quot;eager&quot; /&gt;
  &lt;div class=&quot;link_preview_title&quot;&gt;Hidden Cost of Hydration Mismatches&lt;/div&gt;
  &lt;div class=&quot;link_preview_description&quot;&gt;A single hydration mismatch on a page can take Largest Contentful Paint from green to red&lt;/div&gt;
&lt;/a&gt;</content:encoded></item><item><title>《TanStack Router 与 Query 的最佳实践》</title><link>https://channel.meo-ai.com/posts/647</link><guid isPermaLink="true">https://channel.meo-ai.com/posts/647</guid><pubDate>Fri, 29 May 2026 23:44:47 GMT</pubDate><content:encoded>&lt;span class=&quot;post-title-source&quot;&gt;《TanStack Router 与 Query 的最佳实践》&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;标签：&lt;a href=&quot;/search/result?q=%23%E5%89%8D%E7%AB%AF&quot; title=&quot;#前端&quot;&gt;#前端&lt;/a&gt; &lt;a href=&quot;/search/result?q=%23React&quot; title=&quot;#React&quot;&gt;#React&lt;/a&gt; &lt;a href=&quot;/search/result?q=%23TanStackQuery&quot; title=&quot;#TanStackQuery&quot;&gt;#TanStackQuery&lt;/a&gt; &lt;a href=&quot;/search/result?q=%23TanStackRouter&quot; title=&quot;#TanStackRouter&quot;&gt;#TanStackRouter&lt;/a&gt; &lt;a href=&quot;/search/result?q=%23%E6%95%B0%E6%8D%AE%E8%8E%B7%E5%8F%96&quot; title=&quot;#数据获取&quot;&gt;#数据获取&lt;/a&gt; &lt;a href=&quot;/search/result?q=%23SSR&quot; title=&quot;#SSR&quot;&gt;#SSR&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;总结：&lt;br /&gt;本文详解了 TanStack Router 与 TanStack Query 的集成方案，核心思路是将 Router 的 Loader 视为&quot;预取触发器&quot;，让 Query 接管全局缓存。通过关闭 Router 内置缓存、在 Loader 中预取 Query、组件中使用 useSuspenseQuery 或 useQuery 的组合策略，实现数据尽早获取、避免请求瀑布，同时兼容 &lt;mark class=&quot;highlight&quot;&gt;SSR&lt;/mark&gt; 流式渲染。作者强调始终使用 Query Hooks 而非 useLoaderData 获取数据，以维持自动重取、缓存失效和垃圾回收的正常运作。&lt;br /&gt;&lt;br /&gt;文章要点：&lt;br /&gt;1. Router 自带缓存仅限单路由，Query 缓存全局可跨路由共享，更适合多路由共用数据场景&lt;br /&gt;2. 在 Loader 中预取 Query 能让请求在组件渲染前甚至 JS 加载前就开始，配合 prefetch:&apos;intent&apos; 还能实现悬停预加载&lt;br /&gt;3. 关闭 Router 缓存（defaultPreloadStaleTime: 0）避免与 Query 缓存冲突，让 Query 独掌缓存策略&lt;br /&gt;4. 推荐用 useSuspenseQuery 配合 Router 的默认 Error/Pending 边界，组件只需专注&quot;阳光路径&quot;&lt;br /&gt;5. Loader 中不 await 更灵活：useSuspenseQuery 实现阻塞加载，useQuery 实现延迟加载，由组件自主决定&lt;br /&gt;6. &lt;mark class=&quot;highlight&quot;&gt;SSR&lt;/mark&gt; 场景下 useSuspenseQuery 更友好，支持流式渐进渲染；useQuery 需在 Loader 中 await 否则服务端无 markup&lt;br /&gt;7. 切勿用 useLoaderData 替代 Query Hooks，否则会导致自动重取、失效刷新和垃圾回收全部失效&lt;br /&gt;8. 将 Loader 视为&quot;事件处理器&quot;——只负责触发预取、不返回数据，是渐进优化性能的好心智模型&lt;br /&gt;&lt;br /&gt;URL：&lt;a href=&quot;https://tkdodo.eu/blog/tan-stack-router-and-query&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; title=&quot;https://tkdodo.eu/blog/tan-stack-router-and-query&quot;&gt;https://tkdodo.eu/blog/tan-stack-router-and-query&lt;/a&gt;&lt;a class=&quot;tgme_widget_message_link_preview&quot; href=&quot;https://tkdodo.eu/blog/tan-stack-router-and-query&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; title=&quot;TanStack Router has a built-in cache, so why would we need TanStack Query? There are, of course, reasons ...&quot;&gt;
  
  &lt;div class=&quot;link_preview_site_name accent_color&quot;&gt;tkdodo.eu&lt;/div&gt;
  &lt;img class=&quot;link_preview_image&quot; alt=&quot;TanStack Router and Query&quot; src=&quot;/static/https://cdn4.telesco.pe/file/btImzWDCsgmkqJ3KaJOgX-Bea9HEVbTjgGD2m7VH9P3_OJYSOfVsHCNhl8ZmvhLoLfhFHPs6oe89aM1Sf26XGRX4h6PPyhTBKSmcqHEjAHmLCCOrQGOsbjib7hzeVb7zfvX85tL0k7NpAaKMTLpeW2InaBPh1zwjO-XJcYjFFscQw4og0qBKMbbw6r_xd1kYbUooDKQgy2aobNQbxC2Y6oyiRNqjYuoQXygbXYITQLm35ff8C3lHvYfTpCih6PXkV-3bzp1MgVyttjjLC8MwGdmXklMsQ3UOU2KRUN6bK3XrRsKXlT_ivGlSS6mcEokiW3IepbCToSDo_i18-Eqr6g.jpg&quot; width=&quot;1200&quot; height=&quot;630&quot; loading=&quot;eager&quot; /&gt;
  &lt;div class=&quot;link_preview_title&quot;&gt;TanStack Router and Query&lt;/div&gt;
  &lt;div class=&quot;link_preview_description&quot;&gt;TanStack Router has a built-in cache, so why would we need TanStack Query? There are, of course, reasons ...&lt;/div&gt;
&lt;/a&gt;</content:encoded></item></channel></rss>