Skip to content

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

@pauksztello

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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions