skip to Main Content

I’m interested in getting feedback on the architecture of a web app I’ve inherited. Some useful context:

  • App is client-side rendered, written in React and uses Apollo GraphQL for data fetching. SSR is out of scope and adds more complexity than can be justified for an admin tool.
  • App has a few main top-level routes. The entry point to the app is a dashboard listing a huge number of "projects". Each project has a dedicated page (let’s call it the Project Detail Page or PDP). A project consists of a bunch of project details/metadata, several assets, and posts that consume those assets.
  • Using the PDP as an example, the page makes tons of small GraphQL queries (around 30 for an average page). There’s a query to get all the project metadata. For each asset, there is one query to get the asset details and another to resolve its thumbnail. And for each post, there are also two queries.

We’re interested in improving the performance of this application. To that end, I’m curious what the current best practices are data fetching architecture here. I’ve formed an opinion on this (described below) and would like feedback on if this is a good approach or not.

Proposed Architecture

  • Having many small useQuerys distributed around the app is convenient for getting access to the relevant data in the places that care about that data. So, to that end, the current approach works nicely. I’d like to avoid implementing large scale state management (like Redux) as I think it complicates things and adds an unnecessary intermediate layer a lot of the time.
  • However, having many small queries adds a lot of network overhead and introduces a lot of layout shift as different parts of the page resolve at different times.
  • My proposal would be to create a single, larger GraphQL query for each top-level page. This query would resolve the project details which includes asset IDs and post IDs, which would then be resolved into hydrated asset/post objects too before that data is returned in one payload. That way, all the granular useQuerys distributed around the app could remain in place and would basically be hitting the Apollo cache instead of the network. In a nutshell, using one big query to hydrate the cache and using it as a state management solution almost.

Does this approach make sense? Am I not thinking of something?

2

Answers


  1. One of the strengths of GraphQL is the ability to get exactly the data that you use in the UI, fetching this data from the backend in one query. It is also a good practice to keep representational components free from side effects.
    So, your idea to fetch more data in fewer queries makes total sense.

    • You don’t need global state with apollo-client cache.
    • child components will not make network requests but will receive project assets as a props.

    There is one notable exception. Let’s say that along with your project you need to fetch a list of posts that you want to receive and display page by page. In this case, it might make sense to fetch this list in a separate query to make it easier to implement pagination

    Another things to consider:

    • You can play with fetchPolicy to control caching behaviour
    • Fragments might be helpful
    • Keep an eye on depth of your queries. You might also need to optimize your graphql backend to keep things fast.
    • You can use @defer directive to load parts of your query that is less important for user and take long to load. But it should be supported by your GraphQL backend, and as far as i know only GraphOS/GraphRouter currently suports it (apollo-server v4 just ignore it).
    Login or Signup to reply.
  2. Just a couple of points to complement Slava’s already very accurate answer:

    • Using the @client directive, you can add local-only fields to your queries, making Apollo an advantageous solution for global state management.
    • While Slava’s top-down approach is generally excellent (one top-level query with children receiving data as props on a per-need basis), some components might benefit from retaining the ability to make their own requests, e.g. for data you know will update very frequently (think shop items stock inventory, user comments/reactions/likes, etc.) – although arguably this type of data would be best handled differently to begin with anyway (pub/sub, web sockets, etc.). Not sure this applies in your use case, but worth mentioning.
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search