new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

TanStack Query · React · all subjects

ssr/server-components

35 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

Server Components always run on server

Server Components are guaranteed to only run on the server, both for the initial page view and also on page transitions. This is similar to how Next.js getServerSideProps/getStaticProps and Remix loader works.

Server Components vs Client Components execution

Server Components are guaranteed to only run on the server, but Client Components can actually run in both places because they can also render during the initial server rendering pass. Server Components render during a 'loader phase' that always happens on the server, while Client Components run during the 'application phase' which can run both on the server during SSR and in a browser.

QueryClientProvider setup for Server Components

With Server Components, create a QueryClientProvider in a 'use client' file (e.g., app/providers.tsx in Next.js). Use environmentManager.isServer() to detect the environment and create a new QueryClient on the server for each request, while maintaining a single browserQueryClient in the browser to avoid re-creating it during React suspensions. The provider should avoid useState when initializing the query client if there is no suspense boundary below it, because React will discard the client on the initial render if it suspends and there is no boundary.

Prefetching with Server Components using HydrationBoundary

In a Server Component page, use queryClient.prefetchQuery() to fetch data, then pass the dehydrated state to HydrationBoundary. For example, in app/posts/page.tsx create a QueryClient, await queryClient.prefetchQuery() with the query key and function, then wrap the client components with HydrationBoundary using the dehydrated state. The actual useQuery calls happen in Client Components that are children of HydrationBoundary.

Avoid Server Actions for queryFn data fetching

Do not use Next.js Server Actions to fetch data in a queryFn. Server Actions run serially when called from the client, which conflicts with how React Query fetches and refetches queries in parallel, leaving queries stuck in a pending state or causing the action to never run. Additionally, passing a Server Action reference to queryFn can fail with 'Only plain objects, and a few built-ins, can be passed to Server Actions' error. For client-side data fetching, use fetch from an API route or an RPC layer such as tRPC instead. Server Actions remain a good fit for mutations.

Nesting Server Components with prefetching

Server Components can be nested and prefetch data closer to where it is used instead of only at the top of the application. Each Server Component can have its own QueryClient and HydrationBoundary. However, awaiting prefetches sequentially creates server-side waterfalls. In Next.js, if nested Server Components are expressed as parallel routes instead, the waterfall is flattened automatically because Next.js knows how to fetch them in parallel.

Single queryClient for prefetching with React.cache()

Alternatively to creating a new QueryClient for each Server Component, you can use React's cache() function to create a single queryClient that is reused across all Server Components. cache() is scoped per request so data does not leak between requests. The benefit is you can call getQueryClient() anywhere in utility functions. The downside is that every dehydrate() call serializes the entire queryClient including already-serialized queries unrelated to the current Server Component, creating unnecessary overhead.

Data ownership issue with Server and Client Components

When rendering data from the same query in both a Server Component and a Client Component, the data will be in sync on initial page render. However, when the query revalidates on the client after staleTime passes, React Query has no way to revalidate the Server Component. If React Query refetches and rerenders the client side, the Server Component data will become out of sync. This is why treating Server Components as a place to prefetch data only, not to render the results, is recommended.

When to use React Query with Server Components

React Query with Server Components makes most sense if: (1) migrating an existing React Query app to Server Components without rewriting data fetching, (2) wanting a familiar programming paradigm while sprinkling in Server Component benefits where it makes sense, or (3) having some use case React Query covers that your framework does not. For new Server Components apps, start with tools your framework provides and avoid React Query until actually needed. Avoid queryClient.fetchQuery unless catching errors, and if used, don't render its result on the server or pass it to another component.

Server-side retry default

On the server, retries default to 0 to make server rendering as fast as possible.

Server Components mitigate dependent query waterfalls

Server Components can mitigate dependent query waterfalls by moving the waterfall to the server where latency is lower. They also avoid the tradeoff between including all data fetching code in the main bundle versus putting it in code-split bundles with waterfalls.

QueryClient must be created inside component for SSR

