TanStack Server Components: Opt-In Granularity Beats Next.js
Use renderServerComponent in server functions to render React components on the server granularly, like fetching JSON. Composite components with slots keep client boundaries clean without 'use client' directives.
Opt-In Server Rendering via Server Functions
Render React components on the server explicitly by wrapping them in renderServerComponent inside a server function, then fetch like JSON data. This runs server-only code (e.g., os.hostname(), env vars) without changing your client-first app structure. Pass computed values as props to keep components pure and reusable on client or server. In a TanStack Start route loader, await the server function and use loaderData to render: the boundary stays clear since server logic lives solely in the function. Replace server components with JSON fetches seamlessly—no framework overhaul required, unlike Next.js's server-first tree where 'use client' opts out interactivity.
For multiple components, use Promise.all in one server function or return promises directly for streaming. Client-side, wrap in Suspense for skeletons: slow components load independently without blocking the page.
Composite Components Eliminate Server-Controlled Client Trees
Avoid nesting 'use client' components inside server renders, which muddies boundaries (e.g., tracing counter logic requires jumping files). Instead, return createCompositeComponent from server functions, defining slots for client content. Fetch the source in loaders, then render with <Composite source={greetingSource}>—server defines structure and data passthrough, but clients fill slots.
This keeps all client decisions in client code: server sees opaque slots, executes no client functions. Integrates with TanStack Query for caching/CDN simplicity. Result: explicit server/client separation, no 'use client' needed for slots.
Slot Patterns for Flexible Client Integration
Use three slot types to inject client components without server knowledge of their identity:
- Children slots: Pass
{children}prop; server forwards anything provided (e.g., counter under greeting). - Render props: Define function props (e.g.,
renderActions?: (postId: string, authorId: string) => ReactNode). Server calls optionally with data (e.g., above post title); client provides function returning component. Opaque to server—runs client-side, avoids double-fetches (author data passes directly). - Component props: Pass component directly (e.g.,
PostActions: Component<{postId: string, authorId: string}>). Server renders<PostActions {...data} />as placeholder. Swap implementations client-side (e.g.,FakePostActionsworks identically). Prefer render props if needing data transforms; component props for direct passthrough.
These solve real issues: targeted actions (like/follow) without children dumping at bottom, data sharing without client refetching. Server provides data/slots; client handles rendering—reverses Next.js logic for better DX and modularity.