← All notes
CMS Implementation7 min

Stega is for overlays, not for parsers

Sanity's Presentation Tool is worth wiring: an editor opens the live site in Studio, clicks a headline, and lands on the field that produced it. The mechanism is stega — invisible characters woven into draft strings. The failure mode is treating those strings as data. A slug with stega is not a slug. A hex color with stega is not a color. Clean or disable encoding anywhere a machine, not a person, will consume the value.

What stega is actually doing

When Draft Mode is on and Visual Editing is configured, fetches return content with Content Source Maps encoded into the strings themselves. The encoding is invisible in the browser — zero-width characters that VisualEditing reads from the DOM to draw click-to-edit overlays and jump the Studio to the right document and field.

That is a deliberate trade. You get click-to-edit without wrapping every field in a custom component. The cost is that the string on the wire is no longer equal to the string an editor typed. For display copy that difference is harmless. For anything you parse, compare, or pass to another API, it is a bug that only appears while previewing.

We see this most often after the Draft Mode client split is already correct. Perspective is right, the token is server-only, and then a preview URL 404s because slug.current carried stega into the path.

Where encoded strings break the page

Anything that leaves the paragraph is a candidate. href and src attributes. CSS variables and hex colors. Dates handed to Date.parse. JSON you stringify into a script tag. IDs you match against a route param. generateMetadata fields that Search Console will eventually see if a preview cookie leaks into a shared link.

The failure is quiet. new URL(stegaUrl) throws. A CSS color is ignored and the component falls back. A redirect compares two strings that look identical in a console log and fail ===. Editors report that preview is flaky; production is fine, which is exactly what you get when the defect only exists on the draft path.

On Provale Cup-style clinical pages and on our own notes, the fields that look like prose often feed structured behavior later — a CTA href, a service label used as a filter key, an image hotspot object serialized for the builder. Those seams need an explicit clean step.

stegaClean at the boundary, not everywhere

next-sanity exposes stegaClean for exactly this. Call it at the boundary where a string stops being rendered text and starts being input to logic. Clean the href before it reaches <a>. Clean the slug before you build a path. Leave the headline alone so the overlay still has something to attach to.

Cleaning everything at fetch time defeats Visual Editing — overlays need the encoding in the DOM. Cleaning nothing leaks encoding into parsers. The rule is local: display strings keep stega; data strings lose it at the last responsible moment.

For queries that never render — metadata, sitemaps, static params, structured data — turn stega off on the fetch. defineLive and sanityFetch both support that. Those responses should never carry source maps because nothing on the page will use them for overlays.

Presentation is a second preview contract

Draft Mode decides whether you fetch drafts. Stega decides whether those drafts are clickable. They are related and not the same. You can serve previewDrafts without Visual Editing; you should not enable stega on a client that also feeds production HTML.

The Presentation Tool adds its own wiring: previewUrl.origin pointing at the Next app, an enable route that flips Draft Mode, and a resolve map from document types to frontend paths. If origin is wrong, the iframe is blank. If resolve returns a path the App Router does not own, click-to-open lands on a 404. If stega.studioUrl points at the wrong Studio host, overlays open the wrong editor.

Treat that map like any other integration contract. Change a route segment, update resolve in the same PR. The previous note covered keeping perspective on the request; this one is the next layer — once drafts are on the request, decide which strings are allowed to stay encoded.

How we verify the draft path

Open Presentation, edit a headline, confirm the overlay and live update. Then edit a slug or a CTA URL and confirm the preview navigation and the rendered link still work. If the second check fails while the first passes, you have a stega leak, not a Draft Mode leak.

Log string lengths next to visible text when debugging. Stega adds characters you cannot see in the browser; length and charCodeAt catch what console.log hides. A one-line assert — cleaned === raw only when stega is off — belongs in the preview checklist the same way noindex on Draft Mode does.

Ship Visual Editing where editors actually write long pages. Keep stega out of the paths that build routes, metadata, and anything a parser will touch. The overlay is the feature. The encoding is an implementation detail that should not escape the text node.

Questions

Why does a preview URL 404 when the published page works?
Draft fetches often return slug and path strings with stega encoding. Those characters survive into the href or redirect target and no longer match the real route. Clean stega on slugs and URLs, or disable stega for queries that only build paths.
Should I call stegaClean on every Sanity field?
No. Overlays need encoded strings in rendered text. Clean at the boundary where a value becomes data — hrefs, IDs, colors, dates, metadata — and leave display copy alone.
Is stega the same problem as baking previewDrafts into a shared client?
No. Perspective chooses draft vs published per request. Stega chooses whether draft strings carry source maps for click-to-edit. Fix the client split first, then clean or disable stega anywhere those strings are parsed.

Sources

  1. Sanity — Visual Editing with Next.js App Router
  2. Sanity — Stega / Content Source Maps
  3. next-sanity — Visual Editing
  4. Vercel Stega

Have something to build?

Tell us what you're working on and we'll tell you honestly whether we're the right fit.

Work with us