When using server rendering with React Query, create a new QueryClient instance inside your app component in React state, not at the file root level. This ensures that data is not shared between different users and requests, while still only creating the QueryClient once per component lifecycle. Creating the QueryClient at file root level makes the cache shared between all requests and means all data gets passed to all users, which is bad for performance and can leak sensitive data.

Default staleTime for SSR should be above 0

With server-side rendering, set a default staleTime above 0 (such as 60 * 1000 milliseconds) to avoid refetching immediately on the client after the page loads. This prevents unnecessary double fetching after the server has already provided the data.

Three queryClient instances involved in SSR hydration

There are actually three separate queryClient instances involved in server rendering: one in the framework loader's preloading phase that does the prefetching, and separate instances in both the server rendering process and client rendering process. Each server and client rendering process has its own queryClient, ensuring they both start with the same dehydrated data so they can return the same markup.

SSR with initialData quick start approach

The quickest way to get started with SSR is to pass raw data as the initialData option to useQuery instead of using the dehydrate/hydrate APIs. This requires minimal setup: fetch data in getServerSideProps or a loader function and pass it as initialData prop to useQuery with the matching queryKey.

Drawbacks of initialData approach for SSR

Using initialData for SSR has several drawbacks compared to the full hydration approach: (1) initialData must be passed down the component tree, making it cumbersome for deeply nested components; (2) if useQuery is called with the same query in multiple locations, you must pass initialData to all of them or risk brittleness when components are moved; (3) there is no way to know when the query was fetched on the server, so dataUpdatedAt and staleness checks are based on page load time instead; (4) if data already exists in the cache, initialData will never overwrite it even if the new data is fresher, which prevents cache updates on repeated navigations.

Three-step hydration process for SSR

The hydration process has three main steps: (1) In the framework loader function, create a new QueryClient and call await queryClient.prefetchQuery(...) for each query to prefetch, using await Promise.all(...) to fetch queries in parallel when possible; (2) Return dehydrate(queryClient) from the loader function; (3) Wrap your component tree with <HydrationBoundary state={dehydratedState}> where dehydratedState comes from the loader.

Next.js pages router SSR example with hydration

Example of SSR with hydration in Next.js pages router. In _app.tsx: create a QueryClient in state with staleTime of 60 * 1000, wrap with QueryClientProvider and HydrationBoundary passing pageProps.dehydratedState. In the route page, export getStaticProps or getServerSideProps that creates a QueryClient, calls await queryClient.prefetchQuery({queryKey: ['posts'], queryFn: getPosts}), and returns {props: {dehydratedState: dehydrate(queryClient)}}. The component uses useQuery with matching queryKey and queryFn.

Remix SSR example with hydration

Example of SSR with hydration in Remix. In app/root.tsx: create a QueryClient in state with staleTime of 60 * 1000, wrap with QueryClientProvider and HydrationBoundary. In the route, export a loader function that creates a QueryClient, calls await queryClient.prefetchQuery({queryKey: ['posts'], queryFn: getPosts}), and returns json({dehydratedState: dehydrate(queryClient)}). In the component, use useLoaderData to get dehydratedState and wrap with HydrationBoundary, then use useQuery with matching queryKey and queryFn.

Removing HydrationBoundary boilerplate in Next.js

To remove boilerplate of wrapping each route with HydrationBoundary, modify _app.tsx to wrap the entire Component with HydrationBoundary using pageProps.dehydratedState, then each page can export its component directly without the extra wrapper.

Prefetching dependent queries in SSR loaders

To prefetch dependent queries in SSR loaders, use queryClient.fetchQuery instead of prefetchQuery for the first query to get the result, check the result for dependencies, then conditionally call queryClient.prefetchQuery for dependent queries. For example: const user = await queryClient.fetchQuery({...}); if (user?.userId) { await queryClient.prefetchQuery({...for projects...}) }

prefetchQuery never throws errors in SSR

