본문으로 건너뛰기

SSR 및 하이드레이션

TanStack DB SSR은 서버가 수행한 작업에 필요한 최소한의 스냅샷을 전송합니다:

  • 명시적으로 미리 로드된 컬렉션은 정규화된 컬렉션 행으로 역직렬화됩니다.
  • 미리 로드되었거나 렌더링 중에 발견된 라이브 쿼리는 모든 소스 컬렉션을 직렬화하지 않고 정렬된 쿼리 결과 스냅샷으로 역직렬화됩니다.

브라우저는 두 스냅샷 중 하나를 즉시 렌더링하고, 일반 컬렉션 동기화 및 라이브 쿼리 파이프라인을 시작한 다음, 브라우저 결과가 권위 있는 결과가 되면 라이브 쿼리 스냅샷을 원자적으로 교체합니다.

상위 수준 요약

SSR을 지원하는 API에는 다음 여섯 가지 개념이 추가됩니다:

  • DbClient는 하나의 요청, 브라우저 앱, 테스트 또는 스크립트에 대한 구체화된 컬렉션 인스턴스를 소유합니다.
  • collectionOptions(...)는 안정적인 컬렉션 설명자를 생성합니다. 재사용 가능한 설명자는 각 DbClient에 대해 새로운 어댑터 구성을 생성합니다.
  • dbClient.dehydrate(), dbClient.hydrate(state)dbClient.applyCollectionChunk(chunk)는 명시적인 컬렉션 상태를 서버/클라이언트 경계를 넘어 이동합니다.
  • dbClient.preloadLiveQuery(options)는 다음 항목만 캡처합니다: 하이드레이션 또는 스트리밍을 위한 라이브 쿼리의 정렬된 결과입니다.
  • React 및 Svelte 앱은 DbProvider을 사용하므로 훅이 현재 클라이언트에 대해 컬렉션 설명자를 확인할 수 있습니다.
  • @tanstack/react-router-with-db는 TanStack Start 서버 렌더링 중 Suspense에서 발견된 라이브 쿼리를 스트리밍합니다.

기존 앱은 계속 작동합니다. createCollection(...) 및 직접 컬렉션 인스턴스도 여전히 존재합니다. SSR 안전 요청 격리, 하이드레이션, 증분 청크, Suspense 스트리밍 또는 1.0 준비 React 훅 형태가 필요한 경우 마이그레이션해야 합니다.

이전 종속성 배열 형식은 이제 다음 경고를 표시합니다:

useLiveQuery((q) => q.from({ todos }).where(...), [status])

여전히 작동하지만 개발 환경에서 경고가 표시되며 1.0에서 제거됩니다. 다음 형식을 사용하는 것이 좋습니다:

useLiveQuery({
query: (q) => q.from({ todos: todoCollection }).where(...),
})

React는 기본적으로 구조화된 쿼리 IR에서 라이브 쿼리 ID를 파생합니다. 불투명한 함수형 쿼리 로직에만 queryKey를 추가하거나, 파생된 ID 작업을 건너뛰려는 렌더링 빈도가 높은 경로에 추가합니다.

요약표

작업이전 방식SSR 지원 방식
컬렉션 정의createCollection(options)collectionOptions(id, factory)
컬렉션 구체화모듈 수준 싱글턴dbClient.collection(todoCollection)
컬렉션 상태 범위 지정모듈 수명요청/브라우저/테스트마다 new DbClient()
React 컨텍스트 제공없음<DbProvider client={dbClient}>
React에서 쿼리직접 컬렉션 인스턴스from의 설명자, DbProvider에 의해 확인됨
React에서 뮤테이션싱글턴 컬렉션 가져오기useDbClient().collection(todoCollection)
서버 미리 로드임의의 컬렉션 미리 로드collection.preload() 또는 dbClient.preloadLiveQuery(...)
SSR 상태 직렬화없음const state = dbClient.dehydrate()
브라우저에서 하이드레이션없음훅이 읽기 전에 dbClient.hydrate(state)
행을 증분 방식으로 적용사용자 지정 앱 상태dbClient.applyCollectionChunk(chunk)
렌더링 시간 결과 스트리밍없음routerWithDbClient(router, dbClient)
React 쿼리 ID종속성 배열파생된 IR 또는 필요한 경우 queryKey

최소 React 패턴

import {
DbClient,
DbProvider,
collectionOptions,
eq,
useDbClient,
useLiveQuery,
} from '@tanstack/react-db'

