Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ Production needs the webhook. [Revalidate webhook](#revalidate-webhook) sets it

- `SANITY_API_READ_TOKEN` on the site: a Viewer token, so the site can read drafts.
- `SANITY_STUDIO_PREVIEW_ORIGIN` on the Studio: the site's origin. Default `http://localhost:3000`. `SANITY_STUDIO_WEB_PREVIEW_ORIGINS` adds more, such as deploy previews.
- `NEXT_PUBLIC_SANITY_STUDIO_URL` on the site: the Studio's URL, so the site lets the Studio frame it and click-to-edit opens the right Studio. Default `http://localhost:3333`; `*.sanity.studio` is always allowed.
- `NEXT_PUBLIC_SANITY_STUDIO_URL` on the site: the Studio's URL, so the site lets the Studio frame it and click-to-edit opens the right Studio. Default `http://localhost:3333`; `*.sanity.studio` and the Sanity Dashboard (`*.sanity.io`) are always allowed.

Site and Studio must read the same dataset, or the preview shows other content.

Expand Down
3 changes: 2 additions & 1 deletion web/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ SANITY_API_READ_TOKEN=
SANITY_REVALIDATE_SECRET=

# Studio URL (BUILD): click-to-edit links and CSP `frame-ancestors`. Default
# `http://localhost:3333`; `*.sanity.studio` is always allowed. In production, the
# `http://localhost:3333`; `*.sanity.studio` and the Sanity Dashboard (`*.sanity.io`)
# are always allowed. In production, the
# Studio's `SANITY_STUDIO_HOSTING_URL`.
# NEXT_PUBLIC_SANITY_STUDIO_URL=https://studio.your-domain.tld

Expand Down
15 changes: 10 additions & 5 deletions web/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ function originOf(raw: string | undefined): string | null {

/**
* Origin of the hosted Studio, for `frame-ancestors` — Sanity Presentation
* embeds this site in an iframe on the Studio domain. `*.sanity.studio` and
* the local Studio are always allowed; a custom Studio domain is picked up
* from `NEXT_PUBLIC_SANITY_STUDIO_URL` (the same variable the Sanity client
* uses for stega links).
* embeds this site in an iframe on the Studio domain. `*.sanity.studio`, the
* Sanity Dashboard and the local Studio are always allowed; a custom Studio
* domain is picked up from `NEXT_PUBLIC_SANITY_STUDIO_URL` (the same variable
* the Sanity client uses for stega links).
*/
const studioOrigin = originOf(process.env.NEXT_PUBLIC_SANITY_STUDIO_URL);

Expand Down Expand Up @@ -101,7 +101,11 @@ const analyticsSources = {
* manifests/segments/thumbnails, analytics, and the dev server's HMR
* websocket.
* - `frame-ancestors` must stay a header (a `<meta>` CSP cannot carry it):
* it is what lets Sanity Presentation embed the site.
* it is what lets Sanity Presentation embed the site. The browser checks
* every ancestor, not only the parent: a `*.sanity.studio` URL redirects
* into the Sanity Dashboard (`www.sanity.io`), which frames the Studio,
* which frames this site. A Studio opened from the Dashboard on its own
* domain sits in the same chain, so `*.sanity.io` is listed as well.
*/
function contentSecurityPolicy(): string {
const directives: Record<string, string[]> = {
Expand Down Expand Up @@ -151,6 +155,7 @@ function contentSecurityPolicy(): string {
"'self'",
"http://localhost:3333",
"https://*.sanity.studio",
"https://*.sanity.io",
...(studioOrigin ? [studioOrigin] : []),
],
"worker-src": ["'self'", "blob:"],
Expand Down
14 changes: 10 additions & 4 deletions web/src/components/sanity/DisableDraftMode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,22 @@ import { useIsPresentationTool } from "next-sanity/hooks";
import { useEffect } from "react";

/**
* Drafts are for the Presentation preview only. Once the page is confirmed to run outside the
* Presentation tool (`false`, not the `null` "still detecting" state), Draft Mode is switched
* off through a full navigation, so the page re-renders on the server with published content.
* Inside Presentation (iframe or "open preview" window) nothing happens.
* Drafts are for the Presentation preview only. A top-level page that Presentation does not
* drive (`false`, not the `null` "still detecting" state) leaves Draft Mode through a full
* navigation, so it re-renders on the server with published content.
*
* Inside a frame or a window another page opened, `false` only means the handshake has not
* finished: `<VisualEditing />` reports it after one second without a connection, and the Studio
* can take longer (it may itself sit in the Sanity Dashboard's frame). Leaving Draft Mode there
* would cut Presentation off, and in its cross-site iframe the disable route cannot even clear
* the partitioned cookie — so nothing happens.
*/
export function DisableDraftMode() {
const isPresentationTool = useIsPresentationTool();

useEffect(() => {
if (isPresentationTool !== false) return;
if (window.self !== window.top || window.opener) return;
const { pathname, search, hash } = window.location;
const target = `${pathname}${search}${hash}`;
window.location.href = `/api/draft-mode/disable?redirect=${encodeURIComponent(target)}`;
Expand Down
Loading