react-admin 离线优先(Offline-First)实战:基于 vite-plugin-pwa 与 TanStack Query 持久化的 ra-offline 示例全解
前端UI组件【免费下载链接】react-adminA frontend Framework for single-page applications on top of REST/GraphQL APIs, using TypeScript, React and Material Design项目地址https://gitcode.com/gh_mirrors/re/react-admin点击查看免费下载导读本文以 react-admin 官方仓库中的examples/ra-offline示例应用为主线完整讲解如何为基于 REST/GraphQL API 的 react-admin 单页应用构建离线优先offline-first能力。文章将带你理解两条核心技术路径——用vite-plugin-pwa将应用编译为可离线加载的 Progressive Web App以及通过配置 TanStack Query 的 QueryClient 把查询缓存与待处理的 mutation 持久化到 localStorage从而在网络中断时仍能浏览数据、提交变更并在恢复联网后自动重放。读完本文你将掌握一个可复制的离线方案npm install安装依赖、npm run build npm run preview运行生产构建、用浏览器 DevTools 或手机飞行模式验证离线行为并理解底层源码addOfflineSupportToQueryClient、Service Worker 注入等的工作原理与适用范围。一、示例概览一个演示 offline-first 能力的完整应用examples/ra-offline是 react-admin 官方仓库中专门用于演示离线优先能力的示例应用见 examples/ra-offline/README.md。它做了两件关键的事情使用vite-plugin-pwa将应用构造成一个 Progressive Web AppPWA使应用外壳HTML、JS、CSS、图标等静态资源可以被 Service Worker 预缓存从而在离线环境下也能加载配置 TanStack Query 的QueryClient将查询缓存query cache和待处理的 mutationpending mutations持久化到本地存储localStorage中实现断网可读、断网可写、恢复后重放的离线体验。该示例在功能上是一个极简的博客管理后台只有一个posts资源列表使用ListGuesser自动推断列编辑页使用自定义的PostEdit见 src/App.tsx 与 src/PostEdit.tsx。示例的数据源指向 JSONPlaceholder 这类公共假 REST API因此它更适合作为架构验证与行为演示而非真实数据持久化方案这一点会在局限性一节展开。从目录结构看应用由以下几部分组成src/App.tsx应用入口QueryClient离线配置、持久化 provider 与Admin组件的组装src/sw.ts自定义 Service Worker配合injectManifest策略负责预缓存与离线路由回退src/PostEdit.tsxpost 编辑表单包含ReferenceInput、TextInput等表单控件src/Layout.tsx自定义布局在 react-admin 默认布局上追加 TanStack Query DevToolssrc/index.tsxReact 根节点渲染入口vite.config.tsVite 构建配置含VitePWA插件与 monorepo 内packages/*源码别名public/PWA 所需的图标pwa-192x192.png、pwa-512x512.png、maskable-icon-512x512.png、apple-touch-icon-180x180.png、favicon.ico。二、安装与运行构建生产版本是离线能力生效的前提按照 README 的说明首先安装依赖npm install需要特别注意的是离线模式在 dev 模式下不生效这是vite-plugin-pwa的已知限制。因此必须构建生产版本并用本地服务器预览npm run build npm run preview从 package.json 可以看到构建与预览分别对应 Vite 的vite build和vite preview脚本。生产构建会把 Service Worker 注入到dist产物中浏览器首次访问时完成资源预缓存之后刷新即可在断网环境下加载应用。补充说明虽然vite.config.ts的VitePWA({ devOptions: { enabled: true, type: module, navigateFallback: index.html } })开启了开发模式的 Service Worker 支持但正如 README 明确指出的离线能力仍需以生产构建为验证基线日常调试请直接使用build preview的组合。验证离线行为时可以使用浏览器 DevToolsNetwork 面板勾选 Offline或手机开启飞行模式来模拟断网。README 特别提醒不要试图用 TanStack Query DevTools 来模拟离线——它并不具备这一能力。同时该示例已在 src/Layout.tsx 中通过ReactQueryDevtools initialIsOpen{false} /嵌入了 Query DevTools便于观察缓存状态但离线模拟请一律使用浏览器自身的网络控制。三、离线能力第一支柱vite-plugin-pwa 与自定义 Service Worker3.1 PWA 插件配置解读离线加载应用外壳的能力由VitePWA插件提供vite.config.ts 第 30–70 行关键配置如下registerType: autoUpdateService Worker 更新后自动接管页面用户无需手动刷新确认strategies: injectManifest使用自定义 Service Worker 文件而非 Workbox 生成的默认逻辑配合srcDir: src与filename: sw.ts指定自定义 SW 源码位置injectManifest: { minify: false }关闭注入产物压缩便于调试时阅读生成后的 SW 代码注释中还保留了enableWorkboxModulesLogs的开关样例devOptions: { enabled: true, type: module, navigateFallback: index.html }开发模式启用模块类型 SW并将路由导航回退到index.htmlworkbox: { globPatterns: [**/*.{js,css,html,ico,png,svg}] }预缓存构建产物中的静态资源类型includeAssets额外把favicon.ico、apple-touch-icon-180x180.png、maskable-icon-512x512.png一并预缓存manifest声明 PWA 应用名React-Admin Offline、短名RA Offline、主题色#ffffff以及 192x192 与 512x512 两种尺寸的图标来自public/下的pwa-192x192.png与pwa-512x512.png。index.htmlexamples/ra-offline/index.html中对应的link relapple-touch-icon、link relmask-icon与theme-color元信息与 manifest 中的图标、主题色声明保持了一致共同构成 PWA 的安装与展示要素。3.2 自定义 Service Worker 的离线逻辑injectManifest策略下src/sw.tsexamples/ra-offline/src/sw.ts是应用离线行为的核心import { cleanupOutdatedCaches, createHandlerBoundToURL, precacheAndRoute } from workbox-precaching; import { NavigationRoute, registerRoute } from workbox-routing; declare let self: ServiceWorkerGlobalScope; self.addEventListener(message, (event) { if (event.data event.data.type SKIP_WAITING) self.skipWaiting(); }); // self.__WB_MANIFEST is default injection point precacheAndRoute(self.__WB_MANIFEST); // clean old assets cleanupOutdatedCaches(); // to allow work offline registerRoute(new NavigationRoute(createHandlerBoundToURL(index.html)));其工作方式可以拆解为三点预缓存self.__WB_MANIFEST是 Workbox 注入清单的默认注入点构建时会被替换为需要预缓存的资源列表precacheAndRoute同时完成安装阶段的资源预缓存与请求阶段的缓存命中分发清理旧缓存cleanupOutdatedCaches()会在 SW 更新时删除不属于当前清单的过期缓存避免磁盘膨胀导航路由回退NavigationRoute(createHandlerBoundToURL(index.html))将一切页面导航请求HTML 文档请求回退到index.html。这是 SPA 离线运行的关键——用户无论访问哪个前端路由都能加载应用外壳再由 react-router/react-admin 接管页面渲染自动更新监听SKIP_WAITING消息并调用self.skipWaiting()配合registerType: autoUpdate实现新版本 SW 的自动接管。四、离线能力第二支柱TanStack Query 缓存与 mutation 的本地持久化如果只预缓存了静态资源应用虽能离线打开但数据请求与保存操作依然会因网络失败而报错。ra-offline 的第二个支柱是把 TanStack Query 这一数据层搬到本地。4.1 QueryClient 的离线语义配置在 src/App.tsx 中首先创建了一个带有离线语义的QueryClientconst baseQueryClient new QueryClient({ defaultOptions: { queries: { gcTime: 1000 * 60 * 60 * 24, // 24 hours networkMode: offlineFirst, }, }, });gcTime: 1000 * 60 * 60 * 2424 小时缓存的数据在内存/存储中被垃圾回收前保留一天保证离线期间刷新页面后查询缓存仍可恢复networkMode: offlineFirst这是 TanStack Query 5 提供的网络模式允许查询在无法建立网络连接时先读取本地缓存离线优先而不是直接判失败对于需要写入的操作mutation则进入暂停paused状态等待网络恢复。4.2 为指定资源的 mutation 注册默认函数随后调用 react-admin 5 提供的辅助函数把离线支持挂到指定资源上export const queryClient addOfflineSupportToQueryClient({ queryClient: baseQueryClient, dataProvider, resources: [posts, comments, users], });从 packages/ra-core/src/dataProvider/addOfflineSupportToQueryClient.ts 的实现可以看到它的作用是为每个资源注册 react-admin 默认 mutation 的默认执行函数export const addOfflineSupportToQueryClient ({ dataProvider, resources, queryClient, }: { dataProvider: DataProvider; resources: string[]; queryClient: QueryClient }) { resources.forEach(resource { DATAPROVIDER_MUTATIONS.forEach(mutation { queryClient.setMutationDefaults([resource, mutation], { mutationFn: async (params: any) { const dataProviderFn dataProvider[mutation] as Function; return dataProviderFn.apply(dataProviderFn, ...params); }, }); }); }); return queryClient; };其中DATAPROVIDER_MUTATIONS在 packages/ra-core/src/dataProvider/dataFetchActions.ts 中定义即 react-admin 的五个写操作export const DATAPROVIDER_MUTATIONS [ create, delete, update, updateMany, deleteMany, ];为什么必须注册默认 mutation 函数react-query 官方指南指出要支持持久化离线 mutation例如用户在离线时触发了 mutation、随后离开触发该 mutation 的组件等网络恢复后仍要重放该操作QueryClient 必须知道该 mutation 对应的执行函数mutationFn。addOfflineSupportToQueryClient正是按[resource, mutation]这一组合键把这些默认函数预注册到 QueryClient 上这样即使组件已卸载暂停的 mutation 在恢复后依然能被正确重放。需要理解的一点是resources: [posts, comments, users]是哪些资源参与离线写支持的声明。示例虽然只渲染了posts资源但把数据源中存在的comments、users也一并纳入保证后续扩展资源时无需改动配置。4.3 持久化 provider 与恢复暂停的 mutation接下来把 QueryClient 的缓存与暂停的 mutation 落到 localStorageconst localStoragePersister createAsyncStoragePersister({ storage: window.localStorage, }); export const App () ( PersistQueryClientProvider client{queryClient} persistOptions{{ persister: localStoragePersister }} onSuccess{() { // resume mutations after initial restore from localStorage is successful queryClient.resumePausedMutations(); }} Admin layout{Layout} dataProvider{dataProvider} queryClient{queryClient} disableTelemetry Resource nameposts list{ListGuesser} edit{PostEdit} / /Admin /PersistQueryClientProvider );这一段组装了三层职责createAsyncStoragePersister({ storage: window.localStorage })来自tanstack/query-async-storage-persister负责把缓存序列化写入 localStoragePersistQueryClientProvider来自tanstack/react-query-persist-client在应用启动时从 localStorage 恢复 query 缓存与暂停的 mutation并持续把新缓存写回存储onSuccess回调只有首次从 localStorage 恢复成功后才调用queryClient.resumePausedMutations()把离线期间暂停的写操作重新派发给 dataProvider。这里恢复成功后再重放 mutation的顺序很重要必须先还原完整的缓存状态再重放写操作避免重放时依赖的上下文如关联记录尚未就绪。最后Admin组件通过queryClientprop 显式接收这个已配置好的 QueryClient确保 react-admin 内部的所有查询与变更都走同一份离线语义配置disableTelemetry则关闭了运行时遥测上报。4.4 编辑表单的离线友好设置在 src/PostEdit.tsx 中还有一个值得注意的细节Edit redirectOnError{false}redirectOnError{false}表示当保存请求失败例如离线时 mutation 处于暂停状态、或网络异常时不自动跳转回列表页而是停留在编辑页。这保证了离线编辑场景下的用户路径可控——失败不丢页面配合 mutation 的暂停机制等待网络恢复后自动重放。五、端到端验证如何确认离线方案确实生效在本地完成npm run build npm run preview后按以下步骤即可验证 offline-first 行为首次在线访问打开preview提供的地址等待 Service Worker 完成资源预缓存DevTools → Application → Service Workers 可确认 SW 已激活Cache Storage 中出现预缓存条目模拟断网打开 DevTools → Network 面板勾选 Offline或直接在手机上开启飞行模式并重新加载页面验证可读性断网状态下刷新页面应用外壳与已缓存的数据应正常渲染networkMode: offlineFirst保证查询优先命中本地缓存而非直接报错验证可写性离线状态下编辑一条 post 并保存。由于缓存与暂停的 mutation 已被持久化此时 mutation 会进入 paused 状态写入操作不会立即触发网络错误恢复联网关闭 DevTools 的 Offline或关闭飞行模式PersistQueryClientProvider.onSuccess与resumePausedMutations()会把暂停的写操作重新派发给 dataProvider在 Network 面板中可以看到这些请求随后被真正发出。README 对验证预期做了明确界定由于示例连接的是 JSONPlaceholder 这类公共假 API变更不会被真实持久化但可以在 Network 面板观察到 mutation 请求被发出并且不会触发错误——这正是离线排队、恢复重放行为的可观测证据。六、局限性说明与使用前提README 在最后明确了两条局限使用时必须心中有数开发模式不生效离线能力依赖生产构建产出的 Service Worker 与预缓存清单npm run dev下的体验并不代表真实离线行为请始终以build preview或部署后的站点为验证环境后端不持久化本示例的 dataProvider 指向 JSONPlaceholderjsonDataProvider(https://jsonplaceholder.typicode.com)见 src/App.tsx公共假 API 不会真的保存数据因此离线写入成功只体现在请求重放层面。若要在真实业务中落地 offline-first需要使用支持真实持久化的 dataProvider 与后端如自建 REST/GraphQL 服务合理选择资源清单resources数组与gcTime本示例为 24 小时权衡缓存体积与离线时长需求关注多标签页/多设备场景下的缓存一致性与冲突处理策略。七、把离线方案迁移到自己的 react-admin 项目结合本示例在你自己的项目中复刻 offline-first 的最小步骤是安装相关依赖对应 package.json 中的版本组合如react-admin、tanstack/react-query、tanstack/query-async-storage-persister、tanstack/react-query-persist-client、vite-plugin-pwa在vite.config.ts中引入VitePWA插件配置injectManifest策略与自定义sw.ts可直接参考 examples/ra-offline/vite.config.ts 与 examples/ra-offline/src/sw.ts并补齐public/下的 PWA 图标与index.html的元信息在应用入口组装QueryClientnetworkMode: offlineFirst 合理的gcTime→addOfflineSupportToQueryClient({ queryClient, dataProvider, resources })→createAsyncStoragePersister→PersistQueryClientProvider并在onSuccess中调用queryClient.resumePausedMutations()完整写法见 examples/ra-offline/src/App.tsx对需要失败不跳走的编辑/创建页面设置redirectOnError{false}以生产构建npm run build部署后用浏览器 Offline 模式与飞行模式完成端到端验证。这套方案的关键思想可以概括为一句话Service Worker 解决离线能不能打开应用TanStack Query 的缓存持久化与 mutation 重放解决离线能不能继续用数据、能不能延迟写入。两者结合才构成完整的 offline-first 体验。赞分享前端UI组件【免费下载链接】react-adminA frontend Framework for single-page applications on top of REST/GraphQL APIs, using TypeScript, React and Material Design项目地址https://gitcode.com/gh_mirrors/re/react-admin点击查看免费下载相关推荐使用 TanStack DB 与 RxDB 构建离线优先Offline-First应用使用 TanStack DB 与 RxDB 构建离线优先Offline First应用 TanStack DB 是内存态响应式客户端存储提供实时查询li数据库NoSQL嵌入式数据库实时数据库Gatsby 离线支持实战用 gatsby-plugin-offline 与 Service Worker 打造 PWAGatsby 离线支持实战用 gatsby plugin offline 与 Service Worker 打造 PWA 如果你用 Lighthouse 审计前端静态站点Web框架oh-my-hermes 审批层级完整指南高危操作的分级审批安全设计oh my hermes 审批层级完整指南高危操作的分级审批安全设计 oh my hermes 的审批层级approval tier是内置于这款 Herm人工智能AI 技能AI 插件AI 评测Agent 工作流创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

