NextJS / React SSR: 21 Universal Data Fetching Patterns & Best Practices - WunderGraph
Jens Neuse
CEO & Co-Founder at WunderGraph
May 1, 2022·53min read
Last updated on September 9, 2025
This article is archived and no longer maintained. It describes an earlier version of WunderGraph, including experimental features that are no longer part of the current product. The concepts and examples may not work as described. For current documentation and guidance, see https://wundergraph.com/cosmo.
A frontend developer should be able to define what data is needed for a given page, without having to worry about how the data actually gets into the frontend.
NextJS Universal Data Fetching
My definition of universal data fetching is that you can put a data-fetching hook anywhere in your application, and it would just work. This data fetching hook should work everywhere in your application without any additional configuration.
Example of a "Universal Subscription" Hook
const PriceUpdates = () => {
const data = useSubscription.PriceUpdates()
return (
<div>
<h1>Universal Subscription</h1>
<p>{JSON.stringify(data)}</p>
</div>
)
}
The "PriceUpdates" hook is generated by our framework as we've defined a "PriceUpdates.graphql" file in our project. What's special about this hook? You're free to put React Component anywhere in your application. By default, it will server-render the first item from the subscription. The server-rendered HTML will then be sent to the client, alongside with the data. The client will re-hydrate the application and start a subscription itself.
Why Data Fetching in NextJS is So Hard?
getServerSideProps Only Works on Root Pages
By default, the only place where you can use async functions to load data that is required for server-side rendering is at the root of each page. Here’s an example from the NextJS documentation:
function Page({ data }) {
// Render data...
}
// This gets called on every request
export async function getServerSideProps() {
const res = await fetch(`https://.../data`)
const data = await res.json()
return { props: { data } }
}
export default Page
Authentication Adds Additional Complexity to Data Fetching
Most applications have some sort of authentication mechanism. There might be some content that is publicly available, but what if you want to personalize a website?
Type-Safety Is Needed to Avoid Bugs
As we've seen in the example above using "getServerSideProps", we need to take additional actions to make our API layer type-safe. Wouldn't it be better if the data-fetching hooks were type-safe by default?
Common Data Fetching Challenges
- getServerSideProps only works on root pages
- Authentication-aware data-fetching hooks
- Type-safety in data fetching
- Handling subscriptions and SSR
- Managing window focus and blur in fetching
- Side effects of mutations on data-fetching hooks
- Lazy loading data
- Debouncing query executions
Summary of the Biggest Challenges
Here, we summarized the 8 core problems that need to be solved in building data-fetching hooks for NextJS. Following this, we provide 21 patterns and best practices for addressing these issues.
21 Patterns and Best Practices
Visit this demo repository to experience the patterns explained in this section.
Client-Side User: A hook to fetch current users, especially when the server has not fetched them yet.
Server-Side User: Loading the user on the server to avoid flickering effects.
Universal User: Combines the prior patterns to load and manage user data efficiently.
Refetch User on Window Focus: Automatically fetches user data again when the window regains focus.
Client-Side Query: Basic setup for executing queries on the client side.
Server-Side Query: Extends queries to execute on the server.
Refetch Query on Window Focus: Automatically refetches data when the window regains focus.
Lazy Query: Fetches data only after a specific event occurs.
Debounce Query: Implements debouncing for user inputs to minimize fetching requests.
Protected Query: Ensures user authentication when making queries.
Universal Protected Query: Integrates server and client-side protections for queries.
Client-Side Mutation: Handles mutations without automatic execution.
Protected Mutation: Ensures authentication checks for mutations.
Refetch Mounted Operations on Mutation Success: Invalidates relevant queries after a mutation.
Client-Side Subscription: Auto-fetches data through subscriptions.
Stop Subscription on Window Blur: Manages subscriptions based on window focus.
Universal Subscription: Manages subscriptions consistently across server and client.
Protected Subscription: Handles user authentication in subscriptions.
Client-Side Live Query: Polling mechanism for real-time updates without real-time connections.
Universal Live Query: Helps to achieve real-time data without complex setups.
Improving Performance: Discuss various ways to enhance Fetch performance, including methods through Static Site Generation and Incremental Static Regeneration, and embedding GraphQL fragments for efficiency.
Alternative Approaches and Technologies in Data Fetching
- SWR: Offers flexibility but may lead to compromising performance if not used correctly with SSR.
- NextAuth.js: A robust autheticatication library, but may lead to issues in scaling.
- tRPC: Similar hooks to WunderGraph but with less focus on GraphQL.
- GraphQL Fragments: Reduces load through optimized queries, allowing components to request data flexibly.
Conclusion
Understanding and implementing universal data-fetching hooks in NextJS requires thoughtfulness and an appreciation of the nuances of each approach. Utilizing the patterns discussed and employing best practices can streamline the efficiency of applications significantly.