서버 렌더링 및 하이드레이션
Lit Query는 Lit SSR과 @tanstack/lit-query에서 다시 내보내는 TanStack Query Core 하이드레이션 API를 결합하여 서버 렌더링과 함께 사용할 수 있습니다.
이 가이드의 실행 가능한 소스는 SSR 예제입니다.
흐름
서버 렌더링은 세 단계로 구성됩니다:
- 요청별
QueryClient를 생성합니다. - 서버에서 쿼리를 프리페치하고 해당 클라이언트로 Lit HTML을 렌더링합니다.
- 캐시를 HTML로 디하이드레이션한 다음, 클라이언트 앱이 렌더링되기 전에 브라우저
QueryClient를 하이드레이션합니다.
사용자 또는 요청 간에 하나의 서버 QueryClient를 절대 공유하지 마세요.
서버 프리페치 및 렌더링
import { render } from '@lit-labs/ssr'
import { collectResult } from '@lit-labs/ssr/lib/render-result.js'
import { html } from 'lit'
import { QueryClient, dehydrate, noop } from '@tanstack/lit-query'
import { createDataQueryOptions } from './api.js'
import './app.js'
async function renderPage() {
const apiBaseUrl = 'https://example.com'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 30_000,
},
},
})
await queryClient.query(createDataQueryOptions(apiBaseUrl)).catch(noop)
const appHtml = await collectResult(
render(
html`<ssr-app
api-base-url=${apiBaseUrl}
.queryClient=${queryClient}
></ssr-app>`,
),
)
const dehydratedState = dehydrate(queryClient)
return { appHtml, dehydratedState }
}
서버는 property binding을 사용하여 동일한 client를 Lit 요소에 전달합니다. 이를 통해 createQueryController가 서버 렌더링 중 프리페치된 캐시를 읽을 수 있습니다. SSR 중에 쿼리 함수가 fetch를 호출한다면 브라우저 기준 상대 URL에 의존하지 말고 절대 API origin을 전달합니다.
클라이언트 하이드레이션
import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
import { QueryClient, hydrate, type DehydratedState } from '@tanstack/lit-query'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 30_000,
},
},
})
const dehydratedState = JSON.parse(
document.getElementById('__QUERY_STATE__')?.textContent ?? 'null',
) as DehydratedState
queryClient.mount()
hydrate(queryClient, dehydratedState)
const appElement = document.querySelector('ssr-app') as
| (HTMLElement & { queryClient?: QueryClient })
| null
if (!appElement) {
throw new Error('Expected the SSR app element to exist before hydration.')
}
appElement.queryClient = queryClient
await import('./app.js')
클라이언트를 수동으로 마운트했다면 페이지가 언로드될 때 언마운트합니다:
window.addEventListener(
'pagehide',
() => {
queryClient.unmount()
},
{ once: true },
)
컴포넌트 패턴
SSR 예시는 queryClient 속성을 사용할 수 있게 된 후에만 컨트롤러를 생성합니다:
import { LitElement } from 'lit'
import {
createQueryController,
type QueryClient,
type QueryResultAccessor,
} from '@tanstack/lit-query'
import { createDataQueryOptions, type DataResponse } from './api.js'
class SsrApp extends LitElement {
static properties = {
apiBaseUrl: { attribute: 'api-base-url' },
queryClient: { attribute: false },
}
apiBaseUrl = ''
queryClient?: QueryClient
private dataQuery?: QueryResultAccessor<DataResponse, Error>
protected override willUpdate(): void {
if (!this.dataQuery && this.queryClient) {
this.dataQuery = createQueryController(
this,
createDataQueryOptions(this.apiBaseUrl),
this.queryClient,
)
}
}
}
이 명시적 클라이언트 패턴은 연결된 DOM provider에서 클라이언트를 찾는 대신 renderer가 클라이언트를 생성하므로 SSR에 유용합니다.
직렬화
디하이드레이션된 상태를 JSON으로 HTML에 삽입하고, script 태그를 벗어날 수 있는 문자를 이스케이프합니다. 예제 서버는 빌드된 HTML 템플릿에서 __QUERY_STATE_JSON__을 교체하기 전에 작은 직렬화 도구를 사용합니다.
Lit Query는 TanStack Query Core의 dehydrate와 hydrate를 다시 내보냅니다. 서버 프리페치 후 dehydrate(queryClient)를 사용하여 캐시 상태를 캡처합니다. 브라우저에서 해당 상태를 파싱하고, 새로운 QueryClient를 생성하고, hydrate(queryClient, dehydratedState)를 호출하고, 서버에서 렌더링된 요소에 client를 할당한 다음, 프리페치된 캐시를 사용할 수 있는 상태로 업그레이드되도록 그 이후에만 Lit component를 import합니다.