const todoCollection = collectionOptions('todos', () => ({
id: 'todos',
getKey: (todo: Todo) => todo.id,
sync: {
sync: ({ markReady }) => {
markReady()
},
},
}))

function useTodoCollection() {
return useDbClient().collection(todoCollection)
}

function Todos({ status }: { status: string }) {
const todos = useTodoCollection()

const { data } = useLiveQuery({
query: (q) =>
q
.from({ todo: todoCollection })
.where(({ todo }) => eq(todo.status, status)),
})

return (
<ul>
{data.map((todo) => (
<li
key={todo.id}
onClick={() => todos.update(todo.id, (draft) => {
draft.done = true
})}
>
{todo.title}
</li>
))}
</ul>
)
}

const dbClient = new DbClient()

root.render(
<DbProvider client={dbClient}>
<Todos status="open" />
</DbProvider>
)

config에 변경 가능한 adapter 상태나 클로저가 포함된 경우 factory가 중요합니다. 각 DbClient은 새로운 config와 컬렉션 인스턴스를 가져옵니다. 공식 adapter 옵션 생성기는 이미 동등한 factory를 연결하므로 다음과 같이 사용해도 안전합니다:

const todoCollection = collectionOptions(
localOnlyCollectionOptions<Todo>({
id: 'todos',
getKey: (todo) => todo.id,
})
)

하나의 DbClient 안에서는 동일한 id을 가진 descriptor가 동일한 컬렉션으로 확인됩니다. 첫 번째 descriptor만 구체화됩니다. 컬렉션을 변경하는 모든 동적 매개변수를 해당 id에 포함합니다.

임의의 구체적인 config에서 생성된 descriptor는 하나의 DbClient에서만 구체화할 수 있습니다. 사용자 지정 adapter와 요청 범위 종속성에는 명시적 factory 형식을 사용합니다.

SSR 흐름

서버와 브라우저는 동일한 descriptor를 사용하지만 서로 다른 DbClient 인스턴스를 사용합니다.

server request
-> new DbClient()
-> preload an explicit collection or live-query result
-> dbClient.dehydrate()
-> send state through framework loader

browser
-> new DbClient()
-> dbClient.hydrate(loaderState)
-> <DbProvider client={dbClient}>
-> useLiveQuery({ query })
-> start source sync
-> atomically replace any query snapshot with the live result

React 하이드레이션 중 descriptor 기반 쿼리는 첫 번째 브라우저 렌더링에서 하이드레이션된 컬렉션 행 또는 이에 대응하는 쿼리 결과 스냅샷을 읽습니다. Adapter 동기화와 대기 중인 온디맨드 로드는 React가 외부 스토어 구독을 커밋할 때 시작되므로 초기 마크업은 여전히 서버와 일치합니다. 소스가 로드되는 동안에도 스냅샷이 표시됩니다. 브라우저 라이브 쿼리가 준비되면 DB는 스냅샷에서 라이브 결과로 한 번 인계합니다.

서버

각 요청마다 새로운 DbClient을 생성합니다. 해당 client를 통해 descriptor를 구체화하고, 라우트에 필요한 데이터를 미리 로드한 다음 client를 탈수화합니다.

import { DbClient, collectionOptions, eq } from '@tanstack/db'

export const todoCollection = collectionOptions('todos', () => ({
id: 'todos',
getKey: (todo: Todo) => todo.id,
syncMode: 'on-demand',
sync: {
sync: ({ markReady, begin, write, commit }) => {
markReady()

return {
loadSubset: async () => {
const todos = await api.todos.list()
begin({ immediate: true })
for (const todo of todos) {
write({ type: 'insert', value: todo })
}
commit()
return true
},
}
},
},
}))

export async function loadTodosForSsr() {
const dbClient = new DbClient()
const todos = dbClient.collection(todoCollection)
await todos.preload()

return dbClient.dehydrate()
}

이 명시적 컬렉션 사전 로드는 정규화된 소스 행을 탈수화합니다. 여러 브라우저 쿼리에서 동일한 소스 데이터가 필요한 경우 사용합니다.

소스가 렌더링된 결과보다 훨씬 큰 경우 대신 쿼리를 미리 로드합니다:

const dbClient = new DbClient()

await dbClient.preloadLiveQuery({
query: (q) =>
q
.from({ todo: todoCollection })
.where(({ todo }) => eq(todo.status, 'open'))
.select(({ todo }) => ({ id: todo.id, title: todo.title })),
})

