code/tamagui.dev/data/docs/core/server-rendering.mdx
Tamagui takes a unique approach to server-side rendering that goes beyond typical SSR solutions, ensuring perfect hydration with zero flickering even when using spring animations.
Unlike other libraries that simply render default values and re-render on the client, Tamagui uses a three-phase approach:
This approach delivers perfect hydration with zero flickering, even for complex responsive layouts with animations.
You can opt out of SSR entirely or for specific parts of your app.
For single-page applications that don't need SSR, disable it entirely in your config:
import { createTamagui } from 'tamagui'
export const config = createTamagui({
// ... other config
settings: {
disableSSR: true, // Automatically wraps app with <ClientOnly enabled>
},
})
When disableSSR is true:
<ClientOnly enabled>For fine-grained control, wrap specific parts of your tree:
import { ClientOnly } from '@tamagui/use-did-finish-ssr'
function MyComponent() {
return (
<YStack>
<ProductInfo />
<ClientOnly enabled>
<InteractiveChart />
</ClientOnly>
</YStack>
)
}
The Configuration component is essentially a shorthand for ClientOnly. It accepts configuration settings like disableSSR:
import { Configuration } from 'tamagui'
function MyApp() {
return (
<Configuration disableSSR>
<ComplexComponent />
</Configuration>
)
}
The useMedia hook automatically leverages Tamagui's smart SSR rendering. During server render, it returns appropriate defaults that match CSS media queries, then updates after hydration without flickering.
import { useMedia } from 'tamagui'
function ResponsiveComponent() {
const media = useMedia() // Automatically handles SSR correctly
return media.gtSm ? <DesktopView /> : <MobileView />
}
Returns true when hydration is complete:
import { useDidFinishSSR } from '@tamagui/use-did-finish-ssr'
function MyComponent() {
const isClient = useDidFinishSSR()
return isClient ? <BrowserFeature /> : <Placeholder />
}
Behavior:
falsetrue<ClientOnly enabled>: always trueChecks if you're in a client-only context:
import { useIsClientOnly } from '@tamagui/use-did-finish-ssr'
function MyComponent() {
const isClientOnly = useIsClientOnly()
if (isClientOnly) {
// Safe to use browser APIs
return <ComponentWithBrowserAPIs />
}
return <SSRSafeComponent />
}
Returns undefined during SSR, your value after hydration:
import { useClientValue } from '@tamagui/use-did-finish-ssr'
function WindowInfo() {
const width = useClientValue(() => window.innerWidth)
return <Text>{width ? `${width}px` : 'Loading...'}</Text>
}
Use it for:
Avoid for:
Use it for:
Example:
function ProductPage({ product }) {
return (
<YStack>
<ProductHeader product={product} />
<ClientOnly enabled>
<InteractiveGallery />
</ClientOnly>
</YStack>
)
}
useDidFinishSSR: Conditional rendering based on hydration stateuseIsClientOnly: Checking client-only context for browser APIsuseClientValue: Shorthand for client-only valuesimport {
ClientOnly, // Component for client-only subtrees
useDidFinishSSR, // Hook for hydration state
useIsClientOnly, // Hook for client-only context
useClientValue, // Hook for client-only values
} from '@tamagui/use-did-finish-ssr'