React Query defaults to graceful degradation for SSR: queryClient.prefetchQuery(...) never throws errors and dehydrate(...) only includes successful queries, not failed ones. Failed queries are retried on the client and server-rendered output includes loading states instead of full content.

fetchQuery for critical content error handling in SSR

Use queryClient.fetchQuery(...) instead of prefetchQuery when you need to throw errors for critical content. fetchQuery will throw on failure, allowing you to handle errors in a suitable way such as responding with 404 or 500 status codes.

Including failed queries in dehydrated state

By default, dehydrate(...) only includes successful queries. To include failed queries in the dehydrated state and avoid retries, use the shouldDehydrateQuery option: dehydrate(queryClient, {shouldDehydrateQuery: (query) => true}) to include all queries, or implement custom logic by inspecting the query object.

Serialization unsafe values in SSR

By default, frameworks like Next.js and Remix do not support returning undefined, Error, Date, Map, Set, BigInt, Infinity, NaN, -0, or regular expressions from queries when serializing dehydratedState. Use packages like superjson to handle these types if needed.

XSS vulnerability in custom SSR serialization

When using custom SSR setup, do not use JSON.stringify(dehydratedState) directly as it does not escape values like <script>alert('Oh no..')</script>, leading to XSS vulnerabilities. superjson also does not escape values. Instead use libraries like Serialize JavaScript or devalue which are safe against XSS injections out of the box.

Server rendering flattens request waterfalls for initial load

Server rendering can flatten complex request waterfalls for the initial page load. Instead of the client-rendered waterfall (markup -> JS -> query), server rendering produces (markup with content AND data -> JS). Queries are fetched on the server and data is included in the markup, so client-side fetching is not needed until revalidation.

SPA navigation reverts to client-side waterfalls

Server rendering benefits only apply to the initial page load in SPAs. When navigating between pages via links after the initial load, the request waterfall returns to client-side fetching. Modern frameworks like Next.js and Remix with prefetching patterns can fetch JS and data in parallel to mitigate this.

Staleness measured from server fetch time in SSR

In SSR, query staleness is measured from when dataUpdatedAt was set, which is when the query was fetched on the server. Since staleTime defaults to 0, queries will be refetched in the background on page load by default. Use a higher staleTime to avoid double fetching. The server must have correct UTC time for this to work properly.

CDN caching with background data revalidation strategy

A good pattern for SSR with CDN caching is to set a high cache time on the page itself to avoid re-rendering pages on the server, but configure a lower staleTime on queries to trigger background refetching. For example, cache pages for a week but refetch data on page load if older than a day.

High memory consumption on server with QueryClient

Creating a QueryClient for every request in SSR can lead to high memory consumption because React Query creates an isolated cache preserved in memory for the gcTime period. On the server, gcTime defaults to Infinity, which disables manual garbage collection and automatically clears memory once a request finishes.

Minimum gcTime value for SSR hydration

Avoid setting gcTime to 0 in SSR as it may result in hydration errors because HydrationBoundary places necessary data into the cache for rendering, but if garbage collection removes data before rendering completes, issues arise. If a shorter gcTime is required, set it to at least 2 * 1000 milliseconds to allow sufficient time for the app to reference the data.

Clearing QueryClient cache to reduce server memory

To reduce memory consumption on the server, call queryClient.clear() after the request is handled and dehydrated state has been sent to the client. This clears the cache after it is no longer needed.

Next.js rewrites cause second hydration by React Query

Using Next.js rewrites feature together with Automatic Static Optimization or getStaticProps causes a second hydration by React Query. This happens because Next.js must parse the rewrites on the client and collect params after hydration to provide them in router.query. This results in missing referential equality for hydration data, triggering re-renders in useEffect dependency arrays and memoization.

useSuspenseQuery with SSR requires all queries prefetched

It is possible to use useSuspenseQuery instead of useQuery with SSR as long as all queries are always prefetched. If you forget to prefetch a query when using useSuspenseQuery, the data may suspend and get fetched on the server but never hydrated to the client, causing it to fetch again and creating a markup hydration mismatch.

Give your agent this brain