Fleet 4.5.0 发布深度解读:Team admin 团队角色、查询 OS 兼容性实时检测与查询性能影响指标

Fleet 4.5.0 发布深度解读:Team admin 团队角色、查询 OS 兼容性实时检测与查询性能影响指标

后端前端企业应用运维网络安全 【免费下载链接】fleet Open device management 项目地址: https://gitcode.com/GitHub_Trending/fl/fleet 点击查看 免费下载 Fleet 4.5.0 是面向 osquery 与 Fleet 社区的一次重要发布,核心亮点包括:新增 Te…

2026/9/22 11:08:48 阅读更多 →
ESP32-S3驱动MAX98357A无声的三大硬件隐性冲突

ESP32-S3驱动MAX98357A无声的三大硬件隐性冲突

1. 这块MAX98357A音频芯片,到底坑在哪?我第一次把MAX98357A焊到ESP32-S3开发板上时,心里是真踏实——毕竟这颗芯片在Arduino社区里被吹了快十年,号称“即插即用”“免配置”“I2S直连”,连立创实战派S3的配套教程里都把…

2026/9/22 11:08:48 阅读更多 →
3招解决错误未定义书签,搞定高频面试题

3招解决错误未定义书签,搞定高频面试题

3招解决错误未定义书签,搞定高频面试题 看了一堆教程还是不会写项目?别急,这通常是细节没抠到位。很多 高频面试题 背后,都藏着像“错误未定义书签”这样的基础坑。 性能瓶颈:为什么书签会拖慢你的应用…

