Skip to content

Commit aa35daa

Browse files
committed
feat: enhance Sanity Studio deployment and draft mode functionality
- Added support for deploying Sanity Studio to both development and production environments in the GitHub Actions workflow. - Introduced a new environment variable for the public Sanity Studio URL. - Updated the Sanity CLI configuration to utilize the SANITY_STUDIO_HOSTNAME for dynamic hostname resolution. - Enhanced draft mode functionality by allowing dataset selection based on the preview context, improving content visibility during editing. - Updated various page components to fetch data from the appropriate dataset based on the draft mode state.
1 parent 08554b7 commit aa35daa

File tree

23 files changed

+267
-71
lines changed

23 files changed

+267
-71
lines changed

.github/workflows/deploy.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ jobs:
3030
env:
3131
SANITY_PROJECT_ID: hfh83o0w
3232
SANITY_DATASET: dev
33+
PUBLIC_SANITY_STUDIO_URL: https://codingcat-dev.sanity.studio
3334

3435
- name: Build (production)
3536
if: github.ref == 'refs/heads/main'
@@ -54,9 +55,18 @@ jobs:
5455
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
5556
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
5657

57-
- name: Deploy Sanity Studio
58+
- name: Deploy Sanity Studio (dev)
59+
if: github.ref == 'refs/heads/dev'
60+
run: npx sanity deploy --no-login
61+
working-directory: apps/sanity
62+
env:
63+
SANITY_AUTH_TOKEN: ${{ secrets.SANITY_AUTH_TOKEN }}
64+
SANITY_STUDIO_HOSTNAME: codingcat-dev
65+
66+
- name: Deploy Sanity Studio (production)
5867
if: github.ref == 'refs/heads/main'
5968
run: npx sanity deploy --no-login
6069
working-directory: apps/sanity
6170
env:
6271
SANITY_AUTH_TOKEN: ${{ secrets.SANITY_AUTH_TOKEN }}
72+
SANITY_STUDIO_HOSTNAME: codingcat.dev

apps/sanity/sanity.cli.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@ export default defineCliConfig({
55
projectId: process.env.SANITY_STUDIO_PROJECT_ID || "hfh83o0w",
66
dataset: process.env.SANITY_STUDIO_DATASET || "production",
77
},
8+
// Set via SANITY_STUDIO_HOSTNAME in CI: e.g. "codingcat.dev" (prod), "codingcat-dev" (dev)
9+
studioHost: process.env.SANITY_STUDIO_HOSTNAME,
810
});

apps/web/.dev.vars.example

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
# Local development secrets — NOT committed to git
2-
# Copy to .dev.vars and fill in values
1+
# Runtime secrets for Cloudflare Worker (wrangler dev / preview).
2+
# Copy to .dev.vars and fill in. In production, set via: wrangler secret put <NAME>
3+
4+
# Sanity — viewer token for draft mode / Presentation tool
5+
SANITY_API_READ_TOKEN=
6+
# Optional: set to enable "Turn on draft mode" from site (visit /api/draft-mode/allow-enable?secret=THIS once per session)
7+
# SANITY_PREVIEW_DEV_SECRET=
38

49
# better-auth
5-
BETTER_AUTH_SECRET=your-dev-secret-here
10+
BETTER_AUTH_SECRET=
611
BETTER_AUTH_URL=http://localhost:4321
712

8-
# Google OAuth (for better-auth social login)
9-
GOOGLE_CLIENT_ID=your-google-client-id
10-
GOOGLE_CLIENT_SECRET=your-google-client-secret
11-
12-
# Sanity (viewer token for draft mode / Visual Editing — Presentation tool validates via API)
13-
SANITY_API_READ_TOKEN=your-sanity-viewer-token
13+
# Google OAuth (better-auth social login)
14+
GOOGLE_CLIENT_ID=
15+
GOOGLE_CLIENT_SECRET=

