Skip to content

[feat]: Feature Request: Support for fetchOptions in Strapi Client #109

@mick-sun

Description

@mick-sun

A clear and concise description of what the feature is

Add support for configurable fetchOptions in the @strapi/client to allow developers to control caching behavior when using the client in Next.js applications (App Router).

Currently, @strapi/client doesn't expose any API to configure fetch options, which prevents developers from leveraging Next.js's built-in caching mechanisms (ISR, cache tags, on-demand revalidation, etc.).

Why should this feature be included?

Problem:

  • Next.js App Router aggressively caches fetch requests by default
  • The Strapi client provides no way to configure this caching behavior
  • This results in stale data being served even after calling Next.js revalidation APIs
  • Developers are forced to choose between completely disabling caching (force-dynamic) or dealing with stale data

Impact on developers:

  1. Performance Issues: Forces developers to sacrifice performance by completely disabling caching with export const dynamic = 'force-dynamic', which eliminates the benefits of static generation and ISR

  2. Poor Developer Experience: Requires complex workarounds like wrapping every Strapi call with unstable_cache, which doesn't integrate well with Strapi's API and adds unnecessary boilerplate

  3. SEO Challenges: Makes it difficult to implement proper ISR (Incremental Static Regeneration) for content-heavy sites that need both performance and fresh data

  4. Scalability Concerns: Prevents efficient caching strategies, leading to unnecessary API calls to Strapi backend

Current workarounds attempted:

Page-level force-dynamic - Disables all caching, poor performance
unstable_cache wrapper - Adds complexity, doesn't integrate well
revalidatePath/revalidateTag - Inconsistent because underlying requests aren't properly tagged

Why this matters:

  • Next.js 13+ App Router has fundamentally changed how caching works
  • Strapi is a popular headless CMS choice for Next.js projects
  • This gap between the two tools creates a significant pain point for the community

Please provide an example for how this would work

Option 1: Global fetchOptions configuration (Recommended)

import { strapi } from '@strapi/client';

// Initialize client with fetch options
export const client = strapi({
  baseURL: process.env.STRAPI_BASE_URL,
  auth: process.env.STRAPI_AUTH_TOKEN,
  fetchOptions: {
    cache: 'no-store',        // Disable caching globally
    next: { 
      revalidate: 60,         // Or enable ISR with 60s revalidation
      tags: ['strapi-data']   // Add cache tags for on-demand revalidation
    }
  }
});

// Usage remains the same
const collection = await client.collection('pages');
const { data } = await collection.findOne('page-id');

Option 2: Per-request fetchOptions override

// Global default settings
export const client = strapi({
  baseURL: process.env.STRAPI_BASE_URL,
  auth: process.env.STRAPI_AUTH_TOKEN,
  fetchOptions: {
    next: { revalidate: 3600 } // Default: 1 hour cache
  }
});

// Override per request
const collection = await client.collection('pages');
const { data } = await collection.findOne('page-id', {
  populate: '*',
  fetchOptions: {
    next: { 
      tags: ['specific-page'],  // More specific cache tag
      revalidate: 60            // Different revalidation time
    }
  }
});

Option 3: Callback-based configuration

export const client = strapi({
  baseURL: process.env.STRAPI_BASE_URL,
  auth: process.env.STRAPI_AUTH_TOKEN,
  configureFetch: (url, options) => {
    // Add cache tags based on URL
    const tags = [];
    if (url.includes('/pages')) tags.push('strapi-pages');
    if (url.includes('/articles')) tags.push('strapi-articles');
    
    return {
      ...options,
      next: { 
        tags,
        revalidate: 60 
      }
    };
  }
});

Real-world usage scenario

// src/lib/strapi.ts
export const client = strapi({
  baseURL: process.env.STRAPI_BASE_URL,
  auth: process.env.STRAPI_AUTH_TOKEN,
  fetchOptions: {
    next: { 
      revalidate: 60,
      tags: ['strapi']
    }
  }
});

// src/app/blog/[slug]/page.tsx
export default async function BlogPost({ params }) {
  const collection = await client.collection('articles');
  const { data } = await collection.findOne(params.slug, {
    populate: '*',
    fetchOptions: {
      next: { tags: [`article-${params.slug}`] }
    }
  });
  
  return <article>{/* render article */}</article>;
}

// src/app/api/revalidate/route.ts
export async function POST(request) {
  const { slug } = await request.json();
  
  // Now this actually works!
  revalidateTag(`article-${slug}`);
  
  return Response.json({ revalidated: true });
}

Additional Context

Environment

  • @strapi/client: ^1.5.0
  • next: 14.2.23
  • react: 18.2.21
  • Node.js: v18+

Additional Context

This issue is particularly relevant for Next.js 13+ App Router users, as the framework's caching behavior has changed significantly. Many developers building headless CMS applications with Strapi + Next.js would benefit from this feature.

Related discussions:

Would the maintainers be open to a pull request implementing one of these solutions? I'm happy to contribute if there's interest in this feature.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions