---
title: Query optimization recommendations
description: Learn how to efficiently fetch data from WordPress
url: https://developers.wpengine.com/docs/headless-platform/additional-guides/query-optimization/
image: https://developers.wpengine.com/og/docs/headless-platform/additional-guides/query-optimization.png
---

# Query optimization recommendations

## API Performance Optimizations

If your queries are taking too long, consider optimizing them by reducing complexity, avoiding unnecessary fields, and leveraging caching where possible. Use tools and techniques such as ACF Local JSON and defining GraphQL types to enhance performance.

### ACF: Use Local JSON

Local JSON is a helpful feature that saves field groups, post types, taxonomies, and option pages as JSON files within your theme. Similar to caching, it dramatically speeds up ACF and allows for version control over your field settings. Each time you save a field group, post type, taxonomy, or options page, a JSON file will be created or updated with the settings for the item being saved. By default, the JSON file will be named using the unique key for that item.

Typically, we recommend making changes to ACF field groups in local development and deploying the updated JSON files to WP Engine. No sync is required on WP Engine as long as changes to field groups are not being made there.

[ACF Local JSON Documentation](https://www.advancedcustomfields.com/resources/local-json/)Learn how to implement and customize Local JSON for ACF to improve performance and version control.

### ACF: Define graphql_types on each field group

If you are using GraphQL in your API requests along with the WPGraphQL plugin and its WPGraphQL for ACF extension, you can improve performance by manually setting `graphql_types` for each field group. This bypasses expensive logic and improves the performance of GraphQL queries involving field groups.

To do this, visit the `GraphQL` section in your ACF plugin settings and configure the `graphql_types` field for each group.

![graphql_types in field groups](https://developers.wpengine.com/_astro/graphql-types.Djf4SJEM_ZfXxqI.webp)

### Reduce or Eliminate Meta Queries

[Meta queries](https://developer.wordpress.org/reference/classes/wp_query/#custom-field-post-meta-parameters) have a notorious reputation within the WordPress world due to the impact they have on performance (both in headless and monolithic architectures). These queries search for metadata and can be slow and resource-intensive.

In building out your data models, consider how you may avoid the need for such queries by replacing them with taxonomies (including [utility taxonomies](https://tomjn.com/2018/03/16/utility-taxonomies/)) or ACF relationship fields instead.

For example, you may initially consider a “Feature on Homepage” boolean field for blog posts and a meta query to find all blog posts where that field is set to “true”.

An alternative, more scalable solution may use an ACF relationship field on the Homepage of “Featured Blog Posts”. Another approach could be to use a tag/category taxonomy with a term of “featured”. For a site with a large number of blog posts, both of the alternatives would scale much better than the initial boolean field approach.

## API Cache Hit Optimizations

If your cache hit rate is too low, optimize by using persisted queries and reducing authenticated requests. This helps improve performance and scalability.

### Use WPGraphQL Smart Cache and Persisted Queries

By default, most WPGraphQL clients make POST requests. POST requests are not cacheable due to the nature of such requests. In such clients, you can convert those POST requests to GET requests which encodes the query into URL parameters, but many GraphQL queries may be so large that [HTTP 414](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/414) errors are encountered.

Persisted queries allows your GraphQL client to use a POST request for the initial caching of a request, but then use a hashed ID of a given request so that request URIs are never too long. These queries can then be served from server cache on WP Engine, and the [WPGraphQL Smart Cache](https://wordpress.org/plugins/wpgraphql-smart-cache/) plugin can intelligently invalidate the appropriate responses as the underlying content changes (e.g. publishing a new blog post or updating a page title). The WP Engine platform currently leverages your WordPress server’s HTTP cache (Varnish) to cache GET requests to WPGraphQL. Our Advanced Network CDN (CloudFlare) does not yet support caching WPGraphQL requests.

We often see folks not using Persisted Queries with cache hit ratios of < 10%. Those who are using Persisted Queries and do not need to make authenticated requests often have cache hit ratios of 60-80%. As you can imagine, this has a dramatic impact on the performance and scalability of your CMS!

For more details on how to implement Persisted Queries in your application, visit the “Persisted Queries” section of [this page](https://www.wpgraphql.com/2022/12/20/introducing-wpgraphql-smart-cache).

[Introducing WPGraphQL Smart Cache](https://www.wpgraphql.com/2022/12/20/introducing-wpgraphql-smart-cache)Learn about WPGraphQL Smart Cache and how it can help optimize caching for your headless WordPress site.

[WPGraphQL Smart Cache Optimization on WP Engine Managed Hosting](https://developers.wpengine.com/docs/headless-platform/platform-guides/smart-cache-optimization/)Optimize caching for headless WordPress using WPGraphQL Smart Cache on WP Engine's Managed Hosting platform.

### Reduce authenticated requests

There may be some cases (depending on the nature of a given project) where authentication is required. For example, authentication is required if you need to fetch the cart contents for an e-commerce site or display a user profile page.

However, authentication also means the responses will never be cached. This can have a major impact on the performance of your site! Often, even on sites that require authentication for some content, there is much content that is universal - things such as navigation bar, footer, or blog/page content, etc.

So if your site must have authentication for some queries, make sure you are splitting them apart from the queries that don’t need it and use separate instances of your GraphQL client.

For more info about splitting up your queries, keep reading!

### Split large queries into smaller, focused queries

[WPGraphQL Smart Cache](https://wordpress.org/plugins/wpgraphql-smart-cache/) generates cache keys from the combination of a query’s fields and its variables. When a single query fetches many unrelated resources, any change to one of those resources invalidates the entire cached response — even if most of the data hasn’t changed.

Splitting queries by data volatility ensures that infrequently changing data (like site settings or navigation) stays cached independently from frequently changing data (like page content).

**Before** — a single monolithic query:

```graphql
query GetPageData($uri: String!) {
  page(id: $uri, idType: URI) {
    title
    content
    featuredImage {
      node {
        sourceUrl
      }
    }
  }
  generalSettings {
    title
    description
  }
  menus {
    nodes {
      menuItems {
        nodes {
          label
          url
        }
      }
    }
  }
}
```

Publishing any page invalidates this entire cached response, including the settings and menus that haven’t changed.

**After** — split into focused queries:

```graphql
query GetPage($uri: String!) {
  page(id: $uri, idType: URI) {
    title
    content
    featuredImage {
      node {
        sourceUrl
      }
    }
  }
}


query GetSiteSettings {
  generalSettings {
    title
    description
  }
}


query GetNavMenus {
  menus {
    nodes {
      menuItems {
        nodes {
          label
          url
        }
      }
    }
  }
}
```

Now publishing a page only invalidates the `GetPage` cache for that specific URI. The `GetSiteSettings` and `GetNavMenus` responses remain cached until their underlying data actually changes.

> **Tip**
>
> If you’re using Faust.js, you can pass multiple queries as an array to execute them in parallel. See the [acf.wpgraphql.com front-page example](https://github.com/wp-graphql/acf.wpgraphql.com/blob/22e8078cc2adf6e3ad88b285a25dc2780e164172/wp-templates/front-page.js#L74-L82) for implementation details.

## Was this helpful?

Thanks for the feedback!

```json
{"@context":"https://schema.org","@graph":[{"@type":"WebSite","@id":"https://developers.wpengine.com/#website","name":"WP Engine Product Documentation","url":"https://developers.wpengine.com/","description":"Resources for developing, deploying, and scaling on WP Engine.","inLanguage":"en-US","keywords":["API documentation","product documentation","headless WordPress","managed WordPress hosting"],"publisher":{"@id":"https://wpengine.com/#organization"}}]}
```
