Skip to content

Commit 82acf69

Browse files
committed
test cache components
1 parent 55b9c3b commit 82acf69

File tree

19 files changed

+13272
-19392
lines changed

19 files changed

+13272
-19392
lines changed

app/(personal)/[slug]/page.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import {sanityFetch} from '@/sanity/lib/live'
44
import {pagesBySlugQuery, slugsByTypeQuery} from '@/sanity/lib/queries'
55
import type {Metadata, ResolvingMetadata} from 'next'
66
import {toPlainText, type PortableTextBlock} from 'next-sanity'
7-
import {draftMode} from 'next/headers'
7+
import {cookies, draftMode} from 'next/headers'
88
import {notFound} from 'next/navigation'
9+
import {resolvePerspectiveFromCookie} from 'next-sanity/experimental/live'
910

1011
type Props = {
1112
params: Promise<{slug: string}>
@@ -15,10 +16,13 @@ export async function generateMetadata(
1516
{params}: Props,
1617
parent: ResolvingMetadata,
1718
): Promise<Metadata> {
19+
const jar = await cookies()
20+
const perspective = await resolvePerspectiveFromCookie({cookies: jar})
1821
const {data: page} = await sanityFetch({
1922
query: pagesBySlugQuery,
2023
params,
2124
stega: false,
25+
perspective,
2226
})
2327

2428
return {
@@ -38,7 +42,9 @@ export async function generateStaticParams() {
3842
}
3943

4044
export default async function PageSlugRoute({params}: Props) {
41-
const {data} = await sanityFetch({query: pagesBySlugQuery, params})
45+
const jar = await cookies()
46+
const perspective = await resolvePerspectiveFromCookie({cookies: jar})
47+
const {data} = await sanityFetch({query: pagesBySlugQuery, params, perspective})
4248

4349
// Only show the 404 page if we're in production, when in draft mode we might be about to create a page on this slug, and live reload won't work on the 404 route
4450
if (!data?._id && !(await draftMode()).isEnabled) {

app/(personal)/client-functions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import {isCorsOriginError} from 'next-sanity/live'
3+
import {isCorsOriginError} from 'next-sanity'
44
import {toast} from 'sonner'
55

66
export function handleError(error: unknown) {

app/(personal)/layout.tsx

Lines changed: 72 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,21 @@ import {urlForOpenGraphImage} from '@/sanity/lib/utils'
88
import type {Metadata, Viewport} from 'next'
99
import {toPlainText, type PortableTextBlock} from 'next-sanity'
1010
import {VisualEditing} from 'next-sanity/visual-editing'
11-
import {draftMode} from 'next/headers'
11+
import {cookies, draftMode} from 'next/headers'
1212
import {Suspense} from 'react'
1313
import {Toaster} from 'sonner'
1414
import {handleError} from './client-functions'
1515
import {DraftModeToast} from './DraftModeToast'
1616
import {SpeedInsights} from '@vercel/speed-insights/next'
17+
import {resolvePerspectiveFromCookie} from 'next-sanity/experimental/live'
18+
import type {SettingsQueryResult} from '@/sanity.types'
1719

1820
export async function generateMetadata(): Promise<Metadata> {
21+
const jar = await cookies()
22+
const perspective = await resolvePerspectiveFromCookie({cookies: jar})
1923
const [{data: settings}, {data: homePage}] = await Promise.all([
20-
sanityFetch({query: settingsQuery, stega: false}),
21-
sanityFetch({query: homePageQuery, stega: false}),
24+
sanityFetch({query: settingsQuery, stega: false, perspective}),
25+
sanityFetch({query: homePageQuery, stega: false, perspective}),
2226
])
2327

2428
const ogImage = urlForOpenGraphImage(
@@ -43,31 +47,28 @@ export const viewport: Viewport = {
4347
themeColor: '#000',
4448
}
4549

46-
export default async function IndexRoute({children}: {children: React.ReactNode}) {
47-
const {data} = await sanityFetch({query: settingsQuery})
50+
export default async function PersonalLayout({children}: {children: React.ReactNode}) {
51+
const isDraftMode = (await draftMode()).isEnabled
4852
return (
4953
<>
5054
<div className="flex min-h-screen flex-col bg-white text-black">
51-
<Navbar data={data} />
52-
<div className="mt-20 flex-grow px-4 md:px-16 lg:px-32">{children}</div>
53-
<footer className="bottom-0 w-full bg-white py-12 text-center md:py-20">
54-
{data?.footer && (
55-
<CustomPortableText
56-
id={data._id}
57-
type={data._type}
58-
path={['footer']}
59-
paragraphClasses="text-md md:text-xl"
60-
value={data.footer as unknown as PortableTextBlock[]}
61-
/>
55+
<Suspense
56+
// @TODO add a fallback that reduces layout shift
57+
fallback={null}
58+
>
59+
{isDraftMode ? (
60+
<DynamicLayout>{children}</DynamicLayout>
61+
) : (
62+
<CachedLayout>{children}</CachedLayout>
6263
)}
63-
</footer>
64+
</Suspense>
6465
<Suspense>
6566
<IntroTemplate />
6667
</Suspense>
6768
</div>
6869
<Toaster />
6970
<SanityLive onError={handleError} />
70-
{(await draftMode()).isEnabled && (
71+
{isDraftMode && (
7172
<>
7273
<DraftModeToast />
7374
<VisualEditing />
@@ -77,3 +78,56 @@ export default async function IndexRoute({children}: {children: React.ReactNode}
7778
</>
7879
)
7980
}
81+
82+
/**
83+
* Resolves cookies for the perspective in draft mode, important that it's wrapped in a <Suspense> boundary for PPR to work its magic
84+
*/
85+
async function DynamicLayout({children}: {children: React.ReactNode}) {
86+
const jar = await cookies()
87+
const perspective = await resolvePerspectiveFromCookie({cookies: jar})
88+
const {data} = await sanityFetch({query: settingsQuery, perspective})
89+
return <CachedContent data={data}>{children}</CachedContent>
90+
}
91+
92+
/**
93+
* Runs in production, takes full advantage of prerender and PPR
94+
*/
95+
async function CachedLayout({children}: {children: React.ReactNode}) {
96+
'use cache'
97+
98+
const {data} = await sanityFetch({query: settingsQuery, perspective: 'published', stega: false})
99+
100+
return <CachedContent data={data}>{children}</CachedContent>
101+
}
102+
103+
/**
104+
* Shared by both the cached and dynamic layouts, it is its own cache layer so that even if the `sanityFetch` is
105+
* revalidated and refetched, the component output doesn't change if `data` is the same.
106+
*/
107+
async function CachedContent({
108+
children,
109+
data,
110+
}: {
111+
children: React.ReactNode
112+
data: SettingsQueryResult
113+
}) {
114+
'use cache'
115+
116+
return (
117+
<>
118+
<Navbar data={data} />
119+
<div className="mt-20 flex-grow px-4 md:px-16 lg:px-32">{children}</div>
120+
<footer className="bottom-0 w-full bg-white py-12 text-center md:py-20">
121+
{data?.footer && (
122+
<CustomPortableText
123+
id={data._id}
124+
type={data._type}
125+
path={['footer']}
126+
paragraphClasses="text-md md:text-xl"
127+
value={data.footer as unknown as PortableTextBlock[]}
128+
/>
129+
)}
130+
</footer>
131+
</>
132+
)
133+
}

app/(personal)/page.tsx

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,19 @@
11
import {HomePage} from '@/components/HomePage'
2-
import {studioUrl} from '@/sanity/lib/api'
32
import {sanityFetch} from '@/sanity/lib/live'
43
import {homePageQuery} from '@/sanity/lib/queries'
5-
import Link from 'next/link'
4+
import type {ClientPerspective} from 'next-sanity'
5+
import {resolvePerspectiveFromCookie} from 'next-sanity/experimental/live'
6+
import {cookies, draftMode} from 'next/headers'
67

78
export default async function IndexRoute() {
8-
const {data} = await sanityFetch({query: homePageQuery})
9-
10-
if (!data) {
11-
return (
12-
<div className="text-center">
13-
You don&rsquo;t have a homepage yet,{' '}
14-
<Link href={`${studioUrl}/structure/home`} className="underline">
15-
create one now
16-
</Link>
17-
!
18-
</div>
19-
)
20-
}
9+
const isDraftMode = (await draftMode()).isEnabled
10+
const {data} = await sanityFetch({
11+
query: homePageQuery,
12+
perspective: isDraftMode
13+
? await resolvePerspectiveFromCookie({cookies: await cookies()})
14+
: 'published',
15+
stega: isDraftMode,
16+
})
2117

2218
return <HomePage data={data} />
2319
}

app/(personal)/projects/[slug]/page.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ import {projectBySlugQuery, slugsByTypeQuery} from '@/sanity/lib/queries'
77
import {urlForOpenGraphImage} from '@/sanity/lib/utils'
88
import type {Metadata, ResolvingMetadata} from 'next'
99
import {createDataAttribute, toPlainText} from 'next-sanity'
10-
import {draftMode} from 'next/headers'
10+
import {cookies, draftMode} from 'next/headers'
1111
import Link from 'next/link'
1212
import {notFound} from 'next/navigation'
13+
import {resolvePerspectiveFromCookie} from 'next-sanity/experimental/live'
1314

1415
type Props = {
1516
params: Promise<{slug: string}>
@@ -19,9 +20,12 @@ export async function generateMetadata(
1920
{params}: Props,
2021
parent: ResolvingMetadata,
2122
): Promise<Metadata> {
23+
const jar = await cookies()
24+
const perspective = await resolvePerspectiveFromCookie({cookies: jar})
2225
const {data: project} = await sanityFetch({
2326
query: projectBySlugQuery,
2427
params,
28+
perspective,
2529
stega: false,
2630
})
2731
const ogImage = urlForOpenGraphImage(
@@ -51,7 +55,9 @@ export async function generateStaticParams() {
5155
}
5256

5357
export default async function ProjectSlugRoute({params}: Props) {
54-
const {data} = await sanityFetch({query: projectBySlugQuery, params})
58+
const jar = await cookies()
59+
const perspective = await resolvePerspectiveFromCookie({cookies: jar})
60+
const {data} = await sanityFetch({query: projectBySlugQuery, params, perspective})
5561

5662
// Only show the 404 page if we're in production, when in draft mode we might be about to create a project on this slug, and live reload won't work on the 404 route
5763
if (!data?._id && !(await draftMode()).isEnabled) {

app/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const mono = IBM_Plex_Mono({
1919
weight: ['500', '700'],
2020
})
2121

22-
export default async function RootLayout({children}: {children: React.ReactNode}) {
22+
export default function RootLayout({children}: {children: React.ReactNode}) {
2323
return (
2424
<html lang="en" className={`${mono.variable} ${sans.variable} ${serif.variable}`}>
2525
<body>{children}</body>

app/studio/[[...index]]/page.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* This route is responsible for the built-in authoring environment using Sanity Studio v3.
2+
* This route is responsible for the built-in authoring environment using Sanity Studio v4.
33
* All routes under /studio will be handled by this file using Next.js' catch-all routes:
44
* https://nextjs.org/docs/routing/dynamic-routes#catch-all-routes
55
*
@@ -10,10 +10,12 @@
1010
import config from '@/sanity.config'
1111
import {NextStudio} from 'next-sanity/studio'
1212

13-
export const dynamic = 'force-static'
14-
1513
export {metadata, viewport} from 'next-sanity/studio'
1614

1715
export default function StudioPage() {
18-
return <NextStudio config={config} />
16+
return (
17+
<>
18+
<NextStudio config={config} />
19+
</>
20+
)
1921
}

components/HomePage.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,22 @@ export interface HomePageProps {
1212
}
1313

1414
export async function HomePage({data}: HomePageProps) {
15+
'use cache'
16+
17+
if (!data) {
18+
return (
19+
<div className="text-center">
20+
You don&rsquo;t have a homepage yet,{' '}
21+
<Link href={`${studioUrl}/structure/home`} className="underline">
22+
create one now
23+
</Link>
24+
!
25+
</div>
26+
)
27+
}
28+
1529
// Default to an empty object to allow previews on non-existent documents
16-
const {overview = [], showcaseProjects = [], title = ''} = data ?? {}
30+
const {overview = [], showcaseProjects = [], title = ''} = data
1731

1832
const dataAttribute =
1933
data?._id && data?._type

next-env.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// <reference types="next" />
22
/// <reference types="next/image-types/global" />
3-
/// <reference path="./.next/types/routes.d.ts" />
3+
import "./.next/types/routes.d.ts";
44

55
// NOTE: This file should not be edited
66
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

next.config.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
1-
import {NextConfig} from 'next'
1+
import type {NextConfig} from 'next'
22

33
const config: NextConfig = {
4-
// Helps catch bugs
5-
reactStrictMode: true,
64
experimental: {
75
// Speeds up performance by automatically generating useMemo and useCallback in client components
86
reactCompiler: true,
7+
// Required by `next-sanity/experimental/live`
8+
cacheComponents: true,
9+
cacheLife: {
10+
default: {
11+
// Sanity Live handles on-demand revalidation, so the default 15min time based revalidation is too short
12+
revalidate: 60 * 60 * 24 * 90, // 90 days
13+
},
14+
},
915
},
16+
// Helps catch bugs
17+
reactStrictMode: true,
1018
images: {
1119
remotePatterns: [{hostname: 'cdn.sanity.io'}],
1220
},

0 commit comments

Comments
 (0)