2026/9/22 11:08:48 阅读更多 →

最新新闻

二次元情头污手写实现避坑指南

二次元情头污手写实现避坑指南

二次元情头污手写实现避坑指南 复制来的代码跑不通,报错满屏红字,连个调试入口都找不到。这种绝望感,每个搞技术的都懂。今天咱们不整虚的,直接上硬菜,聊聊怎么 手写实现 一套稳健的二次元情头污处理逻辑。 很多新手喜欢从 GitHub 或…

2026/9/22 12:29:20 阅读更多 →
学画画先学什么?3个代码坑教你搭项目保姆级教程

学画画先学什么?3个代码坑教你搭项目保姆级教程

学画画先学什么?3个代码坑教你搭项目保姆级教程 刚学完语法,对着空白的IDE发呆?这感觉太熟了。很多转行做开发的朋友,啃完了Python或Java的语法书,结果连个像样的小项目都跑不起来。别急,这篇 保姆级教程…

2026/9/22 12:29:20 阅读更多 →
董藩博客性能优化5招解决版本升级API全变痛点

董藩博客性能优化5招解决版本升级API全变痛点

董藩博客性能优化5招解决版本升级API全变痛点 昨天凌晨三点,服务器报警狂响,监控面板一片红。我盯着屏幕,发现刚上线的“董藩博客”新模块响应时间从 20ms 飙到了 2000ms+。更糟的是,底层依赖库刚做了大版本升级,原本熟悉的 API…

