Skip to content

Commit 0334bc8

Browse files
committed
test cache components
1 parent 55b9c3b commit 0334bc8

File tree

19 files changed

+13285
-19392
lines changed

19 files changed

+13285
-19392
lines changed

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

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ 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'
10+
import type {PagesBySlugQueryResult} from '@/sanity.types'
911

1012
type Props = {
1113
params: Promise<{slug: string}>
@@ -15,9 +17,13 @@ export async function generateMetadata(
1517
{params}: Props,
1618
parent: ResolvingMetadata,
1719
): Promise<Metadata> {
20+
const isDraftMode = (await draftMode()).isEnabled
1821
const {data: page} = await sanityFetch({
1922
query: pagesBySlugQuery,
2023
params,
24+
perspective: isDraftMode
25+
? await resolvePerspectiveFromCookie({cookies: await cookies()})
26+
: 'published',
2127
stega: false,
2228
})
2329

@@ -38,13 +44,27 @@ export async function generateStaticParams() {
3844
}
3945

4046
export default async function PageSlugRoute({params}: Props) {
41-
const {data} = await sanityFetch({query: pagesBySlugQuery, params})
47+
const isDraftMode = (await draftMode()).isEnabled
48+
const {data} = await sanityFetch({
49+
query: pagesBySlugQuery,
50+
params,
51+
perspective: isDraftMode
52+
? await resolvePerspectiveFromCookie({cookies: await cookies()})
53+
: 'published',
54+
stega: isDraftMode,
55+
})
4256

4357
// 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
44-
if (!data?._id && !(await draftMode()).isEnabled) {
58+
if (!data?._id && !isDraftMode) {
4559
notFound()
4660
}
4761

62+
return <CachedPageSlugRoute data={data} />
63+
}
64+
65+
async function CachedPageSlugRoute({data}: {data: PagesBySlugQueryResult}) {
66+
'use cache'
67+
4868
const {body, overview, title} = data ?? {}
4969

5070
return (

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: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,18 @@
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 {resolvePerspectiveFromCookie} from 'next-sanity/experimental/live'
5+
import {cookies, draftMode} from 'next/headers'
66

77
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-
}
8+
const isDraftMode = (await draftMode()).isEnabled
9+
const {data} = await sanityFetch({
10+
query: homePageQuery,
11+
perspective: isDraftMode
12+
? await resolvePerspectiveFromCookie({cookies: await cookies()})
13+
: 'published',
14+
stega: isDraftMode,
15+
})
2116

2217
return <HomePage data={data} />
2318
}

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

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ 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'
14+
import type {ProjectBySlugQueryResult} from '@/sanity.types'
1315

1416
type Props = {
1517
params: Promise<{slug: string}>
@@ -19,9 +21,13 @@ export async function generateMetadata(
1921
{params}: Props,
2022
parent: ResolvingMetadata,
2123
): Promise<Metadata> {
24+
const isDraftMode = (await draftMode()).isEnabled
2225
const {data: project} = await sanityFetch({
2326
query: projectBySlugQuery,
2427
params,
28+
perspective: isDraftMode
29+
? await resolvePerspectiveFromCookie({cookies: await cookies()})
30+
: 'published',
2531
stega: false,
2632
})
2733
const ogImage = urlForOpenGraphImage(
@@ -51,13 +57,27 @@ export async function generateStaticParams() {
5157
}
5258

5359
export default async function ProjectSlugRoute({params}: Props) {
54-
const {data} = await sanityFetch({query: projectBySlugQuery, params})
60+
const isDraftMode = (await draftMode()).isEnabled
61+
const {data} = await sanityFetch({
62+
query: projectBySlugQuery,
63+
params,
64+
perspective: isDraftMode
65+
? await resolvePerspectiveFromCookie({cookies: await cookies()})
66+
: 'published',
67+
stega: isDraftMode,
68+
})
5569

5670
// 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
57-
if (!data?._id && !(await draftMode()).isEnabled) {
71+
if (!data?._id && !isDraftMode) {
5872
notFound()
5973
}
6074

75+
return <CachedProjectSlugRoute data={data} />
76+
}
77+
78+
async function CachedProjectSlugRoute({data}: {data: ProjectBySlugQueryResult}) {
79+
'use cache'
80+
6181
const dataAttribute =
6282
data?._id && data._type
6383
? createDataAttribute({

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)