Skip to content

Commit 2c5af17

Browse files
committed
test cache components
1 parent 55b9c3b commit 2c5af17

File tree

20 files changed

+13323
-19433
lines changed

20 files changed

+13323
-19433
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: 73 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,23 @@ 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 isDraftMode = (await draftMode()).isEnabled
22+
const perspective = (await isDraftMode)
23+
? await resolvePerspectiveFromCookie({cookies: await cookies()})
24+
: 'published'
1925
const [{data: settings}, {data: homePage}] = await Promise.all([
20-
sanityFetch({query: settingsQuery, stega: false}),
21-
sanityFetch({query: homePageQuery, stega: false}),
26+
sanityFetch({query: settingsQuery, perspective, stega: false}),
27+
sanityFetch({query: homePageQuery, perspective, stega: false}),
2228
])
2329

2430
const ogImage = urlForOpenGraphImage(
@@ -43,31 +49,25 @@ export const viewport: Viewport = {
4349
themeColor: '#000',
4450
}
4551

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

app/(personal)/page.tsx

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,31 @@
11
import {HomePage} from '@/components/HomePage'
2-
import {studioUrl} from '@/sanity/lib/api'
2+
import IntroTemplate from '@/intro-template'
33
import {sanityFetch} from '@/sanity/lib/live'
44
import {homePageQuery} from '@/sanity/lib/queries'
5-
import Link from 'next/link'
5+
import {resolvePerspectiveFromCookie} from 'next-sanity/experimental/live'
6+
import {cookies, draftMode} from 'next/headers'
7+
import {Suspense} from 'react'
68

7-
export default async function IndexRoute() {
8-
const {data} = await sanityFetch({query: homePageQuery})
9+
export default async function IndexRoute({
10+
searchParams,
11+
}: {
12+
searchParams: Promise<{[key: string]: string | string[] | undefined}>
13+
}) {
14+
const isDraftMode = (await draftMode()).isEnabled
15+
const {data} = await sanityFetch({
16+
query: homePageQuery,
17+
perspective: isDraftMode
18+
? await resolvePerspectiveFromCookie({cookies: await cookies()})
19+
: 'published',
20+
stega: isDraftMode,
21+
})
922

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-
}
21-
22-
return <HomePage data={data} />
23+
return (
24+
<>
25+
<HomePage data={data} />
26+
<Suspense>
27+
<IntroTemplate searchParams={searchParams} />
28+
</Suspense>
29+
</>
30+
)
2331
}

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

0 commit comments

Comments
 (0)