# Cache Components support, cleaner APIs, and better error handling

**Version:** v13.0.0

**Published:** May 21, 2026

next-sanity v13 ships first-class support for Next.js Cache Components (`cacheComponents: true`), cleans up long-deprecated APIs, and adds new escape hatches for error handling and connection lifecycle events.

## Cache Components support

You can now pass `cacheComponents: true` to `defineLive` to opt in to Next.js Cache Components. This unlocks full `'use cache'` support for `sanityFetch`, giving you fine-grained, tag-based cache invalidation that integrates directly with Sanity Live.

We've been running this in production for the past couple of months. [sanity.io](https://www.sanity.io) runs with `cacheComponents: true`, and [sanity.io/docs](https://sanity.io/docs) runs with `cacheComponents: false`. Both have been solid.

The fastest way to adopt Cache Components is with the `sanity-live-cache-components` skill, which drives the migration with an AI agent. For best results, set up [AGENTS.md](https://nextjs.org/docs/app/guides/ai-agents#existing-projects) first, then run:

**Terminal**

```bash
npx skills add https://github.com/sanity-io/next-sanity --skill sanity-live-cache-components
```

Suggested prompt for your agent:

**Prompt**

```text
Use the /sanity-live-cache-components skill to migrate this app to use Cache Components. When verifying with `next dev`, test both draft mode enabled and draft mode disabled because each mode has different rendering rules. `next build --debug-prerender` is not sufficient to verify that draft mode works correctly.
```

Prefer to migrate manually? The [Cache Components setup guide](https://www.sanity.io/docs/nextjs/cache-components) walks through each step in detail.

## Breaking changes

See the [v12 to v13 migration guide](https://github.com/sanity-io/next-sanity/blob/main/packages/next-sanity/MIGRATE-v12-to-v13.md) for full details and code snippets. The key changes are:

- The default cache-invalidation behavior changed. `revalidateSyncTags` on `<SanityLive>` is replaced by `action`; `sanityFetch` no longer caches the internal sync-tag lookup when `cacheComponents: false`.
- Removed the `<SanityLive>` props that were on by default: `refreshOnFocus`, `refreshOnReconnect`.
- Removed the opt-in `<SanityLive>` / `defineLive` props: `refreshOnMount`, `intervalOnGoAway`, `fetchOptions`, `stega`; `onGoAway` signature changed (no longer receives `interval`).
- Removed deprecated hooks: `useDraftModePerspective`, `useIsLivePreview`, `useDraftModeEnvironment`.
- Removed deprecated `tag` option on `sanityFetch` and `tag` prop on `<SanityLive>`. Use `requestTag` instead.
- Renamed `next-sanity/live` type exports: `DefinedSanityFetchType` → `DefinedFetchType`, `DefinedSanityLiveProps` → `DefinedLiveProps`, `DefineSanityLiveOptions` → `DefineLiveOptions`.

## What else is new

### Customize or silence the welcome message with `onWelcome`

By default, `<SanityLive>` logs a welcome message to the console when the live event stream connects. Pass a custom `onWelcome` handler to replace it with your own logic, or pass `onWelcome={false}` to disable it entirely.

**app/client-functions.ts**

```typescript
"use client";

import type { SanityLiveOnWelcome } from "next-sanity/live";

export const onWelcome: SanityLiveOnWelcome = (
  event,
  { includeDrafts, waitFor }
) => {
  console.info(
    `<SanityLive${
      includeDrafts ? " includeDrafts" : ""
    }> is connected and listening for live events to ${
      includeDrafts
        ? "all content including drafts and version documents in content releases"
        : "published content"
    }.${
      waitFor === "function"
        ? " Events will be delayed until after a Sanity Function has processed them."
        : ""
    }`
  );
};
```

**app/layout.tsx**

```typescript
import { onWelcome } from "./client-functions";
import { SanityLive } from "@/sanity/lib/live";

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <>
      {children}
      <SanityLive onWelcome={onWelcome} />
    </>
  );
}
```

### Error boundaries with retry via `onError="throw"`

The default behavior still logs errors with `console.error` (CORS errors use `console.warn`). Pass `onError="throw"` to throw errors during render so they can be caught by the Next.js [unstable_catchError API](https://nextjs.org/docs/app/api-reference/functions/catchError), which supports `unstable_retry` for retrying the render. This lets you build rich error UIs. For example, a toast that offers a retry button when Sanity Live can't connect.

**app/SanityLiveErrorBoundary.tsx**

```typescript
"use client";

import { isCorsOriginError } from "next-sanity/live";
import { unstable_catchError, type ErrorInfo } from "next/error";
import { useEffect } from "react";
import { toast } from "sonner";

function SanityLiveErrorBoundary(
  _props: {},
  { error, unstable_retry }: ErrorInfo
) {
  useEffect(() => {
    let toastId: string | number | undefined;
    if (isCorsOriginError(error)) {
      const { addOriginUrl } = error;
      toastId = toast.warning(`Sanity Live couldn't connect`, {
        description: `${new URL(window.origin).host} is blocked by CORS policy`,
        richColors: true,
        duration: Infinity,
        action: addOriginUrl
          ? { label: "Manage", onClick: (e) => { e.preventDefault(); window.open(addOriginUrl.toString(), "_blank"); } }
          : { label: "Retry", onClick: () => unstable_retry() },
        cancel: addOriginUrl
          ? { label: "Retry", onClick: () => unstable_retry() }
          : undefined,
      });
    } else if (error instanceof Error) {
      console.error(error);
      toastId = toast.error(error.message, {
        richColors: true,
        duration: Infinity,
        action: { label: "Retry", onClick: () => unstable_retry() },
      });
    } else {
      console.error(error);
      toastId = toast.error("Unknown error", {
        description: "Check the console for more details",
        richColors: true,
        duration: Infinity,
        action: { label: "Retry", onClick: () => unstable_retry() },
      });
    }
    return () => { toast.dismiss(toastId); };
  }, [error, unstable_retry]);

  return null;
}

