Fixing IndexedDB Initialization Errors with TanStack Router + Effect + Pglite
Fixing IndexedDB Initialization Errors with TanStack Router + Effect + Pglite
Table of Contents
Overview
Recently, I am doing a migration from fp-ts to Effect. My application needed to run local-first database migrations using Pglite, which support on IndexedDB, In Memory DB, File system. The problem only happen and must happen if not dealing properly using IndexedDB with SSR framework like Tanstack Start.
However, I ran into a critical issue when combining TanStack Router and Pglite in a server-rendered React app: the server-side code tried to initialize IndexedDB, finally after debugging I find out it is leading to a error like:
It turned out that attempting to use pglite
before the client was fully loaded caused these errors. Below, I’ll walk through the problem and my solution.
Background
The Tech Stack
- Effect: A library that improves on concepts from fp-ts, offering a clean way to manage side effects, concurrency, and error handling.
- TanStack Router: A powerful router for React that can optionally be used for server-side rendering (SSR).
- Pglite: A client-side database solution leveraging IndexedDB.
- Local-First Migrations: Following approaches like this guide, I needed to run migrations locally in the browser.
The Cause
Because TanStack Router supports SSR, my root component code was being executed on the server. This code tried to run Pglite’s initialization—which relies on IndexedDB—resulting in an error because there’s no window
or IndexedDB available during SSR.
Since pglite
expects to call open()
on a valid db
instance, but since SSR is happening in a Node environment, there’s no actual IndexedDB to connect to. As a result, the reference to the database was null
or undefined
during the server render.
The Solution
TL;DR: Put all the pglite indexdb related code in client only pages!
Here is how:
I resolved the issue by strictly deferring IndexedDB initialization to the client side. Here’s the approach:
-
Remove Initialization from
_root.tsx
- As described in Tanstack Router document, in the Router Creation Section, Any code in
_root.tsx
is run on both server and client during SSR. - Remove or avoid all Pglite initialization there to prevent server attempts to connect to IndexedDB.
- As described in Tanstack Router document, in the Router Creation Section, Any code in
-
Create a Client-Only Pglite Provider
- Wrap the Pglite logic in a React component that uses
useEffect
. - This ensures the code (migrations, connections, etc.) only runs after the app mounts in the browser.
- Wrap the Pglite logic in a React component that uses
-
Use the Client-Only Provider in a Client-Only Route
- Wherever you need Pglite, wrap your components in
ClientOnlyPgliteProvider
. - This ensures all IndexedDB and database operations are attempted only after the client mounts.
- Wherever you need Pglite, wrap your components in
By using this pattern, SSR won’t attempt to open IndexedDB, preventing the null
database instance errors. All database logic, including migrations, happens only in the browser after hydration.
Conclusion
Switching from fp-ts to Effect has offered a cleaner, more structured way to handle side effects and concurrency. However, any client-side library (like Pglite) that depends on IndexedDB must be carefully isolated from SSR code paths, especially in frameworks like TanStack Router.
Key Takeaways:
- Defer client-only logic: Keep client-specific libraries out of the server render cycle.
- Use
useEffect
: React’suseEffect
defers code until after the component mounts on the client. - Wrap in a dedicated provider: Centralize your client-only logic in a provider to keep the application code clean and maintainable.
This approach ensures a smooth integration of SSR, local-first data strategies, and powerful functional paradigms, allowing you to reap the benefits of server-rendering without sacrificing the convenience of in-browser databases.