Developer guides

Add live content to your application

Learn to use the Live Content API with Next.js or your own integration for real-time content updates in your app.

The Live Content API lets you deliver live content experiences without the complexity and infrastructure requirements traditionally found in real-time apps.

The next-sanity library wraps the Live Content API for Next.js apps. The JavaScript client offers helper utilities to get you started, but you'll need to build additional functionality.

This guide shows two ways to add live content to an application: with next-sanity in a Next.js app, and with the JavaScript client in any other framework.

Add live content with next-sanity

Enable live content with only a few lines of code with next-sanity.

Next.js + Sanity + Visual Editing

If you plan to set up Next.js, Sanity, Visual Editing, and the Live Content API, see the Next.js Visual Editing guide for a complete implementation.

Prerequisites

  • A new or existing Sanity project.
  • Add your frontend or deployment target's origin to the project's CORS origins. This is found in the project's API section at sanity.io/manage.
  • A Next.js application built with the app router architecture. The Live Content features in next-sanity do not support apps built with the pages router.
  • This guide assumes next-sanity v13 or later, which requires Next.js 16, React 19.2 or later, and @sanity/client 7.26.1 or later.

Install and configure the client

You can install, set up, and configure Sanity in your existing Next.js project with init:

Alternatively, install the package or update it to the latest version:

Next, confirm that you have an existing Sanity client configured:

Create the live utilities

Create a live utility file and configure the sanityFetch helper and SanityLive component by passing in your local Sanity client and a token. defineLive requires a browser and server token to fetch draft content when using Draft Mode. If you aren't using Visual Editing or draft previews, set serverToken: false and browserToken: false to opt out and silence the development warnings:

Tokens

Fetch your queries

Whenever you need to query data in your Sanity dataset, import the sanityFetch helper and call it as you would any Sanity client by passing in a GROQ query and any query parameters:

In this example, the data response is destructured to post and sanityFetch receives a GROQ query and an optional params object.

Enable the SanityLive component

The final step to enable the Live Content API is adding the SanityLive React component. It listens for changes in your data and works with your sanityFetch queries to efficiently update content. Include it in your application so it renders on any page that needs live content.

Embedded studios

In this example, it lives just before the closing body tag in the RootLayout component:

Make updates instant in next-sanity v13

Next steps

Create your own integration

If there isn't an official library for your framework that enables live content, you need to create your own integration to use the Live Content API. The Live Content API Examples repository on GitHub collects example projects and is a good starting point for custom implementations.

The minimal example in this section uses the Sanity JavaScript client.

Prerequisites

  • API version v2021-03-25 or later. Older versions omit syncTags from query responses and throw The live events API requires API version 2021-03-25 or later.
  • The real dataset name. The Live Content API does not support dataset aliases.
  • A new or existing Sanity project.
  • Add your frontend or deployment target's origin to the project's CORS origins. This is found in the project's API section at sanity.io/manage.

Install and configure the client

First, install the latest version of the client:

Configure your @sanity/client with your project settings and the latest API version:

How it works

Here's a high-level overview of how the Live Content API works:

  • Every response from Content Lake includes sync tags. Your application stores the tags for the content it needs to keep up to date in real time.
  • It subscribes to a stream of live updates with the client.live.events() method, which returns an Observable that emits an event whenever content in the dataset changes.
  • When an event arrives, it checks whether any of the event tags match the stored sync tags.
  • If there's a match, it refetches the content, passing the event ID as the lastLiveEventId argument to client.fetch so the CDN returns the latest version of the content instead of stale data.

Minimal example

Here is a minimal example running in the console. It keeps a single, predefined document in sync using sync tags:

In this example:

  • The example creates a Sanity client instance with the necessary configuration.
  • It defines a query to fetch posts and executes it, setting filterResponse: false to get the syncTags along with the result.
  • It stores the returned syncTags and renders the initial data.
  • It subscribes to live updates using client.live.events().
  • Whenever an update event arrives, it checks whether any of the event's tags match the stored syncTags.
  • If there's a match, it refetches the data, passing the event ID as lastLiveEventId to get the latest version.
  • It updates the stored syncTags and re-renders with the fresh data.
  • Finally, it unsubscribes from the live updates when they're no longer needed.

This pattern keeps your application's content in sync with the latest changes in your Sanity dataset. For additional examples, including listening for drafts, see the JavaScript client documentation.

Next steps

Troubleshooting

client.live.events() reports failures as an error on the observable rather than throwing, so pass an error handler to subscribe to see them at all.

Origin not allowed by CORS

An unlisted origin makes the connection fail without a usable reason, so the client checks the project's CORS configuration and reports a CorsOriginError. In a browser, the message ends with a link that pre-fills the origin: The current origin is not allowed to connect to the Live Content API. Add it here: followed by the URL. On a server, where no origin is available, it reads The current origin is not allowed to connect to the Live Content API. Change your configuration here: followed by the project's API settings URL.

The stream errors and doesn't retry. The client only reports this error when it can confirm the rejection, so an ambiguous check surfaces the underlying connection error instead. Add the origin in the project's API settings at sanity.io/manage.

In a Next.js app, SanityLive logs a warning instead of failing the render: Sanity Live is unable to connect to the Sanity API as the current origin - ORIGIN - is not in the list of allowed CORS origins for this Sanity Project. Set onError="throw" to surface it to the nearest error boundary instead.

Connection rejected by the API

A rejected token produces EventSource connection failed on the observable, with the HTTP status on the error's status property. Any 4xx other than 408 and 429 is fatal: the client stops and doesn't reconnect. A 5xx, a 408, or a 429 is retried, and the stream emits a reconnect event first.

The status property is only populated where the eventsource package provides the connection. Native browser and Node implementations expose no status, so the client can't tell a rejected token from a dropped network and retries instead. A silent reconnect loop with no error is the symptom of an authentication problem in those environments.

A token used to read drafts needs viewer rights or lower. Requesting drafts with no token throws before any request is made: The live events API requires a token or withCredentials when 'includeDrafts: true'. Please update your client configuration. The token should have the lowest possible access role.

Respond to a restart event

A restart event means the lastLiveEventId you hold is no longer usable. Its payload carries only two fields, type and id, and no sync tags.

Handle it in three parts:

  • Refetch every query, and don't pass the event's ID as lastLiveEventId.
  • Discard the sync tags you've stored. They can no longer be matched against incoming events.
  • Treat reconnect the same way. Both events invalidate buffered tags.

In a Next.js app, SanityLive calls router.refresh() on restart by default, so server components re-render with fresh data.

Draft content missing from results

Querying with the published perspective returns published content only, and nothing tells you that's what happened. There's no error and no console message, and the response looks identical to one from a dataset with no drafts. Since API version v2025-02-19, published is the default.

Set perspective: 'drafts' and supply a token to read drafts. In Next.js, defineLive pins its internal client to published, so pass a serverToken to read drafts on the server and a browserToken for live preview in the browser. Without them, defineLive warns in development only.

Was this page helpful?