export default unstable_catchError(SanityLiveErrorBoundary);
```

**app/layout.tsx**

```typescript
import SanityLiveErrorBoundary from "./SanityLiveErrorBoundary";
import { SanityLive } from "@/sanity/lib/live";

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <>
      {children}
      <SanityLiveErrorBoundary>
        <SanityLive onError="throw" />
      </SanityLiveErrorBoundary>
    </>
  );
}
```

### Strict mode for staged Cache Components migrations

Enable `strict: true` in `defineLive` to split your [Cache Components](https://nextjs.org/docs/app/api-reference/config/next-config-js/cacheComponents) migration across two PRs: first update all `sanityFetch` and `<SanityLive>` call sites to satisfy the new requirements, then flip `cacheComponents: true` in `next.config.ts` and add `'use cache'` to functions that call `sanityFetch`.

### Other additions

- `usePresentationQuery`, `useIsPresentationTool`, and `useVisualEditingEnvironment` no longer require both `<SanityLive>` and `<VisualEditing>` to be rendered in `layout.tsx`. Rendering `<VisualEditing>` alone is now enough
- `onRestart` prop on `<SanityLive>`: calls `router.refresh()` by default; pass `onRestart={false}` to disable
- `onReconnect` prop on `<SanityLive>`: logs to console by default; pass `onReconnect={false}` to disable
- `includeDrafts` prop on `<SanityLive>`: override the automatic draft-mode detection when a `browserToken` is set
- `resolvePerspectiveFromCookies` utility: exposes the same perspective resolution `sanityFetch` uses internally, useful for custom toolbars and `cacheComponents: true` boundaries
- `stega: true` now works on the published perspective, enabling Visual Editing overlays on published content (useful for Vercel Content Link)

