Back to Skills

data-fetching

majiayu000
Updated Today
6 views
58
9
58
View on GitHub
Developmentdata

About

This skill provides Next.js 16+ best practices for server-side data fetching, caching, and rendering using Cache Components and Partial Prerendering. It teaches developers to implement fine-grained caching at the component level with `use cache` directives and lifetime profiles. Use it to optimize performance by separating static and dynamic content within Suspense boundaries while enforcing structured data access through server actions.

Quick Install

Claude Code

Recommended
Plugin CommandRecommended
/plugin add https://github.com/majiayu000/claude-skill-registry
Git CloneAlternative
git clone https://github.com/majiayu000/claude-skill-registry.git ~/.claude/skills/data-fetching

Copy and paste this command in Claude Code to install this skill

Documentation

Overview

This skill covers server-side data fetching and caching patterns using Next.js 16+ Cache Components approach with Partial Prerendering (PPR). It combines fine-grained caching control with server-side data fetching for optimal performance.

Core Principles

Data Access Rules

  • NEVER call Drizzle ORM directly - Always use server actions defined in lib/actions/
  • Cache at the component level - Use the use cache directive in pages/layouts, not in action files
  • Wrap dynamic content - Use Suspense boundaries to separate static and dynamic content
  • Use lifetime profiles - Always specify cacheLife() with appropriate profile

Cache Components Workflow

1. Planning Data Fetching

Before implementing:

  • Identify what data is needed for the page/component
  • Determine what content should be instantly visible (cached) vs. what can stream (dynamic)
  • Locate the appropriate server actions in lib/actions/ or create new ones if needed
  • Plan cache tags for data that needs manual invalidation

2. Implementing Cached Data Fetching

Follow this pattern in pages or layouts:

import { cacheLife } from 'next/cache'
import { getModels } from '@/lib/actions/models'

export default async function ModelsPage() {
  'use cache'
  cacheLife('hours')

  const models = await getModels()

  return <div>{/* render models */}</div>
}

3. Handling Dynamic Content

For runtime-dependent data (cookies, headers, searchParams):

import { Suspense } from 'react'

export default function Page() {
  return (
    <>
      <h1>Static Content</h1>
      <Suspense fallback={<Skeleton />}>
        <DynamicUserContent />
      </Suspense>
    </>
  )
}

async function DynamicUserContent() {
  const session = await getSession() // uses cookies()
  return <div>{session.user.name}</div>
}

Caching Configuration

Cache Life Profiles

Use built-in lifetime profiles with cacheLife():

ProfileUse CaseDuration
'seconds'Highly volatile data~30 seconds
'minutes'Frequently updated content~5 minutes
'hours'Semi-static content~1 hour
'days'Mostly static content~1 day
'weeks'Rarely changing content~1 week
'max'Static contentMaximum duration

Default Choice: Use 'hours' for most content unless you have specific requirements.

Cache Tags and Revalidation

Using cacheTag for Manual Invalidation

Tag cached data that needs to be invalidated on specific events:

import { cacheLife, cacheTag } from 'next/cache'
import { getModelById } from '@/lib/actions/models'

export default async function ModelPage({ params }: { params: { id: string } }) {
  'use cache'
  cacheLife('hours')
  cacheTag('models', `model-${params.id}`)

  const model = await getModelById(params.id)
  return <div>{/* render model */}</div>
}

Invalidating Cache with updateTag

Use in server actions for immediate cache expiration (read-your-own-writes):

'use server'
import { updateTag } from 'next/cache'

export async function updateModel(id: string, data: ModelData) {
  // Update database via action
  await updateModelAction(id, data)

  // Immediately expire cache so user sees fresh data
  updateTag(`model-${id}`, 'models')
}

Using revalidateTag for Background Refresh

For stale-while-revalidate pattern:

'use server'
import { revalidateTag } from 'next/cache'

export async function createModel(data: ModelData) {
  await createModelAction(data)

  // Stale-while-revalidate: serve stale, refresh in background
  revalidateTag('models', 'max')
}

Best Practices

Caching Strategy

  • Cache pages/layouts, not actions - Add use cache directive in pages/layouts that consume actions, never in action files themselves
  • Wrap actions in cached functions - The page/layout function itself becomes the caching boundary
  • Use Suspense boundaries - Separate static shell from dynamic/streaming content
  • Tag strategically - Use cache tags for content that changes infrequently but needs manual updates

Performance Optimization

  • Minimize dynamic APIs - Avoid using cookies(), headers(), or searchParams in cached functions
  • Parallel data fetching - Multiple server actions can be called in parallel within a cached component
  • Appropriate cache lifetimes - Balance freshness needs with server load