const state = dbClient.dehydrate()

이 페이로드에는 투영된 쿼리 결과가 포함되며, 해당 컬렉션도 명시적으로 구체화하지 않았다면 소스 컬렉션 행은 포함되지 않습니다.

브라우저

DB에서 읽는 컴포넌트를 렌더링하기 전에 브라우저 client를 하이드레이션합니다.

import {
DbClient,
DbProvider,
HydrationBoundary,
} from '@tanstack/react-db'

function App({ dehydratedDbState }: { dehydratedDbState: DehydratedDbState }) {
const [dbClient] = React.useState(() => new DbClient())

return (
<DbProvider client={dbClient}>
<HydrationBoundary state={dehydratedDbState}>
<Routes />
</HydrationBoundary>
</DbProvider>
)
}

프레임워크마다 로더 데이터가 client에 전달되는 방식은 다르지만 DB 인계는 동일합니다. 서버에서 DbClient, dehydrate(), 이후 브라우저 client에서 hydrate()입니다.

Svelte

Svelte는 자체 DbProvider에서 descriptor를 확인하고 서버 렌더링 중 하이드레이션된 쿼리 스냅샷을 동기적으로 읽습니다:

<script lang="ts">
import { DbClient, DbProvider } from '@tanstack/svelte-db'
import Todos from './Todos.svelte'

const client = new DbClient()
client.hydrate(dehydratedDbState)
</script>

<DbProvider {client}>
<Todos />
</DbProvider>

Todos.svelte 내부에서는 useLiveQuery({ query })이 컬렉션 descriptor를 직접 사용할 수 있습니다. 브라우저 구독은 소스 동기화를 시작하고 React와 동일한 스냅샷에서 라이브 결과로의 인계를 수행합니다.

라이브 데모: https://tanstack-db-ssr-demo.netlify.app/ssr-db

TanStack Start를 사용한 Suspense 스트리밍

@tanstack/react-router-with-db@tanstack/react-router-with-query과 동일한 통합 패턴을 따릅니다:

import { DbClient } from '@tanstack/react-db'
import { createRouter } from '@tanstack/react-router'
import { routerWithDbClient } from '@tanstack/react-router-with-db'

export type RouterContext = {
dbClient: DbClient
}

export function getRouter() {
const dbClient = new DbClient()
const router = createRouter({
routeTree,
context: { dbClient },
})

return routerWithDbClient(router, dbClient)
}

Adapter는 라우터 컨텍스트에 dbClient을 추가하고, 앱을 DbProvider으로 감싸며, 중요한 상태를 탈수화하고 렌더링 중 나중에 발견되는 쿼리 결과를 위한 스트림을 엽니다.

function RouteComponent() {
return (
<Suspense fallback={<p>Loading todos</p>}>
<TodoList />
</Suspense>
)
}

function TodoList() {
const { data } = useLiveSuspenseQuery({
query: (q) =>
q
.from({ todo: todoCollection })
.where(({ todo }) => eq(todo.status, 'open')),
})

return data.map((todo) => <Todo key={todo.id} todo={todo} />)
}

서버에서 TodoList이 일시 중단되면 adapter는 대기 중인 쿼리 promise를 스트리밍합니다. 해당 promise는 스트리밍된 DehydratedDbState 내부의 정렬된 라이브 쿼리 결과 스냅샷으로 확인됩니다. 소스 컬렉션과 D2 그래프는 네트워크를 통해 전송되지 않습니다. 브라우저는 스냅샷을 표시하고 소스 컬렉션과 라이브 쿼리를 정상적으로 시작한 다음 브라우저 결과가 준비되면 스냅샷을 대체합니다.

서버와 브라우저는 동일한 라이브 쿼리 ID를 도출해야 합니다. 구조화된 쿼리는 이를 자동으로 처리합니다. 불투명한 쿼리는 직렬화 가능한 queryKey을 제공해야 하며, ID를 도출할 수 없으면 렌더링 시간 스트리밍에서 오류가 발생합니다.

Next.js를 사용한 Suspense 스트리밍

Next.js App Router는 React Server Components를 통해 동일한 대기 중 쿼리 promise를 전달할 수 있습니다. 기다리지 않고 사전 로드를 시작하고, 대기 중인 결과를 탈수화한 다음 해당 상태를 client 하이드레이션 경계에 전달합니다:

export default function Page() {
const dbClient = new DbClient()
void dbClient.preloadLiveQuery(openTodosQuery)

const state = dbClient.dehydrate({
shouldDehydrateCollection: () => false,
shouldDehydrateLiveQuery: () => true,
})

return (
<DbHydration state={state}>
<Suspense fallback={<p>Loading todos</p>}>
<TodoList />
</Suspense>
</DbHydration>
)
}

DbHydration은 브라우저 DbClient 하나를 생성하고 children을 감쌉니다 DbProvider으로 감싸고 stateHydrationBoundary에 전달합니다. React는 promise 결과를 해당 경계로 스트리밍합니다. 전체 작동 통합은 다음에 있습니다 examples/react/next-ssr-e2e.

증분 컬렉션 하이드레이션

애플리케이션은 자체 스트림을 통해 받은 컬렉션 행을 적용할 수도 있습니다. 증분 하이드레이션은 전체 탈수화와 동일한 컬렉션 청크 형태를 사용합니다:

dbClient.applyCollectionChunk({
collectionId: 'todos',
rows: [
{
key: 'todo-1',
value: {
id: 'todo-1',
title: 'Streamed row',
status: 'open',
},
metadata: { source: 'stream' },
},
],
syncMeta: { version: 1, cursor: 'abc' },
})

대상 컬렉션이 이미 구체화되어 있으면 행이 즉시 적용되고 기존 라이브 쿼리는 컬렉션 상태에 따라 반응합니다. 컬렉션이 아직 구체화되지 않았다면 청크가 저장되며 해당 collectionId이 구체화될 때 적용됩니다.

직렬화되는 항목

dbClient.dehydrate()은 서로 독립적인 두 가지 스냅샷 유형을 생성할 수 있습니다.

직렬화됨:

  • 명시적 컬렉션 스냅샷: 컬렉션 ID, 동기화된 행 키와 값, 행 메타데이터, 그리고 exportSyncMeta의 adapter 동기화 메타데이터
  • 라이브 쿼리 스냅샷: 쿼리 해시와 정렬된 결과 행. 완료된 명시적 사전 로드는 기본적으로 포함되며, 프레임워크 통합은 대기 중인 promise를 스트리밍에 선택적으로 포함합니다

직렬화되지 않음:

  • 뮤테이션 핸들러
  • 대기 중인 낙관적 뮤테이션
  • 대기 중인 구독
  • D2 그래프 또는 컴파일된 파이프라인
  • 트랜잭션 스택
  • 모듈 수준 런타임 상태
  • 쿼리 결과 스냅샷의 소스 컬렉션 행. 단, 해당 컬렉션도 탈수화를 위해 명시적으로 구체화된 경우는 제외합니다

브라우저에 필요한 항목에 따라 페이로드 단위를 선택합니다. 명시적 컬렉션 사전 로드는 쿼리 간 재사용을 위해 정규화된 행을 보존합니다. 라이브 쿼리 사전 로드는 렌더링된 투영이 작은 경우 50~100배 더 큰 소스를 전송하지 않도록 합니다. 어느 모드도 실행 가능한 쿼리 상태를 직렬화하지 않습니다.

동기화 메타데이터

Adapter는 세 가지 선택적 훅을 통해 재개 가능한 동기화에 참여할 수 있습니다:

type SyncConfig = {
exportSyncMeta?: () => unknown
importSyncMeta?: (meta: unknown) => void
mergeSyncMeta?: (current: unknown, incoming: unknown) => unknown
}

메타데이터 형태는 adapter가 소유합니다. adapter 페이로드 내부에서 버전을 관리합니다. adapter가 수신 메타데이터를 이해할 수 없으면 이를 무시하고 안전한 지점에서 동기화를 다시 시작해야 합니다.

하이드레이션 중 DB는 syncMeta을 구체화된 컬렉션으로 가져옵니다. 컬렉션에 이미 최신 메타데이터가 있으면 제공된 경우 DB는 mergeSyncMeta(current, incoming)을 호출하고 병합된 결과를 가져옵니다.

Adapter가 동기화 메타데이터 훅을 구현하지 않아도 행 스냅샷은 하이드레이션되며 adapter는 정상적으로 동기화를 다시 시작할 수 있습니다.

초기 데이터

initialData은 동기화 준비 신호가 아니라 시작 시드입니다.

Adapter 동기화가 시작되기 전 현재 DbClient 우선순위는 낮은 순서부터 다음과 같습니다:

  1. materialization별 initialData
  2. 영속화된 행
  3. 하이드레이션된 행

새로운 adapter 동기화가 세 항목보다 우선합니다. 하이드레이션된 행과 초기 행은 잠정적인 기본 상태이므로 동일한 키에 대한 adapter의 첫 삽입은 중복 키 오류를 발생시키는 대신 업데이트로 조정됩니다.

하이드레이션된 행과 initialData은 그 자체로 adapter 동기화를 준비 완료로 표시하지 않습니다. 준비 상태는 여전히 adapter가 동기화 수명 주기를 통해 관리합니다.

React 쿼리 ID

React 훅은 기본적으로 구조화된 쿼리 IR에서 라이브 쿼리 ID를 도출합니다:

function Todos({ status }: { status: string }) {
return useLiveQuery({
query: (q) =>
q
.from({ todo: todoCollection })
.where(({ todo }) => eq(todo.status, status)),
})
}

캡처된 status 값은 구조화된 IR에 표현되므로 종속성 배열이나 queryKey이 필요하지 않습니다.

DB가 안정적으로 표현할 수 없는 불투명한 런타임 로직이 쿼리에 포함된 경우 queryKey을 사용합니다:

function SearchTodos({ search }: { search: string }) {
return useLiveQuery({
queryKey: [todoCollection.id, 'search', search],
query: (q) =>
q
.from({ todo: todoCollection })
.fn.where(({ todo }) =>
todo.title.toLowerCase().includes(search.toLowerCase())
),
})
}

queryKey을 추가하는 일반적인 이유:

  • .fn.where(...)
  • .fn.select(...)
  • .fn.having(...)
  • 구조화된 쿼리 내부에 캡처된 함수 값, 심볼, 클래스 인스턴스 또는 순환 객체
  • 파생 ID 계산 비용이 측정 가능할 정도로 큰 렌더링 경로

1.0 이전에는 구조화된 IR을 해시할 수 없을 때 DB가 경고하고 레거시 마운트 안정 ID를 유지합니다. 쿼리는 계속 작동하지만 불투명한 로직 내부의 캡처된 값은 queryKey에 표현되지 않는 한 반응형이 아닙니다. 1.0에서는 queryKey이 없는 해시 불가능한 쿼리에서 오류가 발생합니다.

또한 DB는 ID 도출 비용이 충분히 커서 명시적 queryKey이 더 나은 경우 개발 환경에서 한 번 경고합니다.

하위 호환성을 위해 종속성 배열을 허용합니다:

useLiveQuery((q) => q.from({ todo: todoCollection }), [status])

개발 환경에서 경고하며 1.0에서 제거됩니다. config 객체 형식으로 마이그레이션합니다:

useLiveQuery({
query: (q) => q.from({ todo: todoCollection }),
})

쿼리가 불투명한 로직을 사용하거나 성능 경고를 발생시키는 경우에만 queryKey을 추가합니다.

마이그레이션 가이드

1. SSR 싱글턴 대신 descriptor 생성

SSR이 필요한 컬렉션은 모듈 수준 createCollection(...)을 재사용 가능한 collectionOptions(...) descriptor로 교체합니다.

// Before
export const todoCollection = createCollection({
id: 'todos',
getKey: (todo) => todo.id,
sync: todoSync,
})

// After
export const todoCollection = collectionOptions('todos', () => ({
id: 'todos',
getKey: (todo: Todo) => todo.id,
sync: createTodoSync(),
}))

변경 가능한 상태와 클로저를 factory 내부에 배치합니다. 공식 adapter 옵션 생성기도 새로운 config factory를 제공하므로 직접 전달할 수 있습니다. SSR에 참여하지 않는 컬렉션은 계속 createCollection을 사용할 수 있습니다.

2. DbClient 추가

각 서버 요청에는 새 client를 사용하고 각 브라우저 앱 인스턴스에는 안정적인 client를 사용합니다.

const dbClient = new DbClient()

테스트에서는 공유 상태를 명시적으로 다루는 테스트가 아니라면 테스트마다 새 client를 생성합니다.

3. React를 DbProvider으로 감싸기

root.render(
<DbProvider client={dbClient}>
<App />
</DbProvider>
)

컬렉션 descriptor를 확인하는 훅에는 이 provider가 필요합니다. 이것이 없으면 DB는 숨겨진 전역 상태로 대체하지 않고 오류를 발생시킵니다.

