Skip to content

Commit 3044354

Browse files
author
marcus
committed
fix(portfolio): revalidate sanity content in production without redeploys
1 parent 80f135b commit 3044354

7 files changed

Lines changed: 165 additions & 7 deletions

File tree

.env

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@ NEXT_PUBLIC_SANITY_DATASET="production"
99

1010
NEXT_PUBLIC_BLOG_SANITY_PROJECT_ID="s9thr270"
1111
NEXT_PUBLIC_BLOG_SANITY_DATASET="production"
12+
13+
# Optional local shortcut for the separate blog studio route used by `/studio/blog`
14+
# BLOG_STUDIO_URL="http://localhost:3001/studio"

app/studio/blog/page.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { getBlogStudioUrl } from "@/lib/studio-links";
2+
import { redirect } from "next/navigation";
3+
4+
export default function BlogStudioRedirectPage() {
5+
redirect(getBlogStudioUrl());
6+
}

app/studio/page.tsx

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import {
2+
BLOG_STUDIO_PROXY_PATH,
3+
DEFAULT_BLOG_STUDIO_URL,
4+
PORTFOLIO_STUDIO_PATH,
5+
getBlogStudioUrl,
6+
} from "@/lib/studio-links";
7+
import { ArrowRight, ExternalLink, FilePenLine, Newspaper, UserRound } from "lucide-react";
8+
import Link from "next/link";
9+
10+
function StudioCard({
11+
eyebrow,
12+
title,
13+
description,
14+
href,
15+
icon: Icon,
16+
external = false,
17+
}: {
18+
eyebrow: string;
19+
title: string;
20+
description: string;
21+
href: string;
22+
icon: typeof UserRound;
23+
external?: boolean;
24+
}) {
25+
return (
26+
<Link
27+
href={href}
28+
target={external ? "_blank" : undefined}
29+
rel={external ? "noopener noreferrer" : undefined}
30+
className="group relative overflow-hidden rounded-[2rem] border border-border/70 p-6 transition-transform duration-300 hover:-translate-y-1 md:p-7"
31+
style={{
32+
background:
33+
eyebrow === "Portfolio studio"
34+
? "linear-gradient(155deg, color-mix(in oklch, var(--background) 90%, var(--primary) 10%), color-mix(in oklch, var(--background) 97%, var(--primary) 3%))"
35+
: "linear-gradient(155deg, color-mix(in oklch, var(--background) 90%, var(--secondary) 10%), color-mix(in oklch, var(--background) 97%, var(--secondary) 3%))",
36+
}}
37+
>
38+
<div className="space-y-8">
39+
<div className="space-y-4">
40+
<div className="inline-flex h-12 w-12 items-center justify-center rounded-full border border-border/70 bg-background/72 text-primary">
41+
<Icon className="h-5 w-5" />
42+
</div>
43+
44+
<div className="space-y-2">
45+
<p className="text-[0.72rem] uppercase tracking-[0.24em] text-muted-foreground">
46+
{eyebrow}
47+
</p>
48+
<h2 className="font-incognito text-[clamp(2rem,4vw,3rem)] leading-[0.94] tracking-[-0.04em]">
49+
{title}
50+
</h2>
51+
<p className="max-w-xl text-sm leading-7 text-muted-foreground md:text-base">
52+
{description}
53+
</p>
54+
</div>
55+
</div>
56+
57+
<div className="inline-flex items-center gap-2 text-[0.78rem] font-medium uppercase tracking-[0.2em] text-foreground">
58+
Open studio
59+
{external ? (
60+
<ExternalLink className="h-4 w-4 transition-transform duration-300 group-hover:translate-x-0.5 group-hover:-translate-y-0.5" />
61+
) : (
62+
<ArrowRight className="h-4 w-4 transition-transform duration-300 group-hover:translate-x-1" />
63+
)}
64+
</div>
65+
</div>
66+
</Link>
67+
);
68+
}
69+
70+
export default function StudioHubPage() {
71+
const blogStudioUrl = getBlogStudioUrl();
72+
73+
return (
74+
<section className="mx-auto flex max-w-7xl flex-col gap-10 px-6 pb-24 pt-28 md:px-16">
75+
<div className="grid gap-6 xl:grid-cols-[minmax(0,0.78fr)_minmax(0,1.22fr)] xl:items-end">
76+
<div className="space-y-4">
77+
<p className="text-[0.72rem] uppercase tracking-[0.32em] text-primary">Content</p>
78+
<h1 className="max-w-xl font-incognito text-[clamp(2.8rem,5.8vw,5.3rem)] leading-[0.94] tracking-[-0.05em]">
79+
Choose which Sanity workspace you want to edit.
80+
</h1>
81+
</div>
82+
83+
<div className="flex flex-col gap-4 xl:items-end">
84+
<p className="max-w-2xl text-base leading-7 text-muted-foreground md:text-lg xl:text-right">
85+
The portfolio and blog now read from separate Sanity projects. This page keeps both
86+
editing paths in one place so you can jump straight into adding or updating content.
87+
</p>
88+
</div>
89+
</div>
90+
91+
<div className="grid gap-5 lg:grid-cols-2">
92+
<StudioCard
93+
eyebrow="Portfolio studio"
94+
title="Portfolio content"
95+
description="Edit profile, experience, home sections, projects, and portfolio-specific content inside the main site studio."
96+
href={PORTFOLIO_STUDIO_PATH}
97+
icon={UserRound}
98+
/>
99+
100+
<StudioCard
101+
eyebrow="Blog studio"
102+
title="Blog content"
103+
description="Open the separate blog studio to manage posts, categories, authors, and article content from the dedicated Sanity project."
104+
href={BLOG_STUDIO_PROXY_PATH}
105+
icon={Newspaper}
106+
/>
107+
</div>
108+
109+
<div className="border-t border-border/65 pt-6">
110+
<div className="flex flex-col gap-3 text-sm text-muted-foreground">
111+
<div className="inline-flex items-center gap-2 text-foreground">
112+
<FilePenLine className="h-4 w-4 text-primary" />
113+
<span>Path summary</span>
114+
</div>
115+
<p>
116+
Portfolio studio: <code>{PORTFOLIO_STUDIO_PATH}</code>
117+
</p>
118+
<p>
119+
Blog studio shortcut: <code>{BLOG_STUDIO_PROXY_PATH}</code> which forwards to{" "}
120+
<code>{blogStudioUrl}</code>
121+
</p>
122+
<p>
123+
If your blog studio runs on a different host or port, set <code>BLOG_STUDIO_URL</code>{" "}
124+
in <code>.env.local</code>. Default fallback: <code>{DEFAULT_BLOG_STUDIO_URL}</code>
125+
</p>
126+
<p>
127+
Production note: to create or edit documents from the deployed studio, add your
128+
production domain to the matching Sanity project CORS origins and enable credentials.
129+
</p>
130+
</div>
131+
</div>
132+
</section>
133+
);
134+
}