apps/web/.env.example

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,13 @@
1-
# Sanity CMS
1+
# Build-time env (Astro/Vite). Copy to .env or .env.local.
2+
# Runtime secrets live in .dev.vars (see .dev.vars.example).
3+
4+
# Sanity — used at build; defaults exist in code
25
SANITY_PROJECT_ID=hfh83o0w
36
SANITY_DATASET=production
4-
SANITY_API_TOKEN=
5-
# SANITY_API_READ_TOKEN = viewer token for draft mode / Visual Editing (required for Presentation tool; set in .dev.vars for Cloudflare)
67

7-
# Visual Editing (optional; for local dev or when not using draft-mode cookie)
8+
# Optional: Visual Editing (local or dev site)
89
# PUBLIC_SANITY_VISUAL_EDITING_ENABLED=true
9-
# When running Studio locally, point overlay "edit" links to your Studio:
1010
# PUBLIC_SANITY_STUDIO_URL=http://localhost:3333
1111

12-
# Auth (better-auth)
13-
BETTER_AUTH_SECRET=
14-
BETTER_AUTH_URL=http://localhost:4321
15-
GOOGLE_CLIENT_ID=
16-
GOOGLE_CLIENT_SECRET=
17-
18-
# Cloudflare (set via wrangler secret)
19-
# D1 database binding is configured in wrangler.jsonc
12+
# Optional: Auth base URL (defaults to http://localhost:4321)
13+
# PUBLIC_APP_URL=http://localhost:4321

apps/web/README.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,25 @@ Fresh Astro 6 project replacing the Next.js site. Deployed to Cloudflare Workers
1414

1515
## Getting Started
1616

17+
From repo root:
18+
1719
```bash
18-
cd astro-app
19-
npm install
20-
npm run dev
20+
pnpm install
21+
pnpm --filter @codingcatdev/web dev
2122
```
2223

23-
For Cloudflare Workers preview:
24+
Or from `apps/web`:
25+
2426
```bash
25-
npm run preview
27+
pnpm install && pnpm dev
2628
```
2729

28-
## Environment Variables
30+
For Cloudflare Workers preview: `pnpm preview` (from `apps/web`).
2931

30-
Copy `.env.example` to `.env` and fill in values. For Cloudflare secrets, use `.dev.vars` locally.
32+
## Environment Variables
3133

32-
See `.env.example` for all required variables.
34+
- **Build:** Copy `.env.example` to `.env` or `.env.local`. Sanity project/dataset have defaults; optional vars are commented.
35+
- **Runtime (local):** Copy `.dev.vars.example` to `.dev.vars` and set secrets (Sanity token, better-auth, Google OAuth). In production, set these via `wrangler secret put <NAME>`.
3336