4. 명령형 작업에는 컬렉션 훅 사용

라이브 쿼리 소스에서는 descriptor를 직접 사용하고 컬렉션 메서드가 필요할 때만 구체화합니다:

function useTodoCollection() {
return useDbClient().collection(todoCollection)
}

function TodoActions({ id }: { id: string }) {
const todos = useTodoCollection()

return (
<button onClick={() => todos.delete(id)}>
Delete
</button>
)
}

이렇게 하면 요청/client 범위 지정을 한 곳에서 관리하고 모듈 수준 컬렉션이 다시 도입되는 것을 방지합니다.

5. 종속성 배열 교체

대부분의 쿼리는 종속성 배열을 완전히 제거할 수 있습니다:

// Before
useLiveQuery(
(q) =>
q
.from({ todo: todoCollection })
.where(({ todo }) => eq(todo.status, status)),
[status],
)

// After
useLiveQuery({
query: (q) =>
q
.from({ todo: todoCollection })
.where(({ todo }) => eq(todo.status, status)),
})

쿼리가 불투명한 함수 변형을 사용하는 경우 queryKey을 추가합니다:

useLiveQuery({
queryKey: [todoCollection.id, 'status-fn', status],
query: (q) =>
q
.from({ todo: todoCollection })
.fn.where(({ todo }) => todo.status === status),
})

6. 서버에서 사전 로드 및 탈수화

브라우저가 정규화된 소스 행을 받아야 하는 경우 컬렉션을 미리 로드합니다:

const dbClient = new DbClient()
const todos = dbClient.collection(todoCollection)
await todos.preload()

return {
dbState: dbClient.dehydrate(),
}

브라우저에 렌더링된 결과만 필요한 경우 라이브 쿼리를 미리 로드합니다:

const dbClient = new DbClient()
await dbClient.preloadLiveQuery(openTodosQuery)

return {
dbState: dbClient.dehydrate(),
}

7. client 훅이 DB를 읽기 전에 하이드레이션

<DbProvider client={client}>
<HydrationBoundary state={loaderData.dbState}>
<App />
</HydrationBoundary>
</DbProvider>

명령형 통합에서는 대신 렌더링 전에 client.hydrate(loaderData.dbState)을 호출할 수 있습니다.

호환성

이 변경으로 기존 공개 API가 제거되지 않습니다.

계속 지원되는 항목:

  • createCollection(...)
  • 컬렉션 인스턴스를 useLiveQuery(...)에 전달
  • useLiveQuery(queryFn, deps)
  • useLiveSuspenseQuery(queryFn, deps)
  • insert, update, delete, subscribe와 같은 뮤테이션 API 및 낙관적 뮤테이션 헬퍼

경고:

  • React 의존성 배열은 개발 환경에서 경고를 발생시키며 1.0에서 제거됩니다.
  • queryKey이 없는 불투명한 쿼리 IR은 개발 환경에서 경고를 발생시키며 1.0까지 레거시 마운트 안정적 ID를 유지합니다. 1.0에서는 오류를 발생시킵니다.
  • 비용이 큰 파생 ID는 개발 환경에서 경고를 발생시키며 queryKey를 제안합니다.

SSR에 필요:

  • 안정적인 명시적 컬렉션 ID
  • 요청 범위 서버 DbClient
  • 브라우저 범위 클라이언트 DbClient
  • React에서 디스크립터를 확인하기 위한 DbProvider
  • 서버의 dehydrate() 및 브라우저의 hydrate()

렌더링 시점의 Suspense 스트리밍에 필요:

  • routerWithDbClient(router, dbClient)
  • Suspense 경계 내부의 useLiveSuspenseQuery(...)
  • 안정적인 파생 쿼리 ID 또는 명시적인 직렬화 가능한 queryKey

상세 변경 로그