Data Mutation Patterns

  • Use updateTag for user mutations - When users need to see their changes immediately
  • Use revalidateTag for background updates - When serving slightly stale data is acceptable
  • Tag hierarchies - Use multiple tags (e.g., 'models' and 'model-123') for flexible invalidation

Common Patterns

Pattern 1: Cached List Page

import { cacheLife, cacheTag } from 'next/cache'
import { getModels } from '@/lib/actions/models'

export default async function ModelsPage() {
  'use cache'
  cacheLife('hours')
  cacheTag('models')

  const models = await getModels()
  return <div>{/* render list */}</div>
}

Pattern 2: Cached Detail Page with Params

import { cacheLife, cacheTag } from 'next/cache'
import { getModelById } from '@/lib/actions/models'

export default async function ModelPage({ params }: { params: { id: string } }) {
  'use cache'
  cacheLife('hours')
  cacheTag('models', `model-${params.id}`)

  const model = await getModelById(params.id)
  return <div>{/* render detail */}</div>
}

Pattern 3: Mixed Static and Dynamic Content

import { Suspense } from 'react'
import { cacheLife } from 'next/cache'

export default function Page() {
  return (
    <>
      <StaticContent />
      <Suspense fallback={<LoadingSkeleton />}>
        <DynamicContent />
      </Suspense>
    </>
  )
}

async function StaticContent() {
  'use cache'
  cacheLife('hours')

  const data = await getStaticData()
  return <div>{/* render */}</div>
}

async function DynamicContent() {
  const session = await getSession() // uses cookies
  const userData = await getUserData(session.userId)
  return <div>{/* render */}</div>
}

Pattern 4: Server Action with Cache Invalidation

'use server'
import { updateTag } from 'next/cache'
import { updateModelAction } from '@/lib/actions/models'

export async function updateModel(id: string, data: FormData) {
  const result = await updateModelAction(id, data)

  if (result.status === 'success') {
    // Immediately expire cache for this specific model and all models
    updateTag(`model-${id}`, 'models')
  }

  return result
}

Important Constraints

Serialization Requirements

  • Arguments must be serializable - Pass primitives, plain objects, and arrays only
  • No class instances or functions - Cannot pass non-serializable values as arguments to cached functions
  • Unserializable return values are OK - Can return React components or other unserializable values if you don't introspect them

What NOT to Cache

  • Functions using runtime APIs - cookies(), headers(), searchParams should not be in cached functions
  • Server Actions - Never add use cache to server action files; cache at the consumption point
  • Highly personalized content - User-specific data that varies per request

Configuration

Enable Cache Components in next.config.ts:

const nextConfig = {
  cacheComponents: true,
}

export default nextConfig

Troubleshooting

Cache Not Working

  • Verify cacheComponents: true is set in next.config.ts
  • Check that use cache is at the top of the function body
  • Ensure you're using Node.js runtime (Edge Runtime not supported)
  • Verify function arguments are serializable

Stale Data Issues

  • Check cache lifetime profile - may need shorter duration
  • Use updateTag instead of revalidateTag for immediate updates
  • Verify cache tags match between caching and invalidation

Performance Issues

  • Profile which content needs to be cached vs. dynamic
  • Use more Suspense boundaries to improve streaming
  • Consider longer cache lifetimes for stable content
  • Review database query performance in server actions

GitHub Repository

majiayu000/claude-skill-registry
Path: skills/data-fetching

Related Skills

content-collections

Meta

This skill provides a production-tested setup for Content Collections, a TypeScript-first tool that transforms Markdown/MDX files into type-safe data collections with Zod validation. Use it when building blogs, documentation sites, or content-heavy Vite + React applications to ensure type safety and automatic content validation. It covers everything from Vite plugin configuration and MDX compilation to deployment optimization and schema validation.

View skill

llamaindex

Meta

LlamaIndex is a data framework for building RAG-powered LLM applications, specializing in document ingestion, indexing, and querying. It provides key features like vector indices, query engines, and agents, and supports over 300 data connectors. Use it for document Q&A, chatbots, and knowledge retrieval when building data-centric applications.

View skill

hybrid-cloud-networking

Meta

This skill configures secure hybrid cloud networking between on-premises infrastructure and cloud platforms like AWS, Azure, and GCP. Use it when connecting data centers to the cloud, building hybrid architectures, or implementing secure cross-premises connectivity. It supports key capabilities such as VPNs and dedicated connections like AWS Direct Connect for high-performance, reliable setups.

View skill

polymarket

Meta

This skill enables developers to build applications with the Polymarket prediction markets platform, including API integration for trading and market data. It also provides real-time data streaming via WebSocket to monitor live trades and market activity. Use it for implementing trading strategies or creating tools that process live market updates.

View skill