Skip to content

Commit 4ea5ab1

Browse files
authored
Merge pull request #745 from CodingCatDev/fix/sanity-presentation-preview
fix(site,sanity): enable Sanity Presentation iframe preview and visual editing
2 parents 9acc26d + 1ef41de commit 4ea5ab1

8 files changed

Lines changed: 64 additions & 15 deletions

File tree

‎apps/sanity/sanity.config.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,11 @@ const apiVersion = process.env.SANITY_STUDIO_API_VERSION || "2025-09-30";
6363
const presentationEnabled =
6464
process.env.SANITY_STUDIO_DISABLE_PRESENTATION !== "true";
6565

66-
// Use local Next.js dev server for presentation preview when running Studio locally
66+
// Use local Astro dev server for presentation preview when running Studio locally
6767
const isLocal =
6868
typeof import.meta !== "undefined" &&
6969
(import.meta as unknown as { env?: { DEV?: boolean } }).env?.DEV;
70-
const localPreviewOrigin = "http://localhost:3000";
70+
const localPreviewOrigin = "http://localhost:4321";
7171

7272
// ── Shared helpers ───────────────────────────────────────────────────
7373
function resolveHref(type: string, slug?: string): string | undefined {
@@ -242,6 +242,10 @@ function buildPlugins(previewUrl: string): PluginOptions[] {
242242
route: "/post/:slug",
243243
filter: `_type == "post" && slug.current == $slug`,
244244
},
245+
{
246+
route: "/podcast/:slug",
247+
filter: `_type == "podcast" && slug.current == $slug`,
248+
},
245249
]),
246250
locations: {
247251
settings: defineLocations({

‎apps/site/src/components/DraftModeToggle.astro‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,17 @@ const returnTo = Astro.url.pathname + Astro.url.search;
2020
Exit draft mode
2121
</button>
2222
</form>
23+
24+
<script>
25+
import { enableVisualEditing } from "@sanity/visual-editing-standalone";
26+
27+
enableVisualEditing({
28+
refresh: (payload) => {
29+
if (payload.source === "mutation" || payload.source === "manual") {
30+
window.location.reload();
31+
return Promise.resolve();
32+
}
33+
return false;
34+
},
35+
});
36+
</script>

‎apps/site/src/lib/sanity/config.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const DEFAULTS = {
2828
projectId: "hfh83o0w",
2929
dataset: "production",
3030
apiVersion: "2025-09-30",
31-
studioUrl: "https://codingcat.dev.sanity.studio/production",
31+
studioUrl: "https://codingcat-prod.sanity.studio",
3232
} as const;
3333

3434
function read(env: EnvLike | undefined, key: string): string | undefined {

‎apps/site/src/middleware.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,11 @@ export const onRequest = defineMiddleware(async (context, next) => {
9090
"max-age=31536000; includeSubDomains; preload",
9191
);
9292
response.headers.set("X-Content-Type-Options", "nosniff");
93-
response.headers.set("X-Frame-Options", "SAMEORIGIN");
9493
response.headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
95-
response.headers.set("Content-Security-Policy", "frame-ancestors 'self'");
94+
response.headers.set(
95+
"Content-Security-Policy",
96+
"frame-ancestors 'self' https://*.sanity.studio http://localhost:3333",
97+
);
9698

9799
// AIPREF / AI Preference Headers
98100
response.headers.set("Content-Usage", "train-ai=n, search=y");

‎apps/site/src/pages/api/draft-mode/disable.ts‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,33 @@ export const POST: APIRoute = async ({ request, url, cookies, redirect }) => {
4545
returnTo = url.searchParams.get("returnTo");
4646
}
4747

48+
const isHttps = url.protocol === "https:";
4849
cookies.delete(PREVIEW_COOKIE, { path: "/" });
49-
cookies.set(PREVIEW_COOKIE, "", { maxAge: 0, path: "/" });
50+
cookies.set(PREVIEW_COOKIE, "", {
51+
maxAge: 0,
52+
path: "/",
53+
httpOnly: true,
54+
secure: isHttps,
55+
sameSite: isHttps ? "none" : "lax",
56+
partitioned: isHttps,
57+
});
5058

5159
return redirect(resolveDestination(returnTo), 303);
5260
};
5361

5462
export const GET: APIRoute = async ({ url, cookies, redirect }) => {
5563
const returnTo = url.searchParams.get("returnTo");
64+
const isHttps = url.protocol === "https:";
5665

5766
cookies.delete(PREVIEW_COOKIE, { path: "/" });
58-
cookies.set(PREVIEW_COOKIE, "", { maxAge: 0, path: "/" });
67+
cookies.set(PREVIEW_COOKIE, "", {
68+
maxAge: 0,
69+
path: "/",
70+
httpOnly: true,
71+
secure: isHttps,
72+
sameSite: isHttps ? "none" : "lax",
73+
partitioned: isHttps,
74+
});
5975

6076
return redirect(resolveDestination(returnTo), 307);
6177
};

‎apps/site/src/pages/api/draft-mode/enable.ts‎

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,15 @@ export const GET: APIRoute = async ({ url, cookies, redirect }) => {
1010
(import.meta.env as Record<string, string | undefined>)
1111
?.SANITY_API_READ_TOKEN;
1212

13-
// Query params supported: secret, slug, returnTo
14-
const secret = url.searchParams.get("secret");
13+
// Query params supported: sanity-preview-secret/secret, sanity-preview-pathname/returnTo, slug, sanity-preview-perspective
14+
const secret =
15+
url.searchParams.get("sanity-preview-secret") ??
16+
url.searchParams.get("secret");
1517
const slug = url.searchParams.get("slug");
16-
const returnTo = url.searchParams.get("returnTo");
18+
const returnTo =
19+
url.searchParams.get("sanity-preview-pathname") ??
20+
url.searchParams.get("returnTo");
21+
const perspective = url.searchParams.get("sanity-preview-perspective");
1722

1823
if (!readToken) {
1924
console.warn(
@@ -35,11 +40,18 @@ export const GET: APIRoute = async ({ url, cookies, redirect }) => {
3540
destination = "/";
3641
}
3742

43+
if (perspective && !destination.includes("sanity-preview-perspective=")) {
44+
const sep = destination.includes("?") ? "&" : "?";
45+
destination = `${destination}${sep}sanity-preview-perspective=${encodeURIComponent(perspective)}`;
46+
}
47+
48+
const isHttps = url.protocol === "https:";
3849
cookies.set(PREVIEW_COOKIE, "true", {
3950
path: "/",
4051
httpOnly: true,
41-
secure: true,
42-
sameSite: "lax",
52+
secure: isHttps,
53+
sameSite: isHttps ? "none" : "lax",
54+
partitioned: isHttps,
4355
});
4456

4557
return redirect(destination, 307);

‎apps/site/tests/tier1-features.test.mjs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -877,7 +877,8 @@ describe("Tier 1 - Feature Coverage", () => {
877877
assert.equal(setCookieVal, "true");
878878
assert.equal(setCookieOpts.httpOnly, true);
879879
assert.equal(setCookieOpts.secure, true);
880-
assert.equal(setCookieOpts.sameSite, "lax");
880+
assert.equal(setCookieOpts.sameSite, "none");
881+
assert.equal(setCookieOpts.partitioned, true);
881882
});
882883

883884
it("6.7 POST and GET /api/draft-mode/disable clears __sanity_preview cookie and redirects", async () => {

‎apps/site/wrangler.jsonc‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"PUBLIC_SANITY_PROJECT_ID": "hfh83o0w",
3838
"PUBLIC_SANITY_DATASET": "dev",
3939
"PUBLIC_SANITY_API_VERSION": "2025-09-30",
40-
"PUBLIC_SANITY_STUDIO_URL": "https://codingcat-dev.sanity.studio/dev",
40+
"PUBLIC_SANITY_STUDIO_URL": "https://codingcat-dev.sanity.studio",
4141
"SITE_URL": "https://dev.codingcat.dev",
4242
"FLIPCORE_API_URL": "https://api.flipfeeds.dev",
4343
"PUBLIC_FLIPCORE_WEB_URL": "https://flipfeeds.dev"
@@ -76,7 +76,7 @@
7676
"PUBLIC_SANITY_PROJECT_ID": "hfh83o0w",
7777
"PUBLIC_SANITY_DATASET": "production",
7878
"PUBLIC_SANITY_API_VERSION": "2025-09-30",
79-
"PUBLIC_SANITY_STUDIO_URL": "https://codingcat-dev.sanity.studio/production",
79+
"PUBLIC_SANITY_STUDIO_URL": "https://codingcat-prod.sanity.studio",
8080
"SITE_URL": "https://codingcat.dev",
8181
"FLIPCORE_API_URL": "https://api.flipfeeds.com",
8282
"PUBLIC_FLIPCORE_WEB_URL": "https://flipfeeds.com"

0 commit comments

Comments
 (0)