Skip to content

Prefetching data with dynamic keys in Nextjs 16 cacheComponents: true #4211

Description

@pauksztello

Summary

The current SWR docs for Next.js Server Components show how to pass data into <SWRConfig value={{ fallback }}> as promises. However, there’s no documented (and possibly no supported) way to initiate server-side prefetching when the SWR key is dynamic, e.g. derived from searchParams for pagination/filtering.

I would argue that such pattern in is crucial for Nextjs (and even bare minimum for such library), especially when Nextjs 16 is pushing Cache Components + Partial Prerendering.

Docs reference: https://swr.vercel.app/docs/with-nextjs#prefetch-data-in-server-components

What the docs currently cover (static keys)

import { SWRConfig } from 'swr'

export default async function Layout({
  children,
}: {
  children: React.ReactNode
}) {
  // Initiate the data fetching on the server side.
  const userPromise = fetchUserFromAPI()
  const postsPromise = fetchPostsFromAPI()

  return (
    <SWRConfig
      value={{
        fallback: {
          // Pass the promises to client components.
          '/api/user': userPromise,
          '/api/posts': postsPromise,
        },
      }}
    >
      {children}
    </SWRConfig>
  )
}

Problem: no clear path for dynamic keys

In many pages (pagination, filtering, sorting), the SWR key is built from searchParams, for example:
• buildKey(searchParams)
• ['/api/items', { page, sort, q }]
• getKey(index) for infinite/pagination

There’s currently no documented way to prefetch on the server and provide fallback when the key can’t be expressed as a static string.

Current workaround

Right now, I’m not using <SWRConfig> for fallback in this case. Instead, I manually pass a promise to the client component and feed the resolved value into SWR:

export default function Page({ searchParams }: { searchParams: any }) {
  const dataPromise = fetchData(searchParams)

  return (
    <Suspense>
      <ClientComponent dataPromise={dataPromise} />
    </Suspense>
  )
}

'use client'

import useSWR from 'swr'
import { useSearchParams } from 'next/navigation'
import { use } from 'react'

const ClientComponent = ({ dataPromise }: { dataPromise: Promise<any> }) => {
  const searchParams = useSearchParams()
  const fallback = use(dataPromise)

  const { data } = useSWR(buildKey(searchParams), fetcher, { fallback })

  // ...
}

This works, but:
• it’s repetitive (must be implemented per-hook/per-component),
• it defeats the point of a global <SWRConfig> hydration.

Proposed solution

Allow passing a fallback promise into <SWRConfig>, where the promise resolves to the fallback map:

import { SWRConfig } from 'swr'

export default function Page({ searchParams }: { searchParams: any }) {
  const fallbackPromise = fetchData(searchParams) // resolves to { [dynamicKey]: data }

  return (
    <SWRConfig
      value={{
        fallback: fallbackPromise,
      }}
    >
      {children}
    </SWRConfig>
  )
}

Where fallbackPromise resolves to something like:

// example shape
{
  "dynamicKey": { /* data */ }
}

Any thoughts?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions