Skip to main content

Key optimization parameters

1. The exclude_body parameter

The exclude_body=true parameter reduces response size by 50-70% for blog post listing operations. This is one of the most impactful optimizations you can make. When to Use:
  • Blog post listing pages
  • Archive pages
  • Search results
  • RSS feed generation
  • Related posts widgets
When NOT to Use:
  • Individual post pages (you need the body)
  • Full-text search implementations
  • Content that displays the full article
Example:
// Good - for listing pages
const posts = await butter.post.list({
  page: 1,
  page_size: 10,
  exclude_body: true  // 50-70% smaller response
});

// Display post cards with title and summary
posts.data.data.forEach(post => {
  console.log(post.title);   // Available
  console.log(post.summary); // Available
  console.log(post.body);    // undefined (excluded)
});

2. The levels parameter

The levels parameter controls reference field depth in API responses, balancing data completeness with response size and performance. Configuration:
  • Default: levels=2 covers most page display scenarios
  • Range: 1-5 (values outside this range are automatically adjusted)
  • Level 1: Reference fields return API URIs only (minimal data transfer)
  • Level 2+: Reference fields return complete object data
  • Level 5 (max): Returns URIs for any remaining references beyond specified depth
Level Comparison:
// Level 1 - URIs only (fastest, smallest)
{
  "fields": {
    "author": ["/v2/content/?keys=authors[_id=123]"],
    "category": ["/v2/content/?keys=categories[_id=456]"]
  }
}

// Level 2 - Full objects (default)
{
  "fields": {
    "author": [{
      "meta": { "id": 123 },
      "name": "John Doe",
      "bio": "Technical writer..."
    }],
    "category": [{
      "meta": { "id": 456 },
      "name": "Technology",
      "slug": "technology"
    }]
  }
}
Recommended Usage:
Use CaseRecommended LevelReason
Navigation menus1Only need slugs/titles
Page listings1Minimal reference data needed
Standard page display2 (default)Most common scenario
Pages with nested refs3-4Complex content structures
Deep reference chains5Maximum depth needed

3. Field filtering

Filter content server-side to fetch only what you need — by author, category, tag, or any custom field value. See Filtering Requests for the full reference with examples for blog posts, pages, and collections.

4. Ordering

Order results server-side instead of sorting client-side: Available Order Fields:
  • published - Publication date
  • updated - Last update date
  • Custom content fields
Order Direction:
  • Ascending (default): order=published
  • Descending: order=-published (prepend -)
Examples:
# Newest first
/v2/pages/blog-post/?order=-published

# Alphabetical by title
/v2/pages/blog-post/?order=title

# Custom field ordering
/v2/pages/product/?order=-price

5. Pagination

Use appropriate page sizes and pagination methods to avoid oversized responses. The recommended range is 10-25 items per request for optimal performance.

Pagination

Page-based and offset-based pagination, infinite scroll, and archive page patterns

Filtering Requests

Filter by author, category, tag, and custom field values

Serialization performance

When cached content is unavailable (cache miss), the API must serialize both the requested content and all content referenced at the specified depth. For list endpoints, this increases processing time significantly. Example Problem: Requesting 100 pages with levels=2 may require serializing 100+ individual objects plus their references. Recommendation: Always use levels=1 for list operations, then fetch full details for individual items when needed.

Query optimization patterns

Pattern 1: two-stage fetching

Fetch lists with minimal data, then fetch full details on demand:
// Stage 1: Get list with minimal data
const list = await butter.page.list('blog-post', {
  page_size: 10,
  levels: 1,
  fields: { 'fields.featured': 'true' }
});

// Stage 2: Fetch full details for a specific item
const fullPage = await butter.page.retrieve('blog-post', selectedSlug, {
  levels: 2
});

Pattern 2: parallel requests

Fetch independent data in parallel:
// Good - parallel requests
const [posts, categories, authors] = await Promise.all([
  butter.post.list({ page_size: 10, exclude_body: true }),
  butter.category.list(),
  butter.author.list()
]);

// Avoid - sequential requests (slower)
const posts = await butter.post.list({ page_size: 10 });
const categories = await butter.category.list();
const authors = await butter.author.list();

Pattern 3: selective field loading

Only request the data you need for each view:
// Homepage: Just need titles and images
const homepagePosts = await butter.post.list({
  page_size: 3,
  exclude_body: true
});
// Returns: title, slug, summary, featured_image

// Archive page: Need dates and categories too
const archivePosts = await butter.post.list({
  page_size: 20,
  exclude_body: true
});
// Returns: title, slug, summary, published date, categories

// Individual post: Need everything
const fullPost = await butter.post.retrieve(slug);
// Returns: All fields including body

Pattern 4: caching query results

Cache optimized query results for repeated access:
import NodeCache from 'node-cache';

const cache = new NodeCache({ stdTTL: 300 });

async function getOptimizedPageList(pageType, filters = {}) {
  const cacheKey = `list-${pageType}-${JSON.stringify(filters)}`;

  let result = cache.get(cacheKey);
  if (result) return result;

  result = await butter.page.list(pageType, {
    levels: 1,
    page_size: 100,
    ...filters
  });

  cache.set(cacheKey, result);
  return result;
}

Response size optimization

10MB Response Limit: ButterCMS has a 10MB response size limit. If your queries approach this limit, reduce levels or page_size.

Estimating response sizes

Content TypeTypical SizeWith exclude_body
Blog post (full)5-50KB1-5KB
Page (simple)2-10KBN/A
Page (with components)10-100KBN/A
Collection item0.5-5KBN/A

Size reduction strategies

  1. Use exclude_body=true for all listing endpoints
  2. Use levels=1 for navigation and lists
  3. Limit page_size to 10-25 items
  4. Filter server-side instead of fetching everything

Search optimization

When using the search endpoint, keep these optimizations in mind: Search Limitations:
  • Only the direct content of pages is searched
  • References are excluded from search but appear in results
  • Maximum query length: 100 characters
Optimized Search Implementation:
async function searchPages(query, pageType = '*') {
  const response = await butter.page.search(query, {
    page_type: pageType,
    page_size: 20,
    levels: 1  // Get full details separately if needed
  });

  return response.data.data;
}

// Then fetch full details for the selected result
async function getSearchResultDetails(slug) {
  return butter.page.retrieve('*', slug, { levels: 2 });
}

Query optimization checklist

  • Identify the minimum data needed for each view
  • Plan which endpoints to use for each feature
  • Consider caching strategy for each query type
  • Use exclude_body=true for all blog listings
  • Set appropriate levels (default to 1 for lists)
  • Use server-side filtering instead of client-side
  • Order results server-side with order parameter
  • Implement parallel fetching for independent data
  • Measure response times with real data volumes
  • Test with cache cold and warm states
  • Monitor response sizes stay well under 10MB
  • Verify minimal data for each view type