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