@@ -8,17 +8,21 @@ import {urlForOpenGraphImage} from '@/sanity/lib/utils'
88import type { Metadata , Viewport } from 'next'
99import { toPlainText , type PortableTextBlock } from 'next-sanity'
1010import { VisualEditing } from 'next-sanity/visual-editing'
11- import { draftMode } from 'next/headers'
11+ import { cookies , draftMode } from 'next/headers'
1212import { Suspense } from 'react'
1313import { Toaster } from 'sonner'
1414import { handleError } from './client-functions'
1515import { DraftModeToast } from './DraftModeToast'
1616import { SpeedInsights } from '@vercel/speed-insights/next'
17+ import { resolvePerspectiveFromCookie } from 'next-sanity/experimental/live'
18+ import type { SettingsQueryResult } from '@/sanity.types'
1719
1820export 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+ }
0 commit comments