app/studio/[[...index]]/page.tsx renamed to app/studio/portfolio/[[...index]]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
import config from "@/sanity.config";
44
import { NextStudio } from "next-sanity/studio";
55

6-
export default function Studio() {
6+
export default function PortfolioStudioPage() {
77
return <NextStudio config={config} />;
88
}

lib/studio-links.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const PORTFOLIO_STUDIO_PATH = "/studio/portfolio";
2+
export const BLOG_STUDIO_PROXY_PATH = "/studio/blog";
3+
export const DEFAULT_BLOG_STUDIO_URL = "http://localhost:3001/studio";
4+
5+
export function getBlogStudioUrl() {
6+
return process.env.BLOG_STUDIO_URL || DEFAULT_BLOG_STUDIO_URL;
7+
}

sanity.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export default defineConfig({
88
title: "Marcusng site",
99
projectId,
1010
dataset,
11-
basePath: "/studio",
11+
basePath: "/studio/portfolio",
1212
plugins: [deskTool(), table()],
1313
schema: { types: schema.types },
1414
});

sanity/lib/query.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,16 @@ import { Profile } from "@/types/profile";
55
import type { Project } from "@/types/project";
66
import { groq } from "next-sanity";
77

8+
const SANITY_REVALIDATE_SECONDS = 60;
9+
10+
function sanityFetch<T>(query: string, params?: Record<string, string>) {
11+
return client.fetch<T>(query, params ?? {}, {
12+
next: { revalidate: SANITY_REVALIDATE_SECONDS },
13+
});
14+
}
15+
816
export async function getExperiences(): Promise<Experience[]> {
9-
return client.fetch(
17+
return sanityFetch(
1018
groq`*[_type == "experience"] | order(startDate desc) {
1119
_id,
1220
_type,
@@ -29,7 +37,7 @@ export async function getExperiences(): Promise<Experience[]> {
2937
}
3038

3139
export async function getProfile(): Promise<Profile> {
32-
return client.fetch(
40+
return sanityFetch(
3341
groq`*[_type == "profile"][0]{
3442
_id,
3543
fullName,
@@ -61,7 +69,7 @@ export async function getProfile(): Promise<Profile> {
6169
}
6270

6371
export async function getProjects(): Promise<Project[]> {
64-
return client.fetch(
72+
return sanityFetch(
6573
groq`*[_type == "project"] | order(_createdAt asc) {
6674
_id,
6775
title,
@@ -84,7 +92,7 @@ export async function getProjects(): Promise<Project[]> {
8492
}
8593

8694
export async function getAllPets(): Promise<Pet[]> {
87-
return client.fetch(
95+
return sanityFetch(
8896
groq`*[_type == "pet"] | order(_createdAt desc) {
8997
_id,
9098
name,
@@ -111,7 +119,7 @@ export async function getAllPets(): Promise<Pet[]> {
111119
}
112120

113121
export async function getPetBySlug(slug: string): Promise<Pet | null> {
114-
return client.fetch(
122+
return sanityFetch(
115123
groq`*[_type == "pet" && slug.current == $slug][0]{
116124
_id,
117125
name,

0 commit comments

Comments
 (0)