추가됨

  • DbClient
  • collectionOptions(...)
  • CollectionOptions 디스크립터 유형
  • CollectionMaterializeOptions
  • DehydratedDbState
  • DehydratedCollectionChunk
  • DehydratedCollectionRow
  • dbClient.collection(descriptor, options?)
  • dbClient.dehydrate()
  • dbClient.hydrate(state)
  • dbClient.applyCollectionChunk(chunk)
  • dbClient.subscribe(listener)
  • dbClient.createTransaction(config)
  • dbClient.cleanup()
  • React DbProvider
  • React useDbClient()
  • React useOptionalDbClient()
  • React HydrationBoundary
  • 라이브 쿼리 빌더 내부의 React 디스크립터 확인
  • React 파생 구조화 쿼리 식별자
  • 불투명하거나 핫 패스 쿼리를 위한 React queryKey 이스케이프 해치
  • React 쿼리별 client 재정의
  • SSR을 지원하는 useSyncExternalStore 서버 스냅샷 지원
  • dbClient.preloadLiveQuery(...)
  • Svelte DbProvider, useDbClient(), 디스크립터 확인 및 동기 서버 스냅샷 지원
  • TanStack Start 및 Next.js Playwright SSR E2E 적용 범위
  • @tanstack/react-router-with-db
  • 렌더링 시점의 useLiveSuspenseQuery 프로미스 스트리밍

변경 사항

  • React useLiveQuery({ query })은 컬렉션 디스크립터를 다음 소스에서 직접 사용할 수 있습니다: from, join, leftJoinunionAll 소스. 단, DbProvider가 있어야 합니다.
  • 다음 항목이 제공되지 않으면 React 라이브 쿼리 식별자는 정규화된 구조화 IR에서 파생됩니다: 명시적 queryKey 또는 레거시 종속성 배열.
  • 명시적 컬렉션 사전 로딩은 정규화된 컬렉션 행을 직렬화합니다.
  • 라이브 쿼리 사전 로딩 및 렌더링 시점 검색은 정렬된 결과를 직렬화하며 소스 컬렉션은 암묵적으로 직렬화하지 않습니다.
  • 브라우저 옵저버는 일반 소스 동기화가 시작되는 동안 수화된 결과를 계속 표시한 뒤 권위 있는 핸드오프를 한 번 게시합니다.
  • 수화는 뮤테이션 핸들러를 호출하거나 낙관적 상태를 만들지 않고 행을 커밋된 동기화 상태로 적용합니다.
  • 하이드레이션과 어댑터 동기화는 결정론적인 순서로 시작됩니다. 보류 중인 행과 동기화 메타데이터를 동기화가 시작되기 전에 가져옵니다.
  • DbClient는 컬렉션 인스턴스와 주변 트랜잭션 범위를 소유하며, 정리 작업에서 두 항목을 모두 해제합니다.
  • 증분 청크는 전체 탈수와 동일한 컬렉션 페이로드 형태를 사용합니다.
  • 스트리밍된 라이브 쿼리 프라미스는 라이브 쿼리 결과 스냅샷으로 확인됩니다.

더 이상 사용되지 않음

  • useLiveQuery 및 이를 위임하는 래퍼에 대한 React 의존성 배열입니다. 여전히 작동하며 개발 환경에서 경고를 표시합니다. 1.0에서 제거될 예정입니다.

변경되지 않음

  • createCollection(...)는 계속 사용할 수 있습니다.
  • 직접 컬렉션 런타임 API를 계속 사용할 수 있습니다.
  • Vue, Solid 및 Angular는 자체 SSR/클라이언트 프로바이더 작업이 적용될 때까지 기존 의존성/반응성 모델을 유지합니다. Svelte는 이 변경 사항의 적용 대상입니다.
  • 쿼리 컬렉션 queryKey는 여전히 TanStack Query의 캐시 키입니다. React 라이브 쿼리 식별자와는 별개입니다.

검증

SSR 전략은 다음을 통해 검증됩니다.

  • 하이드레이션, 스트리밍 청크, 동기화 메타데이터, 초기 데이터 우선순위, 명시적 ID 및 낙관적 직렬화 없음에 대한 핵심 DbClient 테스트
  • DbProvider, 디스크립터 확인, 파생 쿼리 식별자, queryKey, 사용 중단 경고, SSR 결과 스냅샷 및 원자적 인계에 대한 React 테스트
  • 프로바이더 소유권, 서버 스냅샷 렌더링 및 브라우저 인계에 대한 Svelte 테스트
  • Query 캐시 동작이 계속 유지되는지 확인하는 쿼리 어댑터 테스트
  • 영속된 행 동작이 그대로 유지되는지 확인하는 영속성 코어 테스트
  • Suspense 폴백, 스트리밍된 쿼리 결과, 생략된 소스 전용 데이터, 정상적인 하이드레이션 및 브라우저 동기화에 의한 원자적 교체를 검증하는 TanStack Start 및 Next.js Playwright E2E