T
TanStack•10mo ago
inland-turquoise

SSR with HydrationBoundary doesn't work, client fetches the data

What I expect: /feedback page loads with prefetched data on the server, meaning the data is in the page source code and there is no additional network request to /api/feedback on the client. I am importing this into the page and wrapping the page with it.
const PrefetchBoundary = async ({ children }: { children: React.ReactNode }) => {
// Create a new QueryClient for server-side prefetching
const queryClient = new QueryClient()

await queryClient.prefetchQuery({
queryKey: config.queryKey,
queryFn: config.queryFn
})


return (
<HydrationBoundary state={dehydrate(queryClient)}>
{children}
</HydrationBoundary>
)
}
const PrefetchBoundary = async ({ children }: { children: React.ReactNode }) => {
// Create a new QueryClient for server-side prefetching
const queryClient = new QueryClient()

await queryClient.prefetchQuery({
queryKey: config.queryKey,
queryFn: config.queryFn
})


return (
<HydrationBoundary state={dehydrate(queryClient)}>
{children}
</HydrationBoundary>
)
}
In the feedback page.tsx
export default async function FeedbackPage() {
return (
<PrefetchBoundary>
<div className="min-h-screen bg-primary-light/50">
...some JSX

<FeedbackForm />
<FeatureRequests />
</main>
</div>
</PrefetchBoundary>
);
}
export default async function FeedbackPage() {
return (
<PrefetchBoundary>
<div className="min-h-screen bg-primary-light/50">
...some JSX

<FeedbackForm />
<FeatureRequests />
</main>
</div>
</PrefetchBoundary>
);
}
and in FeatureRequests.tsx I call useQuery and render the data
const { data: featureRequests, isLoading, error } = useFeatureRequestsQuery();
const { data: featureRequests, isLoading, error } = useFeatureRequestsQuery();
Now when I load this page and show source code, the feature requests are not there and the loading spinner shows. I thought the whole point of SSR should be this doesnt happen, I load the page with the data already. What am I missing here?
14 Replies
robust-apricot
robust-apricot•10mo ago
staleTime ?
fair-rose
fair-rose•10mo ago
Ya first thought is that you need to add:
new QueryClient({
defaultOptions: {
queries: {
// With SSR, we usually want to set some default staleTime
// above 0 to avoid refetching immediately on the client
staleTime: 60 * 1000,
},
},
}),
new QueryClient({
defaultOptions: {
queries: {
// With SSR, we usually want to set some default staleTime
// above 0 to avoid refetching immediately on the client
staleTime: 60 * 1000,
},
},
}),
inland-turquoise
inland-turquoiseOP•10mo ago
How would staletime explain the data is not in the "View Page Source"? I tried setting staleTime in the boundary to 1 minute (that is my default in the QueryClientProvider) but changed nothing.
fair-rose
fair-rose•10mo ago
TkDodo is the big guns here but my thought is that you need to add the staleTime to the new QueryClient that you are passing into the hydration boundary as well.
inland-turquoise
inland-turquoiseOP•10mo ago
Also fyi I am using static pages with yarn build, but this route has dynamic on demand rendering. I dont know if that affects anything, if I switch the whole project into SSR (used to be called getInitialProps) or if that is even a thing in next 14+ and app router.
fair-rose
fair-rose•10mo ago
O wait ya nvm what I said, got confused for a minute, I don't think you need the staletime for the hydration boundary queryClient
fair-rose
fair-rose•10mo ago
Hmm ya not sure. @TkDodo 🔮 could add some more insights but the things I would check: 1. I'm not sure how reliable looking at the page source would be. Possibly using the https://tanstack.com/query/latest/docs/framework/react/devtools would be a better option to get a better picture of how things are working 2. I'd confirm that you are correctly using the query keys and that they exactly match as it is odd that even with the stale time that it is refetching immediately on the client side 3. I'm not exactly sure what you mean by "dynamic on demand rendering". Could you give an example of what you mean?
Devtools | TanStack Query React Docs
Wave your hands in the air and shout hooray because React Query comes with dedicated devtools! 🥳 When you begin your React Query journey, you'll want these devtools by your side. They help visualize...
inland-turquoise
inland-turquoiseOP•10mo ago
1. How can I check with dev tools that data is stored in query client from the server side render? 2.yes query keys are the same, I have them saved in an object specifically for this reason, I import the object and pass the property in useQuery
export const feedbackKeys = {
all: ['feedback'] as const,
features: () => [...feedbackKeys.all, 'features'] as const,
feature: (id: string) => [...feedbackKeys.features(), id] as const,
} as const;
export const feedbackKeys = {
all: ['feedback'] as const,
features: () => [...feedbackKeys.all, 'features'] as const,
feature: (id: string) => [...feedbackKeys.features(), id] as const,
} as const;
3. I mean this section you get when you run "next build" ○ (Static) prerendered as static content ƒ (Dynamic) server-rendered on demand I am just saying this /feedback route is dynamic
fair-rose
fair-rose•10mo ago
I could be wrong here but if you are just using yarn build, wouldn't it not have access to any server abililties as you aren't running the server? Don't you need to be running next dev?
inland-turquoise
inland-turquoiseOP•10mo ago
Ofc I run the server after with "next start". I also use "next dev" it is the same. The data are not returned in the html from the server.
fair-rose
fair-rose•10mo ago
gotcha. To answer the devtools question: I would remove the useQuery call on the client side so that the only data that should be in the cache would be from the server call just to make sure that that data is being hydrated into the cache. But ya I'm kinda stumped after that
inland-turquoise
inland-turquoiseOP•10mo ago
I did what you said and the dev tools are empty
fair-rose
fair-rose•10mo ago
Can you show me where you have the <QueryClientProvider client={queryClient}>?
inland-turquoise
inland-turquoiseOP•10mo ago
providers.tsx
import {
isServer,
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query'


function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1 minute
refetchOnWindowFocus: false,
},
},
})
}

let browserQueryClient: QueryClient | undefined = undefined

export function getQueryClient() {
if (isServer) {
// Server: always make a new query client
return makeQueryClient()
} else {
// Browser: make a new query client if we don't already have one
if (!browserQueryClient) browserQueryClient = makeQueryClient()
return browserQueryClient
}
}

interface ProvidersProps {
children: ReactNode
}

export default function Providers({ children }: ProvidersProps) {
const queryClient = getQueryClient()

return (
<CSPostHogProvider>
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</CSPostHogProvider>
)
}
import {
isServer,
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query'


function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1 minute
refetchOnWindowFocus: false,
},
},
})
}

let browserQueryClient: QueryClient | undefined = undefined

export function getQueryClient() {
if (isServer) {
// Server: always make a new query client
return makeQueryClient()
} else {
// Browser: make a new query client if we don't already have one
if (!browserQueryClient) browserQueryClient = makeQueryClient()
return browserQueryClient
}
}

interface ProvidersProps {
children: ReactNode
}

export default function Providers({ children }: ProvidersProps) {
const queryClient = getQueryClient()

return (
<CSPostHogProvider>
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</CSPostHogProvider>
)
}
OK so I figured it out.... I didn't have await before queryClient.prefetchQuery({ queryKey: config.queryKey, queryFn: config.queryFn }) Classic. oh well. Sorry for wasting your time.

Did you find this page helpful?