2026/9/22 12:29:20 阅读更多 →
3秒读懂n康泰图解原理性能优化实战

3秒读懂n康泰图解原理性能优化实战

3秒读懂n康泰图解原理性能优化实战 盯着屏幕上滚动的红色报错,脑子里一团浆糊?那种 StackTrace 像天书一样,一行行代码指着你鼻子骂,却找不到根源,这种痛苦每个写过 Java 或 Python…

2026/9/22 12:29:20 阅读更多 →
hr医学数据接口选型:3个框架对比,附完整示例与避坑指南

hr医学数据接口选型:3个框架对比,附完整示例与避坑指南

hr医学数据接口选型:3个框架对比,附完整示例与避坑指南 刚入行后端,是不是也常对着 Python 或 Java 的语法书发呆?API 文档背得滚瓜烂熟,真到 hr…

2026/9/22 12:29:20 阅读更多 →
STM32 ADC双模式:规则组与注入组的硬件调度本质

STM32 ADC双模式:规则组与注入组的硬件调度本质

1. 项目概述:为什么规则组与注入组的“双模共存”是STM32 ADC真正的分水岭你手头正调试一个基于STM32F407的电机电流采样系统,用规则组采集三相电流,一切正常;但突然需要在某个特定时刻——比如PWM死区时间结束的瞬间——精准捕获…