3437
## Deployment
3538

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
interface Props {
3+
draftMode: boolean;
4+
currentPath: string;
5+
}
6+
7+
const { draftMode, currentPath } = Astro.props;
8+
const disableUrl = `/api/draft-mode/disable?slug=${encodeURIComponent(currentPath)}`;
9+
const enableUrl = `/api/draft-mode/enable?redirect=${encodeURIComponent(currentPath)}`;
10+
---
11+
12+
<div class="draft-toggle" aria-label="Draft mode">
13+
<span class="draft-toggle__label">
14+
Draft {draftMode ? "on" : "off"}
15+
</span>
16+
{draftMode ? (
17+
<a href={disableUrl} class="draft-toggle__btn">Turn off</a>
18+
) : (
19+
<a href={enableUrl} class="draft-toggle__btn">Turn on</a>
20+
)}
21+
</div>
22+
23+
<style>
24+
.draft-toggle {
25+
position: fixed;
26+
bottom: 1rem;
27+
right: 1rem;
28+
z-index: 9999;
29+
display: flex;
30+
align-items: center;
31+
gap: 0.5rem;
32+
padding: 0.375rem 0.75rem;
33+
font-size: 0.8125rem;
34+
background: var(--bg);
35+
color: var(--text);
36+
border: 1px solid var(--border, rgba(128, 128, 128, 0.3));
37+
border-radius: 9999px;
38+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
39+
}
40+
.draft-toggle__label {
41+
opacity: 0.85;
42+
}
43+
.draft-toggle__btn {
44+
font-weight: 500;
45+
color: var(--accent, #0ea5e9);
46+
text-decoration: none;
47+
}
48+
.draft-toggle__btn:hover {
49+
text-decoration: underline;
50+
}
51+
</style>

apps/web/src/layouts/BaseLayout.astro

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import ThemeScript from "../components/ThemeScript.astro";
44
import Header from "../components/Header.astro";
55
import Footer from "../components/Footer.astro";
66
import SocialMeta from "../components/SocialMeta.astro";
7+
import DraftModeToggle from "../components/DraftModeToggle.astro";
78
89
interface Props {
910
title: string;
@@ -17,9 +18,10 @@ interface Props {
1718
1819
const { title, description = "CodingCat.dev — Purrfect Web Tutorials", ogImage, ogType, publishedAt, author, canonicalUrl } = Astro.props;
1920
21+
const draftMode = Astro.cookies.has("__sanity_preview");
2022
const visualEditingEnabled =
21-
import.meta.env.PUBLIC_SANITY_VISUAL_EDITING_ENABLED === "true" ||
22-
Astro.cookies.has("__sanity_preview");
23+
import.meta.env.PUBLIC_SANITY_VISUAL_EDITING_ENABLED === "true" || draftMode;
24+
const currentPath = Astro.url.pathname || "/";
2325
---
2426

2527
<!doctype html>
@@ -67,5 +69,7 @@ const visualEditingEnabled =
6769
import("../scripts/visual-editing").then((m) => m.run());
6870
</script>
6971
)}
72+
73+
<DraftModeToggle draftMode={draftMode} currentPath={currentPath} />
7074
</body>
7175
</html>

apps/web/src/pages/[slug].astro

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ if (reservedSlugs.includes(slug!)) {
1717
1818
const draftMode = Astro.cookies.has("__sanity_preview");
1919
const perspective = Astro.url.searchParams.get("sanity-preview-perspective") ?? undefined;
20-
const { data: page } = await loadQuery<any>({ query: pageQuery, params: { slug }, draftMode, perspective });
20+
const previewDataset = Astro.cookies.get("__sanity_preview_dataset")?.value ?? undefined;
21+
const { data: page } = await loadQuery<any>({ query: pageQuery, params: { slug }, draftMode, perspective, dataset: previewDataset });
2122
2223
if (!page) {
2324
return new Response(null, { status: 404 });
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* One-time setup so the "Turn on draft mode" button can enable preview without Studio.
3+
* Visit /api/draft-mode/allow-enable?secret=YOUR_SANITY_PREVIEW_DEV_SECRET once per session.
4+
* Sets __sanity_allow_enable cookie; then links to enable with devSecret can work.
5+
*/
6+
import type { APIRoute } from "astro";
7+
import { env } from "cloudflare:workers";
8+
9+
export const prerender = false;
10+
11+
export const GET: APIRoute = async ({ url, cookies, redirect }) => {
12+
const devSecret = (env as Record<string, string>).SANITY_PREVIEW_DEV_SECRET;
13+
const secret = url.searchParams.get("secret");
14+
15+
if (!devSecret || secret !== devSecret) {
16+
return new Response("Invalid or missing secret", { status: 400 });
17+
}
18+
19+
const isLocal = url.hostname === "localhost";
20+
cookies.set("__sanity_allow_enable", "1", {
21+
path: "/",
22+
httpOnly: true,
23+
sameSite: isLocal ? "lax" : "none",
24+
secure: !isLocal,
25+
maxAge: 60 * 60 * 24,
26+
});
27+
28+
return redirect("/", 307);
29+
};

apps/web/src/pages/api/draft-mode/disable.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ export const prerender = false;
1212
export const GET: APIRoute = async ({ url, cookies, redirect }) => {
1313
const slug = url.searchParams.get("slug") || "/";
1414

15-
// Delete the preview cookie
15+
// Delete the preview cookies
1616
cookies.delete("__sanity_preview", { path: "/" });
17+
cookies.delete("__sanity_preview_dataset", { path: "/" });
1718

1819
// Redirect back to the page (now showing published content)
1920
return redirect(slug, 307);

0 commit comments

Comments
 (0)