-
Notifications
You must be signed in to change notification settings - Fork 106
feat: Add SSR/RSC support for live queries in @tanstack/react-db #709
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
KyleAMathews
wants to merge
13
commits into
main
Choose a base branch
from
claude/implement-issue-545-011CUP2MtvMz7yJVeYw2WYv6
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
feat: Add SSR/RSC support for live queries in @tanstack/react-db #709
KyleAMathews
wants to merge
13
commits into
main
from
claude/implement-issue-545-011CUP2MtvMz7yJVeYw2WYv6
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Implements SSR (Server-Side Rendering) and RSC (React Server Components) support for live queries, following TanStack Query's hydration patterns.
## Key Features
- **Server-side query execution**: `prefetchLiveQuery()` executes queries on the server and extracts results
- **Dehydration**: `dehydrate()` serializes query results for client transfer
- **Hydration**: Automatic hydration of query results on the client via React Context or global state
- **HydrationBoundary**: Component to provide hydrated data to child components
- **Seamless integration**: `useLiveQuery` automatically uses hydrated data when available
## Implementation Details
- Creates temporary collections on server for query execution
- Stores only query results (not full collection state) for minimal payload
- Client checks for hydrated data when collection is empty
- Smooth transition from hydrated data to live reactive updates
- Supports both HydrationBoundary context and direct `hydrate()` calls
## API Usage
### Server-side (Next.js App Router example)
```tsx
async function Page() {
const serverContext = createServerContext()
await prefetchLiveQuery(serverContext, {
id: 'todos',
query: (q) => q.from({ todos: todosCollection })
})
return (
<HydrationBoundary state={dehydrate(serverContext)}>
<TodoList />
</HydrationBoundary>
)
}
```
### Client-side
```tsx
'use client'
function TodoList() {
const { data } = useLiveQuery({
id: 'todos',
query: (q) => q.from({ todos: todosCollection })
})
return <div>{data.map(todo => <Todo key={todo.id} {...todo} />)}</div>
}
```
## Testing
- Added comprehensive test suite for SSR/RSC functionality
- Tests cover prefetching, dehydration, hydration, and HydrationBoundary
- All existing tests pass (67 total tests)
- Code coverage improved from 75.77% to 86.5%
Closes #545
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <[email protected]>
🦋 Changeset detectedLatest commit: 968e37c The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
More templates
@tanstack/angular-db
@tanstack/db
@tanstack/db-ivm
@tanstack/electric-db-collection
@tanstack/query-db-collection
@tanstack/react-db
@tanstack/rxdb-db-collection
@tanstack/solid-db
@tanstack/svelte-db
@tanstack/trailbase-db-collection
@tanstack/vue-db
commit: |
|
Size Change: 0 B Total Size: 84.3 kB ℹ️ View Unchanged
|
|
Size Change: 0 B Total Size: 2.89 kB ℹ️ View Unchanged
|
Demonstrates SSR/RSC support for live queries in the TanStack Start projects example app. ## Changes **Project Detail Page** (`src/routes/_authenticated/project/$projectId.tsx`): - ✅ Enabled SSR (`ssr: true`) - ✅ Added server-side prefetching in loader using `createServerContext` and `prefetchLiveQuery` - ✅ Wrapped component with `HydrationBoundary` to provide server-rendered data - ✅ Updated all `useLiveQuery` calls to include `id` option for hydration matching - ✅ Added comprehensive inline comments explaining the SSR/RSC pattern **Prefetched Queries:** - Project details - Project todos (filtered and ordered) - All users (for member management) - Project membership info **README Updates:** - Added new "Server-Side Rendering (SSR) & React Server Components (RSC)" section - Included complete code example showing the pattern - Documented benefits (instant rendering, SEO, performance) - Explained key concepts (query ID matching, minimal payload, automatic transitions) ## Benefits - **Zero loading states**: Page renders immediately with server data - **SEO optimized**: Fully populated HTML for search engines - **Smooth UX**: Seamless transition from server data to live reactive updates - **Minimal payload**: Only query results transferred, not full collection state ## Usage The implementation follows TanStack Query's hydration patterns: 1. Server prefetches queries and dehydrates results 2. HydrationBoundary provides data to components 3. useLiveQuery with matching IDs uses hydrated data 4. Queries automatically become reactive after hydration This serves as a reference implementation for SSR/RSC with TanStack DB live queries. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
This commit addresses priority fixes from external code review:
- Fix singleResult bug in hydrated data path - now properly returns single object vs array
- Remove unused staleTime option from prefetchLiveQuery
- Split server.tsx into server.ts (server-only) and hydration.tsx ('use client') for proper RSC module boundaries
- Add test coverage for singleResult with hydrated data
- Improve option detection from fragile duck typing to explicit property check
- Add comprehensive serialization constraints documentation to README
All 68 tests passing.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <[email protected]>
…ration, and enhanced testing This commit addresses the second round of code review feedback: **Critical fixes:** - Fix README docs bug - async query example now correctly uses transform callback instead of async function - Switch to toArrayWhenReady() in prefetchLiveQuery for rock-solid first render guarantees **New features:** - Add transform option to prefetchLiveQuery for server-side data transformations (e.g., Date serialization) - Implement safer global hydration using Symbol.for() to avoid bundle collisions - Add oneShot option to hydrate() for one-time consumption patterns **Enhanced testing:** - Add nested HydrationBoundary test (inner shadows outer) - Add one-shot hydration test - Update existing test to check new Symbol-based storage structure **Developer experience improvements:** - Comprehensive JSDoc examples for transform option - Clearer documentation for serialization constraints - More robust global state handling All 70 tests passing with 89.94% coverage. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
This commit addresses the final round of code review feedback to make the SSR/RSC implementation production-ready: **Critical bug fixes:** - Fix test cleanup to clear Symbol-based global state, preventing test flakiness - Align status reporting in hydrated path with underlying collection status for consistency - Users can now trust `status` and `isReady` flags even during hydration **Type safety & robustness:** - Harden transform types to accept `Array<any> | any` return values - Add runtime normalization to ensure transform output is always an array - Updated docs: "transform should return an array of rows; non-arrays are normalized to a single-element array" **Public API hygiene:** - Remove internal `useHydratedQuery` from root exports to prevent accidental coupling - Explicitly export only public APIs: createServerContext, prefetchLiveQuery, dehydrate, HydrationBoundary, hydrate - Keep types exported for advanced use cases **Developer experience improvements:** - Add subpath exports in package.json for explicit server/client boundaries: - `@tanstack/react-db/server` for server-only code - `@tanstack/react-db/hydration` for client components - Document subpath imports in README with examples - Add oneShot option documentation to API reference - Mention transform option in API reference **Benefits:** - Better bundler optimization for RSC environments - Clearer intent in imports (server vs. client) - Reduced bundle size by preventing internal API leakage - More predictable behavior during hydration transitions All 70 tests passing with 90% coverage. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
This review document provides: - Complete API comparison (requested vs implemented) - Technical challenges and solutions - Phase 1-3 completion status - Phase 4-6 deferral explanation - Test coverage summary - Bonus features added during code review Useful for PR description and future reference. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Based on user feedback, we're removing the global hydrate() function and oneShot option
in favor of exclusively using HydrationBoundary for a simpler, more React-idiomatic API.
**Changes:**
- Remove hydrate() function and oneShot option from hydration.tsx
- Remove Symbol-based global storage (no longer needed)
- Remove getHydratedQuery() helper function
- Update useLiveQuery to only check HydrationContext (no global fallback)
- Update all tests to use HydrationBoundary wrapper instead of global hydrate()
- Remove "should hydrate state globally" test (no longer applicable)
- Remove "should support one-shot hydration" test (feature removed)
- Update README to remove hydrate() from API reference
- Update index.ts to not export hydrate()
**Benefits:**
- Simpler API surface (one way to do hydration)
- No global state pollution
- No memory management complexity
- React manages lifecycle automatically via Context
- More predictable behavior (no hidden globals)
**Migration:**
Before:
```tsx
hydrate(dehydratedState, { oneShot: true })
```
After:
```tsx
<HydrationBoundary state={dehydratedState}>
{children}
</HydrationBoundary>
```
All 68 tests passing with 89.61% coverage.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <[email protected]>
This commit addresses final documentation cleanup to ensure the public API is unambiguous:
**Documentation cleanup:**
- Remove hydrate() from changeset API list
- Remove "direct hydrate() approaches" mention from changeset
- Add transform option to changeset API docs
- Update IMPLEMENTATION_REVIEW.md to remove all global hydrate() references
- Remove OneShot and Symbol storage sections (features removed)
- Update section numbering after removal
- Update "Safety" line to remove Symbol globals mention
**Dev experience improvement:**
- Add console.warn in development when useLiveQuery has an id but no hydrated data found
- Warning message: "TanStack DB: no hydrated data found for id "..." — did you wrap this subtree in <HydrationBoundary state={...}>?"
- Only fires when:
- process.env.NODE_ENV !== 'production'
- queryId is provided
- Collection is empty
- No hydrated data found in context
This helps developers quickly identify SSR hydration setup issues.
**Transform type signature verified:**
- Already correct: `transform?: (rows: Array<any>) => Array<any> | any`
- Defensive normalization already in place: `Array.isArray(out) ? out : [out]`
All 68 tests passing.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <[email protected]>
- Format documentation files with prettier - Improve clarity in changeset and implementation review - Ensure all references to removed hydrate() function are cleaned up - Polish README examples and API reference sections 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
- Update server.ts to use hydrateId in PrefetchLiveQueryOptions - Update hydration.tsx to match queries by hydrateId - Update useLiveQuery.ts to extract hydrateId from config - Separates SSR hydration identifier from collection id (used for devtools) This allows users to freely use id for devtools/debugging without triggering SSR hydration warnings. Only hydrateId opts into SSR. WIP: Tests and documentation still need updating. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
- Extract hydrateId only from config objects (not collections/functions)
- Update dev warning to reference hydrateId instead of id
- Aligns with server API pattern: prefetchLiveQuery({ hydrateId, query })
This ensures consistency between server and client APIs.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <[email protected]>
Changed from hydrateId back to id to align with TanStack Query patterns. Updated dev warning to only trigger when: - HydrationBoundary context exists (SSR environment detected) - Query has an id - No matching hydrated data found This prevents false warnings in client-only apps while still catching SSR setup mistakes. Benefits: - Simpler API (single identifier like TanStack Query) - No warnings in client-only apps (no hydration context = no warning) - Helpful warnings in SSR apps when query wasn't prefetched - id serves dual purpose: collection identity + SSR matching All 11 SSR tests passing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Implements SSR (Server-Side Rendering) and RSC (React Server Components) support for live queries, following TanStack Query's hydration patterns.
Key Features
prefetchLiveQuery()executes queries on the server and extracts resultsdehydrate()serializes query results for client transferuseLiveQueryautomatically uses hydrated data when availableImplementation Details
hydrate()callsAPI Usage
Server-side (Next.js App Router example)
Client-side
Testing
Closes #545
🤖 Generated with Claude Code
🎯 Changes
✅ Checklist
pnpm test:pr.🚀 Release Impact