Skip to content

Commit 394bbf6

Browse files
authoredFeb 9, 2021
Improve sanity.io example (#18227)
1 parent 5d58626 commit 394bbf6

18 files changed

+183
-150
lines changed
 
+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
NEXT_PUBLIC_SANITY_PROJECT_ID=
2+
NEXT_PUBLIC_SANITY_DATASET=
23
SANITY_API_TOKEN=
3-
SANITY_PREVIEW_SECRET=
4+
SANITY_PREVIEW_SECRET=

‎examples/cms-sanity/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,15 @@ cp .env.local.example .env.local
7070
Then set each variable on `.env.local`:
7171

7272
- `NEXT_PUBLIC_SANITY_PROJECT_ID` should be the `projectId` value from the `sanity.json` file created in step 2.
73+
- `NEXT_PUBLIC_SANITY_DATASET` should be the `dataset` value from the `sanity.json` file created in step 2 - defaults to `production` if not set.
7374
- `SANITY_API_TOKEN` should be the API token generated in the previous step.
7475
- `SANITY_PREVIEW_SECRET` can be any random string (but avoid spaces), like `MY_SECRET` - this is used for [Preview Mode](https://nextjs.org/docs/advanced-features/preview-mode).
7576

7677
Your `.env.local` file should look like this:
7778

7879
```bash
7980
NEXT_PUBLIC_SANITY_PROJECT_ID=...
81+
NEXT_PUBLIC_SANITY_DATASET=...
8082
SANITY_API_TOKEN=...
8183
SANITY_PREVIEW_SECRET=...
8284
```

‎examples/cms-sanity/components/avatar.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1+
import { urlForImage } from '../lib/sanity'
2+
13
export default function Avatar({ name, picture }) {
24
return (
35
<div className="flex items-center">
4-
<img src={picture} className="w-12 h-12 rounded-full mr-4" alt={name} />
6+
<img
7+
src={urlForImage(picture).height(96).width(96).fit('crop').url()}
8+
className="w-12 h-12 rounded-full mr-4"
9+
alt={name}
10+
/>
511
<div className="text-xl font-bold">{name}</div>
612
</div>
713
)

‎examples/cms-sanity/components/cover-image.js

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
import cn from 'classnames'
22
import Link from 'next/link'
3-
import { imageBuilder } from '../lib/sanity'
3+
import { urlForImage } from '../lib/sanity'
44

5-
export default function CoverImage({ title, url, slug }) {
6-
const image = (
5+
export default function CoverImage({ title, slug, image: source }) {
6+
const image = source ? (
77
<img
88
width={2000}
99
height={1000}
1010
alt={`Cover Image for ${title}`}
1111
className={cn('shadow-small', {
1212
'hover:shadow-medium transition-shadow duration-200': slug,
1313
})}
14-
src={imageBuilder.image(url).height(1000).width(2000).url()}
14+
src={urlForImage(source).height(1000).width(2000).url()}
1515
/>
16+
) : (
17+
<div style={{ paddingTop: '50%', backgroundColor: '#ddd' }} />
1618
)
1719

1820
return (

‎examples/cms-sanity/components/hero-post.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export default function HeroPost({
1414
return (
1515
<section>
1616
<div className="mb-8 md:mb-16">
17-
<CoverImage slug={slug} title={title} url={coverImage} />
17+
<CoverImage slug={slug} title={title} image={coverImage} />
1818
</div>
1919
<div className="md:grid md:grid-cols-2 md:col-gap-16 lg:col-gap-8 mb-20 md:mb-28">
2020
<div>

‎examples/cms-sanity/components/meta.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export default function Meta() {
3636
name="description"
3737
content={`A statically generated blog example using Next.js and ${CMS_NAME}.`}
3838
/>
39-
<meta property="og:image" content={HOME_OG_IMAGE_URL} />
39+
<meta property="og:image" content={HOME_OG_IMAGE_URL} key="ogImage" />
4040
</Head>
4141
)
4242
}

‎examples/cms-sanity/components/post-header.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export default function PostHeader({ title, coverImage, date, author }) {
1111
<Avatar name={author.name} picture={author.picture} />
1212
</div>
1313
<div className="mb-8 md:mb-16 sm:mx-0">
14-
<CoverImage title={title} url={coverImage} />
14+
<CoverImage title={title} image={coverImage} />
1515
</div>
1616
<div className="max-w-2xl mx-auto">
1717
<div className="block md:hidden mb-6">

‎examples/cms-sanity/components/post-preview.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export default function PostPreview({
1414
return (
1515
<div>
1616
<div className="mb-5">
17-
<CoverImage slug={slug} title={title} url={coverImage} />
17+
<CoverImage slug={slug} title={title} image={coverImage} />
1818
</div>
1919
<h3 className="text-3xl mb-3 leading-snug">
2020
<Link as={`/posts/${slug}`} href="/posts/[slug]">

‎examples/cms-sanity/lib/api.js

-72
This file was deleted.

‎examples/cms-sanity/lib/config.js

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export const sanityConfig = {
2+
// Find your project ID and dataset in `sanity.json` in your studio project
3+
dataset: process.env.NEXT_PUBLIC_SANITY_DATASET || 'production',
4+
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
5+
useCdn: process.env.NODE_ENV === 'production',
6+
// useCdn == true gives fast, cheap responses using a globally distributed cache.
7+
// Set this to false if your application require the freshest possible
8+
// data always (potentially slightly slower and a bit more expensive).
9+
}

‎examples/cms-sanity/lib/queries.js

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
const postFields = `
2+
_id,
3+
name,
4+
title,
5+
date,
6+
excerpt,
7+
coverImage,
8+
"slug": slug.current,
9+
"author": author->{name, picture},
10+
`
11+
12+
export const indexQuery = `
13+
*[_type == "post"] | order(date desc, _updatedAt desc) {
14+
${postFields}
15+
}`
16+
17+
export const postQuery = `
18+
{
19+
"post": *[_type == "post" && slug.current == $slug] | order(_updatedAt desc) | [0] {
20+
content,
21+
${postFields}
22+
},
23+
"morePosts": *[_type == "post" && slug.current != $slug] | order(date desc, _updatedAt desc) | [0...2] {
24+
content,
25+
${postFields}
26+
}
27+
}`
28+
29+
export const postSlugsQuery = `
30+
*[_type == "post" && defined(slug.current)][].slug.current
31+
`
32+
33+
export const postBySlugQuery = `
34+
*[_type == "post" && slug.current == $slug][0] {
35+
${postFields}
36+
}
37+
`

‎examples/cms-sanity/lib/sanity.js

+11-21
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,14 @@
1-
import sanityClient from '@sanity/client'
2-
import sanityImage from '@sanity/image-url'
1+
import {
2+
createImageUrlBuilder,
3+
createPreviewSubscriptionHook,
4+
} from 'next-sanity'
5+
import { sanityConfig } from './config'
36

4-
const options = {
5-
// Find your project ID and dataset in `sanity.json` in your studio project
6-
dataset: 'production',
7-
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
8-
useCdn: process.env.NODE_ENV === 'production',
9-
// useCdn == true gives fast, cheap responses using a globally distributed cache.
10-
// Set this to false if your application require the freshest possible
11-
// data always (potentially slightly slower and a bit more expensive).
12-
}
7+
export const imageBuilder = createImageUrlBuilder(sanityConfig)
138

14-
const client = sanityClient(options)
9+
export const urlForImage = (source) =>
10+
imageBuilder.image(source).auto('format').fit('max')
1511

16-
export const imageBuilder = sanityImage(client)
17-
18-
export const previewClient = sanityClient({
19-
...options,
20-
useCdn: false,
21-
token: process.env.SANITY_API_TOKEN,
22-
})
23-
24-
export default client
12+
export const usePreviewSubscription = createPreviewSubscriptionHook(
13+
sanityConfig
14+
)
+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* Server-side Sanity utilities. By having these in a separate file from the
3+
* utilities we use on the client side, we are able to tree-shake (remove)
4+
* code that is not used on the client side.
5+
*/
6+
import { createClient } from 'next-sanity'
7+
import { sanityConfig } from './config'
8+
9+
export const sanityClient = createClient(sanityConfig)
10+
11+
export const previewClient = createClient({
12+
...sanityConfig,
13+
useCdn: false,
14+
token: process.env.SANITY_API_TOKEN,
15+
})
16+
17+
export const getClient = (preview) => (preview ? previewClient : sanityClient)
18+
19+
export function overlayDrafts(docs) {
20+
const documents = docs || []
21+
const overlayed = documents.reduce((map, doc) => {
22+
if (!doc._id) {
23+
throw new Error('Ensure that `_id` is included in query projection')
24+
}
25+
26+
const isDraft = doc._id.startsWith('drafts.')
27+
const id = isDraft ? doc._id.slice(7) : doc._id
28+
return isDraft || !map.has(id) ? map.set(id, doc) : map
29+
}, new Map())
30+
31+
return Array.from(overlayed.values())
32+
}

‎examples/cms-sanity/package.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@
88
},
99
"dependencies": {
1010
"@sanity/block-content-to-react": "2.0.7",
11-
"@sanity/client": "1.149.2",
12-
"@sanity/image-url": "0.140.17",
1311
"classnames": "2.2.6",
1412
"date-fns": "2.10.0",
13+
"next-sanity": "0.1.5",
1514
"next": "latest",
1615
"react": "^16.13.0",
1716
"react-dom": "^16.13.0"

‎examples/cms-sanity/pages/api/preview.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { getPreviewPostBySlug } from '../../lib/api'
1+
import { postBySlugQuery } from '../../lib/queries'
2+
import { previewClient } from '../../lib/sanity.server'
23

34
export default async function preview(req, res) {
45
// Check the secret and next parameters
@@ -10,8 +11,10 @@ export default async function preview(req, res) {
1011
return res.status(401).json({ message: 'Invalid token' })
1112
}
1213

13-
// Fetch the headless CMS to check if the provided `slug` exists
14-
const post = await getPreviewPostBySlug(req.query.slug)
14+
// Check if the post with the given `slug` exists
15+
const post = await previewClient.fetch(postBySlugQuery, {
16+
slug: req.query.slug,
17+
})
1518

1619
// If the slug doesn't exist prevent preview mode from being enabled
1720
if (!post) {

‎examples/cms-sanity/pages/index.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
import Head from 'next/head'
12
import Container from '../components/container'
23
import MoreStories from '../components/more-stories'
34
import HeroPost from '../components/hero-post'
45
import Intro from '../components/intro'
56
import Layout from '../components/layout'
6-
import { getAllPostsForHome } from '../lib/api'
7-
import Head from 'next/head'
87
import { CMS_NAME } from '../lib/constants'
8+
import { indexQuery } from '../lib/queries'
9+
import { getClient, overlayDrafts } from '../lib/sanity.server'
910

1011
export default function Index({ allPosts, preview }) {
1112
const heroPost = allPosts[0]
@@ -36,7 +37,7 @@ export default function Index({ allPosts, preview }) {
3637
}
3738

3839
export async function getStaticProps({ preview = false }) {
39-
const allPosts = await getAllPostsForHome(preview)
40+
const allPosts = overlayDrafts(await getClient(preview).fetch(indexQuery))
4041
return {
4142
props: { allPosts, preview },
4243
}

‎examples/cms-sanity/pages/posts/[slug].js

+38-15
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Head from 'next/head'
12
import { useRouter } from 'next/router'
23
import ErrorPage from 'next/error'
34
import Container from '../../components/container'
@@ -7,16 +8,28 @@ import Header from '../../components/header'
78
import PostHeader from '../../components/post-header'
89
import SectionSeparator from '../../components/section-separator'
910
import Layout from '../../components/layout'
10-
import { getAllPostsWithSlug, getPostAndMorePosts } from '../../lib/api'
1111
import PostTitle from '../../components/post-title'
12-
import Head from 'next/head'
1312
import { CMS_NAME } from '../../lib/constants'
13+
import { postQuery, postSlugsQuery } from '../../lib/queries'
14+
import { urlForImage, usePreviewSubscription } from '../../lib/sanity'
15+
import { sanityClient, getClient, overlayDrafts } from '../../lib/sanity.server'
1416

15-
export default function Post({ post, morePosts, preview }) {
17+
export default function Post({ data = {}, preview }) {
1618
const router = useRouter()
17-
if (!router.isFallback && !post?.slug) {
19+
20+
const slug = data?.post?.slug
21+
const {
22+
data: { post, morePosts },
23+
} = usePreviewSubscription(postQuery, {
24+
params: { slug },
25+
initialData: data,
26+
enabled: preview && slug,
27+
})
28+
29+
if (!router.isFallback && !slug) {
1830
return <ErrorPage statusCode={404} />
1931
}
32+
2033
return (
2134
<Layout preview={preview}>
2235
<Container>
@@ -30,7 +43,17 @@ export default function Post({ post, morePosts, preview }) {
3043
<title>
3144
{post.title} | Next.js Blog Example with {CMS_NAME}
3245
</title>
33-
{/* <meta property="og:image" content={post.ogImage.url} /> */}
46+
{post.coverImage && (
47+
<meta
48+
key="ogImage"
49+
property="og:image"
50+
content={urlForImage(post.coverImage)
51+
.width(1200)
52+
.height(627)
53+
.fit('crop')
54+
.url()}
55+
/>
56+
)}
3457
</Head>
3558
<PostHeader
3659
title={post.title}
@@ -50,25 +73,25 @@ export default function Post({ post, morePosts, preview }) {
5073
}
5174

5275
export async function getStaticProps({ params, preview = false }) {
53-
const data = await getPostAndMorePosts(params.slug, preview)
76+
const { post, morePosts } = await getClient(preview).fetch(postQuery, {
77+
slug: params.slug,
78+
})
79+
5480
return {
5581
props: {
5682
preview,
57-
post: data?.post || null,
58-
morePosts: data?.morePosts || null,
83+
data: {
84+
post,
85+
morePosts: overlayDrafts(morePosts),
86+
},
5987
},
6088
}
6189
}
6290

6391
export async function getStaticPaths() {
64-
const allPosts = await getAllPostsWithSlug()
92+
const paths = await sanityClient.fetch(postSlugsQuery)
6593
return {
66-
paths:
67-
allPosts?.map((post) => ({
68-
params: {
69-
slug: post.slug,
70-
},
71-
})) || [],
94+
paths: paths.map((slug) => ({ params: { slug } })),
7295
fallback: true,
7396
}
7497
}

‎examples/cms-sanity/schemas/schema.js

+24-24
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,6 @@ export default createSchema({
1212
// to the ones provided by any plugins that are installed
1313
types: schemaTypes.concat([
1414
/* Your types here! */
15-
{
16-
name: 'author',
17-
type: 'document',
18-
title: 'Author',
19-
fields: [
20-
{
21-
name: 'name',
22-
title: 'Name',
23-
type: 'string',
24-
},
25-
{
26-
name: 'picture',
27-
title: 'Picture',
28-
type: 'image',
29-
},
30-
],
31-
},
32-
3315
{
3416
name: 'post',
3517
type: 'document',
@@ -40,6 +22,14 @@ export default createSchema({
4022
title: 'Title',
4123
type: 'string',
4224
},
25+
{
26+
name: 'slug',
27+
title: 'Slug',
28+
type: 'slug',
29+
options: {
30+
source: 'title',
31+
},
32+
},
4333
{
4434
name: 'content',
4535
title: 'Content',
@@ -67,13 +57,23 @@ export default createSchema({
6757
type: 'reference',
6858
to: [{ type: 'author' }],
6959
},
60+
],
61+
},
62+
63+
{
64+
name: 'author',
65+
type: 'document',
66+
title: 'Author',
67+
fields: [
7068
{
71-
name: 'slug',
72-
title: 'Slug',
73-
type: 'slug',
74-
options: {
75-
source: 'title',
76-
},
69+
name: 'name',
70+
title: 'Name',
71+
type: 'string',
72+
},
73+
{
74+
name: 'picture',
75+
title: 'Picture',
76+
type: 'image',
7777
},
7878
],
7979
},

0 commit comments

Comments
 (0)
Please sign in to comment.