releases.shpreview

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

v13.0.0

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: DefinedSanityFetchTypeDefinedFetchType, DefinedSanityLivePropsDefinedLiveProps, DefineSanityLiveOptionsDefineLiveOptions.

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)

Fetched May 21, 2026