next-sanity v13.0.0: Cache Components support, cleaner APIs, and better error handling

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 set cacheComponents: true in your next.config.ts file to use defineLive with 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 runs with cacheComponents: true, and 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 first, then run:

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

Suggested prompt for your agent:

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 walks through each step in detail.

Breaking changes

See the v12 to v13 migration guide 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.

"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."
: ""
}`
);
};

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, 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.

"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);

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 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)