Skip to content

Commit 30fa60f

Browse files
committed
use cache components
1 parent 7c6ce2a commit 30fa60f

File tree

19 files changed

+572
-381
lines changed

19 files changed

+572
-381
lines changed

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

Lines changed: 72 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,45 @@
11
import {CustomPortableText} from '@/components/CustomPortableText'
22
import {Header} from '@/components/Header'
3-
import {sanityFetch} from '@/sanity/lib/live'
4-
import {pagesBySlugQuery, slugsByTypeQuery} from '@/sanity/lib/queries'
3+
import {
4+
isPreviewMode,
5+
sanityFetch,
6+
sanityFetchMetadata,
7+
sanityFetchStaticParams,
8+
} from '@/sanity/lib/live'
9+
import {slugsByTypeQuery} from '@/sanity/lib/queries'
510
import type {Metadata, ResolvingMetadata} from 'next'
6-
import {toPlainText, type PortableTextBlock} from 'next-sanity'
7-
import {draftMode} from 'next/headers'
11+
import {defineQuery, toPlainText, type PortableTextBlock} from 'next-sanity'
812
import {notFound} from 'next/navigation'
13+
import {Suspense} from 'react'
914

1015
type Props = {
1116
params: Promise<{slug: string}>
1217
}
1318

19+
export async function generateStaticParams() {
20+
return sanityFetchStaticParams({
21+
query: slugsByTypeQuery,
22+
params: {type: 'page'},
23+
})
24+
}
25+
26+
const pagesBySlugQuery = defineQuery(`
27+
*[_type == "page" && slug.current == $slug][0] {
28+
_id,
29+
_type,
30+
body,
31+
overview,
32+
title,
33+
"slug": slug.current,
34+
}
35+
`)
1436
export async function generateMetadata(
1537
{params}: Props,
1638
parent: ResolvingMetadata,
1739
): Promise<Metadata> {
18-
const {data: page} = await sanityFetch({
40+
const {data: page} = await sanityFetchMetadata({
1941
query: pagesBySlugQuery,
20-
params,
21-
stega: false,
42+
params: await params,
2243
})
2344

2445
return {
@@ -27,50 +48,61 @@ export async function generateMetadata(
2748
}
2849
}
2950

30-
export async function generateStaticParams() {
31-
const {data} = await sanityFetch({
32-
query: slugsByTypeQuery,
33-
params: {type: 'page'},
34-
stega: false,
35-
perspective: 'published',
36-
})
37-
return data
51+
export default function PageSlugRoute({params}: Props) {
52+
return (
53+
<div>
54+
<div className="mb-14">
55+
<Suspense
56+
fallback={
57+
<Header
58+
id={null}
59+
type={null}
60+
path={['overview']}
61+
title="Loading…"
62+
description={null}
63+
loading
64+
/>
65+
}
66+
>
67+
<PageSlugRouteContent params={params} />
68+
</Suspense>
69+
</div>
70+
<div className="absolute left-0 w-screen border-t" />
71+
</div>
72+
)
3873
}
3974

40-
export default async function PageSlugRoute({params}: Props) {
41-
const {data} = await sanityFetch({query: pagesBySlugQuery, params})
75+
async function PageSlugRouteContent({params}: Props) {
76+
const {data} = await sanityFetch({query: pagesBySlugQuery, params: await params})
4277

43-
// 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) {
78+
// Only show the 404 page if we're in production, when in preview mode we might be about to create a page on this slug, and live reload won't work on the 404 route
79+
if (!data?._id && !(await isPreviewMode())) {
4580
notFound()
4681
}
4782

4883
const {body, overview, title} = data ?? {}
4984

5085
return (
51-
<div>
52-
<div className="mb-14">
53-
{/* Header */}
54-
<Header
86+
<>
87+
{/* Header */}
88+
<Header
89+
id={data?._id || null}
90+
type={data?._type || null}
91+
path={['overview']}
92+
title={title || (data?._id ? 'Untitled' : '404 Page Not Found')}
93+
description={overview}
94+
/>
95+
96+
{/* Body */}
97+
{body && (
98+
<CustomPortableText
5599
id={data?._id || null}
56100
type={data?._type || null}
57-
path={['overview']}
58-
title={title || (data?._id ? 'Untitled' : '404 Page Not Found')}
59-
description={overview}
101+
path={['body']}
102+
paragraphClasses="font-serif max-w-3xl text-gray-600 text-xl"
103+
value={body as unknown as PortableTextBlock[]}
60104
/>
61-
62-
{/* Body */}
63-
{body && (
64-
<CustomPortableText
65-
id={data?._id || null}
66-
type={data?._type || null}
67-
path={['body']}
68-
paragraphClasses="font-serif max-w-3xl text-gray-600 text-xl"
69-
value={body as unknown as PortableTextBlock[]}
70-
/>
71-
)}
72-
</div>
73-
<div className="absolute left-0 w-screen border-t" />
74-
</div>
105+
)}
106+
</>
75107
)
76108
}

app/(personal)/layout.tsx

Lines changed: 32 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,48 @@
11
import '@/styles/index.css'
2-
import {CustomPortableText} from '@/components/CustomPortableText'
3-
import {Navbar} from '@/components/Navbar'
2+
import {FallbackNavbar, Navbar} from '@/components/Navbar'
43
import IntroTemplate from '@/intro-template'
5-
import {sanityFetch, SanityLive} from '@/sanity/lib/live'
6-
import {homePageQuery, settingsQuery} from '@/sanity/lib/queries'
4+
import {isPreviewMode, sanityFetchMetadata, SanityLive} from '@/sanity/lib/live'
75
import {urlForOpenGraphImage} from '@/sanity/lib/utils'
86
import type {Metadata, Viewport} from 'next'
9-
import {toPlainText, type PortableTextBlock} from 'next-sanity'
7+
import {defineQuery} from 'next-sanity'
108
import {VisualEditing} from 'next-sanity/visual-editing'
119
import {draftMode} from 'next/headers'
1210
import {Suspense} from 'react'
1311
import {Toaster} from 'sonner'
1412
import {handleError} from './client-functions'
1513
import {DraftModeToast} from './DraftModeToast'
1614
import {SpeedInsights} from '@vercel/speed-insights/next'
15+
import {DEFAULT_TITLE} from '../constants'
16+
import {Footer} from '@/components/Footer'
1717

1818
export async function generateMetadata(): Promise<Metadata> {
19-
const [{data: settings}, {data: homePage}] = await Promise.all([
20-
sanityFetch({query: settingsQuery, stega: false}),
21-
sanityFetch({query: homePageQuery, stega: false}),
22-
])
19+
// Only select exactly the fields we need
20+
const layoutMetadataQuery = defineQuery(`{
21+
"settings": *[_type == "settings"][0]{ogImage},
22+
"home": *[_type == "home"][0]{
23+
title,
24+
// Render portabletext to string
25+
"description": pt::text(overview)
26+
}
27+
}`)
28+
const {data, tags} = await sanityFetchMetadata({
29+
query: layoutMetadataQuery,
30+
params: {defaultTitle: DEFAULT_TITLE},
31+
})
2332

2433
const ogImage = urlForOpenGraphImage(
2534
// @ts-expect-error - @TODO update @sanity/image-url types so it's compatible
26-
settings?.ogImage,
35+
data?.ogImage,
2736
)
2837
return {
29-
title: homePage?.title
38+
title: data?.home?.title
3039
? {
31-
template: `%s | ${homePage.title}`,
32-
default: homePage.title || 'Personal website',
40+
template: `%s | ${data.home.title}`,
41+
default: data.home.title,
3342
}
34-
: undefined,
35-
description: homePage?.overview ? toPlainText(homePage.overview) : undefined,
43+
: DEFAULT_TITLE,
44+
// @TODO truncate to max length of 155 characters
45+
description: data?.home?.description?.trim().split('\n').join(' ').split(' ').join(' '),
3646
openGraph: {
3747
images: ogImage ? [ogImage] : [],
3848
},
@@ -43,36 +53,23 @@ export const viewport: Viewport = {
4353
themeColor: '#000',
4454
}
4555

46-
export default async function IndexRoute({children}: {children: React.ReactNode}) {
47-
const {data} = await sanityFetch({query: settingsQuery})
56+
export default async function PersonalWebsiteLayout({children}: {children: React.ReactNode}) {
4857
return (
4958
<>
5059
<div className="flex min-h-screen flex-col bg-white text-black">
51-
<Navbar data={data} />
60+
<Suspense fallback={<FallbackNavbar />}>
61+
<Navbar />
62+
</Suspense>
5263
<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-
/>
62-
)}
63-
</footer>
64+
<Footer />
6465
<Suspense>
6566
<IntroTemplate />
6667
</Suspense>
6768
</div>
6869
<Toaster />
70+
{(await isPreviewMode()) && <VisualEditing />}
6971
<SanityLive onError={handleError} />
70-
{(await draftMode()).isEnabled && (
71-
<>
72-
<DraftModeToast />
73-
<VisualEditing />
74-
</>
75-
)}
72+
{(await draftMode()).isEnabled && <DraftModeToast />}
7673
<SpeedInsights />
7774
</>
7875
)

app/(personal)/page.tsx

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,45 @@
1-
import {HomePage} from '@/components/HomePage'
1+
import {Header} from '@/components/Header'
2+
import {OptimisticSortOrder} from '@/components/OptimisticSortOrder'
3+
import {ProjectListItem} from '@/components/ProjectListItem'
24
import {studioUrl} from '@/sanity/lib/api'
35
import {sanityFetch} from '@/sanity/lib/live'
4-
import {homePageQuery} from '@/sanity/lib/queries'
6+
import {resolveHref} from '@/sanity/lib/utils'
7+
import {createDataAttribute, defineQuery} from 'next-sanity'
58
import Link from 'next/link'
9+
import {Suspense} from 'react'
610

7-
export default async function IndexRoute() {
11+
const homePageQuery = defineQuery(`
12+
*[_type == "home"][0]{
13+
_id,
14+
_type,
15+
overview,
16+
showcaseProjects[]{
17+
_key,
18+
...@->{
19+
_id,
20+
_type,
21+
coverImage,
22+
overview,
23+
"slug": slug.current,
24+
tags,
25+
title,
26+
}
27+
},
28+
title,
29+
}
30+
`)
31+
32+
export default function HomePage() {
33+
return (
34+
<div className="space-y-20">
35+
<Suspense fallback={<div className="w-full h-screen" />}>
36+
<HomePageContent />
37+
</Suspense>
38+
</div>
39+
)
40+
}
41+
42+
async function HomePageContent() {
843
const {data} = await sanityFetch({query: homePageQuery})
944

1045
if (!data) {
@@ -19,5 +54,54 @@ export default async function IndexRoute() {
1954
)
2055
}
2156

22-
return <HomePage data={data} />
57+
// Default to an empty object to allow previews on non-existent documents
58+
const {overview = [], showcaseProjects = [], title = ''} = data ?? {}
59+
60+
const dataAttribute =
61+
data?._id && data?._type
62+
? createDataAttribute({
63+
baseUrl: studioUrl,
64+
id: data._id,
65+
type: data._type,
66+
})
67+
: null
68+
69+
return (
70+
<>
71+
{/* Header */}
72+
{title && (
73+
<Header
74+
id={data?._id || null}
75+
type={data?._type || null}
76+
path={['overview']}
77+
centered
78+
title={title}
79+
description={overview}
80+
/>
81+
)}
82+
{/* Showcase projects */}
83+
<div className="mx-auto max-w-[100rem] rounded-md border">
84+
<OptimisticSortOrder id={data?._id} path={'showcaseProjects'}>
85+
{showcaseProjects &&
86+
showcaseProjects.length > 0 &&
87+
showcaseProjects.map((project) => {
88+
const href = resolveHref(project?._type, project?.slug)
89+
if (!href) {
90+
return null
91+
}
92+
return (
93+
<Link
94+
className="flex flex-col gap-x-5 p-2 transition odd:border-b odd:border-t hover:bg-gray-50/50 xl:flex-row odd:xl:flex-row-reverse"
95+
key={project._key}
96+
href={href}
97+
data-sanity={dataAttribute?.(['showcaseProjects', {_key: project._key}])}
98+
>
99+
<ProjectListItem project={project as any} />
100+
</Link>
101+
)
102+
})}
103+
</OptimisticSortOrder>
104+
</div>
105+
</>
106+
)
23107
}

0 commit comments

Comments
 (0)