2026/9/22 12:28:19 阅读更多 →

日新闻

3台商务办公笔记本实测:手写实现环境配置,告别卡半天

3台商务办公笔记本实测:手写实现环境配置,告别卡半天

3台商务办公笔记本实测:手写实现环境配置,告别卡半天 配置环境就卡半天?别怪机器慢,多半是你没选对工具链。在Java、Go或Python的项目现场, 手写实现…

2026/9/22 0:00:41 阅读更多 →
剑帝加点速查手册:3分钟搞懂核心逻辑

剑帝加点速查手册:3分钟搞懂核心逻辑

剑帝加点速查手册:3分钟搞懂核心逻辑 面试被问原理答不上来,是不是常态?别慌。很多开发者对着 GitHub 开源仓库里的代码发呆,看似简单实则暗藏玄机。今天这份【剑帝加点】速查手册,直接带你拆解核心实现,把面试必考的原理讲透。…

2026/9/22 0:00:41 阅读更多 →
手写实现图片压缩网站核心:搞定WebP转换与质量调优

手写实现图片压缩网站核心:搞定WebP转换与质量调优

手写实现图片压缩网站核心:搞定WebP转换与质量调优 复制来的代码跑不通不知道怎么调?别慌,这种“复制粘贴地狱”在开发圈太常见了。尤其是做 图片压缩网站…

2026/9/22 0:00:41 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/22 4:32:41 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/22 4:38:57 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/22 8:51:04 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/21 15:36:51 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/21 15:36:51 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/22 2:43:42 阅读更多 →