Developer guides

Relay live draft updates through your server

Keep your Sanity token on the server and relay Live Content API draft updates to the browser in a Next.js app, with no token in client-side code.

Live previews of draft content need a token on the Live Content API connection. In a Next.js app, the browserToken option of defineLive sends that token to the browser. If you can't expose a token client-side, keep it on the server. Listen for live events in a route handler and forward a bare "content changed" signal to the browser. The browser then reruns your server components with router.refresh(). The server components refetch with the server-side token, so no credentials or sync tags reach the browser.

This guide shows how to relay live draft updates in a Next.js App Router app so the token never leaves the server.

You don't need a relay for published content

Prerequisites:

Add the token to the client

Set token on the client that defineLive uses. The route handler in this guide uses the same client to open the drafts stream, and on a private dataset, published queries need the token too. SANITY_API_READ_TOKEN has no NEXT_PUBLIC_ prefix, so Next.js keeps it out of browser bundles:

Keep the token out of the browser

Pass the token as serverToken so server components can fetch drafts in Draft Mode, and set browserToken: false so SanityLive never receives it:

Create the relay route

Create a route handler that streams server-sent events. It listens to the Live Content API with includeDrafts: true, authorized by the token on the server-side client, and writes a change message whenever content changes:

In this example:

  • The route returns 401 unless Draft Mode is enabled, so only visitors previewing drafts can open the stream.
  • It forwards a change message for message events, and for restart and reconnect events, which mean earlier events may have been missed.
  • It sends no sync tags or event IDs. The browser only learns that something changed.
  • It unsubscribes from the Live Content API when the browser disconnects.

Each open browser tab holds one connection to the route, and the route holds one connection to the Live Content API for that tab. Hosting platforms also cap how long a response can stay open. When the host ends the stream, EventSource in the browser reconnects on its own, so check your platform's maximum function duration to know how often that happens.

Refresh the page when content changes

Create a client component that connects to the route with EventSource and calls router.refresh() on every message. SanityLive does the same when it receives draft events with a browserToken:

Render the relay in Draft Mode

In your root layout, render LiveDraftRefresh when Draft Mode is enabled and SanityLive otherwise. Visitors outside Draft Mode keep receiving published updates through SanityLive, and editors previewing drafts get updates through the relay:

This also covers previews in the Presentation tool. The VisualEditing component doesn't refresh the page when content changes, so the relay supplies those updates without causing a second refresh.

Refreshes aren't filtered by query

Troubleshooting

The route returns 401

The route responds with Draft Mode is not enabled when the request has no Draft Mode cookie. LiveDraftRefresh only renders in Draft Mode, and EventSource sends cookies on same-origin requests, so this usually means the route was requested directly or from another origin.

The stream closes right after it opens

If the client has no token, client.live.events() fails with 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. If the Live Content API rejects the token, the error is EventSource connection failed, with the HTTP status on the error's status property. The route logs the error and closes the stream, and EventSource in the browser reconnects and fails the same way. Check that SANITY_API_READ_TOKEN is set in the server environment and belongs to the project.

Next steps

Was this page helpful?