如何在Remix SSR中处理IndexedDB?现有方案是否最优?
Remix + Dexie/IndexedDB 服务端渲染问题解决方案
问题回顾
你在开发Remix应用时,使用Dexie及dexie-react-hooks基于IndexedDB存储本地数据,但因服务端环境不存在IndexedDB,遇到以下问题:
- 使用
database.ts文件名时触发Dexie.MissingAPIError,推测需服务端polyfill - 改为
database.client.ts后,初始服务端渲染阶段因db未定义报错 - 给
db加可选链后虽能运行,但感觉是临时解决方案
当前方案的合理性
用database.client.ts加可选链的方案能解决报错,但确实是权宜之计:
- 优点:快速修复,无需额外依赖
- 缺点:代码中频繁使用
?.不够优雅,服务端渲染时会输出空状态,客户端hydration后数据突然加载,可能出现页面闪烁或hydration不匹配警告
更优解决方案
1. 服务端polyfill IndexedDB
用fake-indexeddb库在服务端模拟IndexedDB环境,可保留database.ts命名,避免服务端报错。
实现步骤:
- 安装依赖:
npm install fake-indexeddb --save-dev - 在Remix的
entry.server.tsx顶部引入polyfill:
import "fake-indexeddb/auto"; import { createRequestHandler } from "@remix-run/node"; import * as build from "@remix-run/dev/server-build"; export default createRequestHandler({ build, mode: process.env.NODE_ENV, });
- 正常使用
database.ts即可:服务端用模拟的IndexedDB避免报错,客户端自动使用真实的浏览器实现
注意:服务端的模拟实现仅用于避免报错,无法存储或读取真实数据,初始渲染仍为空状态,客户端hydration后才会加载本地数据。
2. 客户端条件渲染+自定义钩子
明确区分服务端和客户端渲染逻辑,避免在服务端直接访问db。
实现方式:
- 用
remix-utils的useIsClient钩子(或自行判断typeof window !== 'undefined'),仅在客户端加载数据:
import { useIsClient } from "remix-utils"; import { useLiveQuery } from "dexie-react-hooks"; import db from "~/database.client"; export default function ItemList() { const isClient = useIsClient(); // 服务端返回空数组,客户端用LiveQuery加载数据 const items = isClient ? useLiveQuery(() => db.items.toArray()) : []; // 服务端渲染时展示加载状态 if (!isClient) { return <div>加载中...</div>; } return ( <ul> {items.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul> ); }
- 封装自定义钩子简化组件代码:
// app/hooks/useDbItems.ts import { useIsClient } from "remix-utils"; import { useLiveQuery } from "dexie-react-hooks"; import db from "~/database.client"; export function useDbItems() { const isClient = useIsClient(); return isClient ? useLiveQuery(() => db.items.toArray()) : []; }
组件中直接使用:
import { useDbItems } from "~/hooks/useDbItems"; export default function ItemList() { const items = useDbItems(); const isClient = useIsClient(); if (!isClient) return <div>加载中...</div>; return <ul>{items.map(item => <li key={item.id}>{item.name}</li>)}</ul>; }
方案选择建议
- 若希望保持
database.ts命名、代码结构更统一,选服务端polyfill方案 - 若想明确区分服务端/客户端逻辑、避免hydration问题,选客户端条件渲染+自定义钩子方案
内容的提问来源于stack exchange,提问作者Aaron Dale
相关产品推荐
相关产品推荐

