├── .gitignore
├── README.md
├── blog-frontend
├── .env.local.example
├── .gitignore
├── README.md
├── components
│ ├── alert.js
│ ├── avatar.js
│ ├── comments.js
│ ├── container.js
│ ├── cover-image.js
│ ├── date.js
│ ├── footer.js
│ ├── form.js
│ ├── header.js
│ ├── hero-post.js
│ ├── intro.js
│ ├── layout.js
│ ├── markdown-styles.module.css
│ ├── meta.js
│ ├── more-stories.js
│ ├── post-body.js
│ ├── post-header.js
│ ├── post-preview.js
│ ├── post-title.js
│ └── section-separator.js
├── lib
│ ├── api.js
│ ├── constants.js
│ └── sanity.js
├── package.json
├── pages
│ ├── _app.js
│ ├── _document.js
│ ├── api
│ │ ├── createComment.js
│ │ ├── exit-preview.js
│ │ └── preview.js
│ ├── index.js
│ └── posts
│ │ └── [slug].js
├── postcss.config.js
├── public
│ └── favicon
│ │ ├── android-chrome-192x192.png
│ │ ├── android-chrome-512x512.png
│ │ ├── apple-touch-icon.png
│ │ ├── browserconfig.xml
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── favicon.ico
│ │ ├── mstile-150x150.png
│ │ ├── safari-pinned-tab.svg
│ │ └── site.webmanifest
├── schemas
│ └── schema.js
├── styles
│ └── index.css
├── tailwind.config.js
└── yarn.lock
└── studio
├── README.md
├── config
├── .checksums
└── @sanity
│ ├── data-aspects.json
│ ├── default-layout.json
│ ├── default-login.json
│ └── form-builder.json
├── package.json
├── plugins
└── .gitkeep
├── sanity.json
├── schemas
├── author.js
├── blockContent.js
├── category.js
├── comment.js
├── post.js
└── schema.js
├── static
├── .gitkeep
└── favicon.ico
├── tsconfig.json
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | .env
2 | node_modules
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Next.js blog with comment section
2 |
3 | This is a demo of how to add a simple comment section to blog post using [Next.js](https://nextjs.org), [Sanity.io](https://www.sanity.io), and [Vercel](https://vercel.com).
4 |
5 | [You can watch the walkthrough here](https://youtu.be/NzUNMUHxvZ4)
6 |
7 | This repository is just a demo and will not be maintained. You're always welcome to join [our community](https://slack.sanity.io/?utm_source=github&utm_medium=readme&utm_campaign=community) and ask for help there if you're stuck.
8 |
--------------------------------------------------------------------------------
/blog-frontend/.env.local.example:
--------------------------------------------------------------------------------
1 | NEXT_PUBLIC_SANITY_PROJECT_ID=
2 | SANITY_API_TOKEN=
3 | SANITY_PREVIEW_SECRET=
--------------------------------------------------------------------------------
/blog-frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
--------------------------------------------------------------------------------
/blog-frontend/README.md:
--------------------------------------------------------------------------------
1 | # A statically generated blog example using Next.js and Sanity
2 |
3 | This example showcases Next.js's [Static Generation](https://nextjs.org/docs/basic-features/pages) feature using [Sanity](https://www.sanity.io/) as the data source.
4 |
5 | ## Demo
6 |
7 | ### [https://next-blog-sanity.now.sh/](https://next-blog-sanity.now.sh/)
8 |
9 | ## Deploy your own
10 |
11 | Once you have access to [the environment variables you'll need](#step-4-set-up-environment-variables), deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
12 |
13 | [](https://vercel.com/import/git?c=1&s=https://github.com/vercel/next.js/tree/canary/examples/cms-sanity&env=NEXT_PUBLIC_SANITY_PROJECT_ID,SANITY_API_TOKEN,SANITY_PREVIEW_SECRET&envDescription=Required%20to%20connect%20the%20app%20with%20Sanity&envLink=https://vercel.link/cms-sanity-env)
14 |
15 | ### Related examples
16 |
17 | - [WordPress](/examples/cms-wordpress)
18 | - [DatoCMS](/examples/cms-datocms)
19 | - [TakeShape](/examples/cms-takeshape)
20 | - [Prismic](/examples/cms-prismic)
21 | - [Contentful](/examples/cms-contentful)
22 | - [Strapi](/examples/cms-strapi)
23 | - [Agility CMS](/examples/cms-agilitycms)
24 | - [Cosmic](/examples/cms-cosmic)
25 | - [ButterCMS](/examples/cms-buttercms)
26 | - [Storyblok](/examples/cms-storyblok)
27 | - [GraphCMS](/examples/cms-graphcms)
28 | - [Blog Starter](/examples/blog-starter)
29 |
30 | ## How to use
31 |
32 | ### Using `create-next-app`
33 |
34 | Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
35 |
36 | ```bash
37 | npx create-next-app --example cms-sanity cms-sanity-app
38 | # or
39 | yarn create next-app --example cms-sanity cms-sanity-app
40 | ```
41 |
42 | ### Download manually
43 |
44 | Download the example:
45 |
46 | ```bash
47 | curl https://codeload.github.com/vercel/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/cms-sanity
48 | cd cms-sanity
49 | ```
50 |
51 | ## Configuration
52 |
53 | ### Step 1. Create an account and a project on Sanity
54 |
55 | First, [create an account on Sanity](https://sanity.io).
56 |
57 | After creating an account, install the Sanity cli from npm `npm i -g @sanity/cli`.
58 |
59 | ### Step 2. Create a new Sanity project
60 |
61 | In a separate folder run `sanity init` to initialize a new studio project.
62 |
63 | This will be where we manage our data.
64 |
65 | When going through the init phase make sure to select **Yes** to the **Use the default dataset configuration** step and select **Clean project with no predefined schemas** for the **Select project template** step.
66 |
67 | ### Step 3. Generate an API token
68 |
69 | Log into https://manage.sanity.io/ and choose the project you just created. Then from **Settings**, select **API**, then click **Add New Token** and create a token with the **Read** permission.
70 |
71 | ### Step 4. Set up environment variables
72 |
73 | Copy the `.env.local.example` file in this directory to `.env.local` (which will be ignored by Git):
74 |
75 | ```bash
76 | cp .env.local.example .env.local
77 | ```
78 |
79 | Then set each variable on `.env.local`:
80 |
81 | - `NEXT_PUBLIC_SANITY_PROJECT_ID` should be the `projectId` value from the `sanity.json` file created in step 2.
82 | - `SANITY_API_TOKEN` should be the API token generated in the previous step.
83 | - `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).
84 |
85 | Your `.env.local` file should look like this:
86 |
87 | ```bash
88 | NEXT_PUBLIC_SANITY_PROJECT_ID=...
89 | SANITY_API_TOKEN=...
90 | SANITY_PREVIEW_SECRET=...
91 | ```
92 |
93 | ### Step 5. Prepare project for previewing
94 |
95 | Go to https://www.sanity.io/docs/preview-content-on-site and follow the three steps on that page. It should be done inside the studio project generated in Step 2.
96 |
97 | When you get to the second step about creating a file called `resolveProductionUrl.js`, copy the following instead:
98 |
99 | ```js
100 | const previewSecret = 'MY_SECRET' // Copy the string you used for SANITY_PREVIEW_SECRET
101 | const projectUrl = 'http://localhost:3000'
102 |
103 | export default function resolveProductionUrl(document) {
104 | return `${projectUrl}/api/preview?secret=${previewSecret}&slug=${document.slug.current}`
105 | }
106 | ```
107 |
108 | ### Step 6. Copy the schema file
109 |
110 | After initializing your Sanity studio project there should be a `schemas` folder.
111 |
112 | Replace the contents of `schema.js` in the Sanity studio project directory with [`./schemas/schema.js`](./schemas/schema.js) in this example directory. This will set up the schema we’ll use this for this example.
113 |
114 | ### Step 7. Populate Content
115 |
116 | To add some content go to your Sanity studio project directory and run `sanity start`.
117 |
118 | After the project has started and you have navigated to the URL given in the terminal, select **Author** and create a new record.
119 |
120 | - You just need **1 Author record**.
121 | - Use dummy data for the text.
122 | - For the image, you can download one from [Unsplash](https://unsplash.com/).
123 |
124 | Next, select **Post** and create a new record.
125 |
126 | - We recommend creating at least **2 Post records**.
127 | - Use dummy data for the text.
128 | - You can write markdown for the **Content** field.
129 | - For the images, you can download ones from [Unsplash](https://unsplash.com/).
130 | - Pick the **Author** you created earlier.
131 |
132 | **Important:** For each post record, you need to click **Publish** after saving. If not, the post will be in the draft state.
133 |
134 | ### Step 8. Run Next.js in development mode
135 |
136 | ```bash
137 | npm install
138 | npm run dev
139 |
140 | # or
141 |
142 | yarn install
143 | yarn dev
144 | ```
145 |
146 | Your blog should be up and running on [http://localhost:3000](http://localhost:3000)! If it doesn't work, post on [GitHub discussions](https://github.com/vercel/next.js/discussions).
147 |
148 | ### Step 9. Try preview mode
149 |
150 | On Sanity, go to one of the posts you've created and:
151 |
152 | - **Update the title**. For example, you can add `[Draft]` in front of the title.
153 | - As you edit the document it will be saved as a draft, but **DO NOT** click **Publish**. By doing this, the post will be in the draft state.
154 |
155 | Now, if you go to the post page on localhost, you won't see the updated title. However, if you use the **Preview Mode**, you'll be able to see the change ([Documentation](https://nextjs.org/docs/advanced-features/preview-mode)).
156 |
157 | To view the preview, go to the post edit page on Sanity, click the three dots above the document and select **Open preview** ([see the instruction here](https://www.sanity.io/docs/preview-content-on-site))
158 |
159 | You should now be able to see the updated title. To exit Preview Mode, you can click on _"Click here to exit preview mode"_ at the top.
160 |
161 | ### Step 10. Deploy on Vercel
162 |
163 | You can deploy this app to the cloud with [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
164 |
165 | #### Deploy Your Local Project
166 |
167 | To deploy your local project to Vercel, push it to GitHub/GitLab/Bitbucket and [import to Vercel](https://vercel.com/import/git?utm_source=github&utm_medium=readme&utm_campaign=next-example).
168 |
169 | **Important**: When you import your project on Vercel, make sure to click on **Environment Variables** and set them to match your `.env.local` file.
170 |
171 | #### Deploy from Our Template
172 |
173 | Alternatively, you can deploy using our template by clicking on the Deploy button below.
174 |
175 | [](https://vercel.com/import/git?c=1&s=https://github.com/vercel/next.js/tree/canary/examples/cms-sanity&env=NEXT_PUBLIC_SANITY_PROJECT_ID,SANITY_API_TOKEN,SANITY_PREVIEW_SECRET&envDescription=Required%20to%20connect%20the%20app%20with%20Sanity&envLink=https://vercel.link/cms-sanity-env)
176 |
--------------------------------------------------------------------------------
/blog-frontend/components/alert.js:
--------------------------------------------------------------------------------
1 | import Container from './container'
2 | import cn from 'classnames'
3 | import { EXAMPLE_PATH } from '../lib/constants'
4 |
5 | export default function Alert({ preview }) {
6 | return (
7 |
13 |
14 |
15 | {preview ? (
16 | <>
17 | This page is a preview.{' '}
18 |
22 | Click here
23 | {' '}
24 | to exit preview mode.
25 | >
26 | ) : (
27 | <>
28 | The source code for this blog is{' '}
29 |
33 | available on GitHub
34 |
35 | .
36 | >
37 | )}
38 |
39 |
40 |
41 | )
42 | }
43 |
--------------------------------------------------------------------------------
/blog-frontend/components/avatar.js:
--------------------------------------------------------------------------------
1 | export default function Avatar({ name, picture }) {
2 | return (
3 |
4 |
5 |
{name}
6 |
7 | )
8 | }
9 |
--------------------------------------------------------------------------------
/blog-frontend/components/comments.js:
--------------------------------------------------------------------------------
1 | import { Fragment } from 'react'
2 | import Date from './date'
3 |
4 | export function Comments({ comments = [] }) {
5 | return (
6 |
7 | Comments:
8 |
9 | {comments?.map(({ _id, _createdAt, name, email, comment }) => (
10 |
11 |
12 |
15 | {comment}
16 |
17 |
18 | ))}
19 |
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/blog-frontend/components/container.js:
--------------------------------------------------------------------------------
1 | export default function Container({ children }) {
2 | return {children}
3 | }
4 |
--------------------------------------------------------------------------------
/blog-frontend/components/cover-image.js:
--------------------------------------------------------------------------------
1 | import cn from 'classnames'
2 | import Link from 'next/link'
3 | import { imageBuilder } from '../lib/sanity'
4 |
5 | export default function CoverImage({ title, url, slug }) {
6 | const image = (
7 |
16 | )
17 |
18 | return (
19 |
20 | {slug ? (
21 |
22 |
{image}
23 |
24 | ) : (
25 | image
26 | )}
27 |
28 | )
29 | }
30 |
--------------------------------------------------------------------------------
/blog-frontend/components/date.js:
--------------------------------------------------------------------------------
1 | import { isValid, parseISO, format } from 'date-fns'
2 |
3 | export default function Date({ dateString }) {
4 | if (!isValid(parseISO(dateString))) {
5 | return 'No date'
6 | }
7 | const date = parseISO(dateString)
8 | return {format(date, 'LLLL d, yyyy')}
9 | }
10 |
--------------------------------------------------------------------------------
/blog-frontend/components/footer.js:
--------------------------------------------------------------------------------
1 | import Container from './container'
2 | import { EXAMPLE_PATH } from '../lib/constants'
3 |
4 | export default function Footer() {
5 | return (
6 |
7 |
8 |
9 |
10 | Statically Generated with Next.js.
11 |
12 |
26 |
27 |
28 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/blog-frontend/components/form.js:
--------------------------------------------------------------------------------
1 | import {useState} from 'react'
2 | import { useForm } from 'react-hook-form'
3 |
4 | export function Form ({_id}) {
5 | const [formData, setFormData] = useState()
6 | const [isSubmitting, setIsSubmitting] = useState(false)
7 | const [hasSubmitted, setHasSubmitted] = useState(false)
8 | const { register, handleSubmit, watch, errors } = useForm()
9 | const onSubmit = async data => {
10 | setIsSubmitting(true)
11 | let response
12 | setFormData(data)
13 | try {
14 | response = await fetch('/api/createComment', {
15 | method: 'POST',
16 | body: JSON.stringify(data),
17 | type: 'application/json'
18 | })
19 | setIsSubmitting(false)
20 | setHasSubmitted(true)
21 | } catch (err) {
22 | setFormData(err)
23 | }
24 | }
25 |
26 | if (isSubmitting) {
27 | return Submitting comment…
28 | }
29 | if (hasSubmitted) {
30 | return (
31 | <>
32 | Thanks for your comment!
33 |
34 |
35 | Name: {formData.name}
36 | Email: {formData.email}
37 | Comment: {formData.comment}
38 |
39 |
40 | >)
41 | }
42 |
43 | return (
44 |
62 | )
63 | }
64 |
--------------------------------------------------------------------------------
/blog-frontend/components/header.js:
--------------------------------------------------------------------------------
1 | import Link from 'next/link'
2 |
3 | export default function Header() {
4 | return (
5 |
6 |
7 | Blog
8 |
9 | .
10 |
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/blog-frontend/components/hero-post.js:
--------------------------------------------------------------------------------
1 | import Avatar from '../components/avatar'
2 | import Date from '../components/date'
3 | import CoverImage from '../components/cover-image'
4 | import Link from 'next/link'
5 |
6 | export default function HeroPost({
7 | title,
8 | coverImage,
9 | date,
10 | excerpt,
11 | author,
12 | slug,
13 | }) {
14 | return (
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | {title}
24 |
25 |
26 |
27 |
28 |
29 |
30 |
34 |
35 |
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/blog-frontend/components/intro.js:
--------------------------------------------------------------------------------
1 | import { CMS_NAME, CMS_URL } from '../lib/constants'
2 |
3 | export default function Intro() {
4 | return (
5 |
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/blog-frontend/components/layout.js:
--------------------------------------------------------------------------------
1 | import Alert from '../components/alert'
2 | import Footer from '../components/footer'
3 | import Meta from '../components/meta'
4 |
5 | export default function Layout({ preview, children }) {
6 | return (
7 | <>
8 |
9 |
10 |
11 |
{children}
12 |
13 |
14 | >
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/blog-frontend/components/markdown-styles.module.css:
--------------------------------------------------------------------------------
1 | .markdown {
2 | @apply text-lg leading-relaxed;
3 | }
4 |
5 | .markdown p,
6 | .markdown ul,
7 | .markdown ol,
8 | .markdown blockquote {
9 | @apply my-6;
10 | }
11 |
12 | .markdown h2 {
13 | @apply text-3xl mt-12 mb-4 leading-snug;
14 | }
15 |
16 | .markdown h3 {
17 | @apply text-2xl mt-8 mb-4 leading-snug;
18 | }
19 |
--------------------------------------------------------------------------------
/blog-frontend/components/meta.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head'
2 | import { CMS_NAME, HOME_OG_IMAGE_URL } from '../lib/constants'
3 |
4 | export default function Meta() {
5 | return (
6 |
7 |
12 |
18 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
39 |
40 |
41 | )
42 | }
43 |
--------------------------------------------------------------------------------
/blog-frontend/components/more-stories.js:
--------------------------------------------------------------------------------
1 | import PostPreview from '../components/post-preview'
2 |
3 | export default function MoreStories({ posts }) {
4 | return (
5 |
6 |
7 | More Stories
8 |
9 |
10 | {posts.map((post) => (
11 |
20 | ))}
21 |
22 |
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/blog-frontend/components/post-body.js:
--------------------------------------------------------------------------------
1 | import markdownStyles from './markdown-styles.module.css'
2 | import BlockContent from '@sanity/block-content-to-react'
3 |
4 | export default function PostBody({ content }) {
5 | return (
6 |
7 |
8 |
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/blog-frontend/components/post-header.js:
--------------------------------------------------------------------------------
1 | import Avatar from '../components/avatar'
2 | import Date from '../components/date'
3 | import CoverImage from '../components/cover-image'
4 | import PostTitle from '../components/post-title'
5 |
6 | export default function PostHeader({ title, coverImage, date, author }) {
7 | return (
8 | <>
9 | {title}
10 |
13 |
14 |
15 |
16 |
24 | >
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/blog-frontend/components/post-preview.js:
--------------------------------------------------------------------------------
1 | import Avatar from '../components/avatar'
2 | import Date from '../components/date'
3 | import CoverImage from './cover-image'
4 | import Link from 'next/link'
5 |
6 | export default function PostPreview({
7 | title,
8 | coverImage,
9 | date,
10 | excerpt,
11 | author,
12 | slug,
13 | }) {
14 | return (
15 |
16 |
17 |
18 |
19 |
20 |
21 | {title}
22 |
23 |
24 |
25 |
26 |
27 |
{excerpt}
28 |
29 |
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/blog-frontend/components/post-title.js:
--------------------------------------------------------------------------------
1 | export default function PostTitle({ children }) {
2 | return (
3 |
4 | {children}
5 |
6 | )
7 | }
8 |
--------------------------------------------------------------------------------
/blog-frontend/components/section-separator.js:
--------------------------------------------------------------------------------
1 | export default function SectionSeparator() {
2 | return
3 | }
4 |
--------------------------------------------------------------------------------
/blog-frontend/lib/api.js:
--------------------------------------------------------------------------------
1 | import client, { previewClient } from './sanity'
2 |
3 | const getUniquePosts = (posts) => {
4 | const slugs = new Set()
5 | return posts.filter((post) => {
6 | if (slugs.has(post.slug)) {
7 | return false
8 | } else {
9 | slugs.add(post.slug)
10 | return true
11 | }
12 | })
13 | }
14 |
15 | const postFields = `
16 | _id,
17 | name,
18 | title,
19 | 'date': publishedAt,
20 | excerpt,
21 | 'slug': slug.current,
22 | 'coverImage': mainImage.asset->url,
23 | 'author': author->{name, 'picture': image.asset->url},
24 | `
25 |
26 | const getClient = (preview) => (preview ? previewClient : client)
27 |
28 | export async function getPreviewPostBySlug(slug) {
29 | const data = await getClient(true).fetch(
30 | `*[_type == "post" && slug.current == $slug] | order(dpublishedAtate desc){
31 | ${postFields}
32 | body
33 | }`,
34 | { slug }
35 | )
36 | return data[0]
37 | }
38 |
39 | export async function getAllPostsWithSlug() {
40 | const data = await client.fetch(`*[_type == "post"]{ 'slug': slug.current }`)
41 | return data
42 | }
43 |
44 | export async function getAllPostsForHome(preview) {
45 | const results = await getClient(preview)
46 | .fetch(`*[_type == "post"] | order(date desc, _updatedAt desc){
47 | ${postFields}
48 | }`)
49 | return getUniquePosts(results)
50 | }
51 |
52 | export async function getPostAndMorePosts(slug, preview) {
53 | const curClient = getClient(preview)
54 | const [post, morePosts] = await Promise.all([
55 | curClient
56 | .fetch(
57 | `*[_type == "post" && slug.current == $slug] | order(_updatedAt desc) {
58 | ${postFields}
59 | body,
60 | 'comments': *[_type == "comment" && post._ref == ^._id]{_id, name, email, comment, _createdAt}
61 | }`,
62 | { slug }
63 | )
64 | .then((res) => res?.[0]),
65 | curClient.fetch(
66 | `*[_type == "post" && slug.current != $slug] | order(publishedAt desc, _updatedAt desc){
67 | ${postFields}
68 | body,
69 | }[0...2]`,
70 | { slug }
71 | ),
72 | ])
73 | return { post, morePosts: getUniquePosts(morePosts) }
74 | }
75 |
--------------------------------------------------------------------------------
/blog-frontend/lib/constants.js:
--------------------------------------------------------------------------------
1 | export const EXAMPLE_PATH = 'cms-sanity'
2 | export const CMS_NAME = 'Sanity'
3 | export const CMS_URL = 'https://sanity.io/'
4 | export const HOME_OG_IMAGE_URL =
5 | 'https://og-image.now.sh/Next.js%20Blog%20Example%20with%20**Sanity**.png?theme=light&md=1&fontSize=75px&images=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fnextjs-black-logo.svg&images=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB2aWV3Qm94PSIwIDAgMTA1IDIyIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMWVtIj48dGl0bGU%2BU2FuaXR5PC90aXRsZT48cGF0aCBvcGFjaXR5PSIwLjciIGQ9Ik03OC4xNzkzIDcuOTkyNjFWMjEuMDAyOEg3My45MDMxVjEwLjIxMzhMNzguMTc5MyA3Ljk5MjYxWiIgZmlsbD0iY3VycmVudENvbG9yIj48L3BhdGg%2BPHBhdGggb3BhY2l0eT0iMC43IiBkPSJNMjAuOTUxMSAyMS4zM0wzMC45NDQgMTYuMTA1MUwyOS43MTIxIDEyLjkxNDFMMjMuMTMzMiAxNS45ODIxTDIwLjk1MTEgMjEuMzNaIiBmaWxsPSJjdXJyZW50Q29sb3IiPjwvcGF0aD48cGF0aCBvcGFjaXR5PSIwLjUiIGQ9Ik03My45MDMxIDEwLjIwMjdMODQuNzQ0MyA0LjY1NDc3TDgyLjkxMjYgMS41NTcxTDczLjkwMzEgNS45NTk5N1YxMC4yMDI3WiIgZmlsbD0iY3VycmVudENvbG9yIj48L3BhdGg%2BPHBhdGggb3BhY2l0eT0iMC43IiBkPSJNNDMuMzcwNSA2Ljk2MjMzVjIxLjAwMjhIMzkuMjkyN1YxLjAwNzE0TDQzLjM3MDUgNi45NjIzM1oiIGZpbGw9ImN1cnJlbnRDb2xvciI%2BPC9wYXRoPjxwYXRoIG9wYWNpdHk9IjAuNSIgZD0iTTI3LjEyOTkgNi4xODYxN0wyMC45NTExIDIxLjMzTDE3Ljc3MzEgMTguNTk0M0wyNS4xMzUzIDEuMDA3MTRMMjcuMTI5OSA2LjE4NjE3WiIgZmlsbD0iY3VycmVudENvbG9yIj48L3BhdGg%2BPHBhdGggZD0iTTI1LjEzNTMgMS4wMDcxNEgyOS4zNDc3TDM3LjEzODYgMjEuMDAyOEgzMi44MjY5TDI1LjEzNTMgMS4wMDcxNFoiIGZpbGw9ImN1cnJlbnRDb2xvciI%2BPC9wYXRoPjxwYXRoIGQ9Ik00NC4wMDEyIDEuMDA3MTRMNTIuOTgyNCAxNC42NjgyVjIxLjAwMjhMMzkuMjkyNyAxLjAwNzE0SDQ0LjAwMTJaIiBmaWxsPSJjdXJyZW50Q29sb3IiPjwvcGF0aD48cGF0aCBkPSJNNjQuOTE4MyAxLjAwNzE0SDYwLjY3MzlWMjEuMDA2M0g2NC45MTgzVjEuMDA3MTRaIiBmaWxsPSJjdXJyZW50Q29sb3IiPjwvcGF0aD48cGF0aCBkPSJNNzMuOTAzMSA0LjY1NDc0SDY3LjM3VjEuMDA3MTRIODIuNTg2N0w4NC43NDQzIDQuNjU0NzRINzguMTc5M0g3My45MDMxWiIgZmlsbD0iY3VycmVudENvbG9yIj48L3BhdGg%2BPHBhdGggb3BhY2l0eT0iMC41IiBkPSJNOTcuMjc1NCAxMy40MTUzVjIxLjAwMjhIOTMuMDYyOVYxMy40MTUzIiBmaWxsPSJjdXJyZW50Q29sb3IiPjwvcGF0aD48cGF0aCBkPSJNOTMuMDYyOSAxMy40MTUyTDEwMC4xOTEgMS4wMDcxNEgxMDQuNjY2TDk3LjI3NTQgMTMuNDE1Mkg5My4wNjI5WiIgZmlsbD0iY3VycmVudENvbG9yIj48L3BhdGg%2BPHBhdGggb3BhY2l0eT0iMC43IiBkPSJNOTMuMDYzIDEzLjQxNTJMODUuNzM2MyAxLjAwNzE0SDkwLjM0NTZMOTUuMzA5MiA5LjUxMDA4TDkzLjA2MyAxMy40MTUyWiIgZmlsbD0iY3VycmVudENvbG9yIj48L3BhdGg%2BPHBhdGggZD0iTTEuOTYxMjYgMy4zMTQ3OUMxLjk2MTI2IDYuMDk5MjEgMy43MTE0NSA3Ljc1NTk1IDcuMjE1MzYgOC42Mjk1NkwxMC45MjgzIDkuNDc1MzNDMTQuMjQ0NCAxMC4yMjM2IDE2LjI2MzkgMTIuMDgyMiAxNi4yNjM5IDE1LjExMDNDMTYuMjg5NyAxNi40Mjk1IDE1Ljg1MzEgMTcuNzE3MyAxNS4wMjc0IDE4Ljc1NzlDMTUuMDI3NCAxNS43MzY4IDEzLjQzNjcgMTQuMTA0NCA5LjU5OTcyIDEzLjEyMjlMNS45NTQwOSAxMi4zMDg1QzMuMDM0NzUgMTEuNjU0MSAwLjc4MTQ3OCAxMC4xMjYyIDAuNzgxNDc4IDYuODM3MDlDMC43NjYxMjMgNS41NjY5MyAxLjE4MTE2IDQuMzI3ODEgMS45NjEyNiAzLjMxNDc5IiBmaWxsPSJjdXJyZW50Q29sb3IiPjwvcGF0aD48cGF0aCBvcGFjaXR5PSIwLjciIGQ9Ik01Mi45ODI0IDEzLjY0MTVWMS4wMDcxNEg1Ny4wNjAyVjIxLjAwMjhINTIuOTgyNFYxMy42NDE1WiIgZmlsbD0iY3VycmVudENvbG9yIj48L3BhdGg%2BPHBhdGggb3BhY2l0eT0iMC43IiBkPSJNMTIuNzQ1OCAxNC4zNjg5QzE0LjMyOTQgMTUuMzY0MyAxNS4wMjM4IDE2Ljc1NjUgMTUuMDIzOCAxOC43NTQ0QzEzLjcxMyAyMC40MDQxIDExLjQxMDEgMjEuMzMgOC43MDMzMyAyMS4zM0M0LjE0NzE4IDIxLjMzIDAuOTU4NTc3IDE5LjEyNjggMC4yNSAxNS4yOTgySDQuNjI1NDdDNS4xODg3OCAxNy4wNTU5IDYuNjgwMzQgMTcuODcwMyA4LjY3MTQ0IDE3Ljg3MDNDMTEuMTAxOSAxNy44NzAzIDEyLjcxNzQgMTYuNTk2NCAxMi43NDkzIDE0LjM2MTkiIGZpbGw9ImN1cnJlbnRDb2xvciI%2BPC9wYXRoPjxwYXRoIG9wYWNpdHk9IjAuNyIgZD0iTTQuMjM1NjcgNy40NDI2N0MzLjUxMjUgNy4wMjA0NSAyLjkxOTIgNi40MTM3NSAyLjUxODczIDUuNjg2OTdDMi4xMTgyNyA0Ljk2MDE5IDEuOTI1NTggNC4xNDA0NSAxLjk2MTEzIDMuMzE0NzZDMy4yMjU5NCAxLjY3ODkxIDUuNDI2MDggMC42Nzk5OTMgOC4xMDgwNCAwLjY3OTk5M0MxMi43NDkyIDAuNjc5OTkzIDE1LjQzNDcgMy4wODg1MiAxNi4wOTcyIDYuNDc4NTZIMTEuODg4M0MxMS40MjQyIDUuMTQyMDMgMTAuMjYyMSA0LjEwMTM2IDguMTQzNDcgNC4xMDEzNkM1Ljg3OTU3IDQuMTAxMzYgNC4zMzQ4NyA1LjM5NjExIDQuMjQ2MjkgNy40NDI2NyIgZmlsbD0iY3VycmVudENvbG9yIj48L3BhdGg%2BPC9zdmc%2B&widths=undefined&widths=auto&heights=250&heights=150'
6 |
--------------------------------------------------------------------------------
/blog-frontend/lib/sanity.js:
--------------------------------------------------------------------------------
1 | import sanityClient from '@sanity/client'
2 | import sanityImage from '@sanity/image-url'
3 |
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 | }
13 |
14 | const client = sanityClient(options)
15 |
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
25 |
--------------------------------------------------------------------------------
/blog-frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cms-sanity",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "dev": "next",
6 | "build": "next build",
7 | "start": "next start"
8 | },
9 | "dependencies": {
10 | "@sanity/block-content-to-react": "2.0.7",
11 | "@sanity/client": "1.149.18",
12 | "@sanity/image-url": "0.140.19",
13 | "classnames": "2.2.6",
14 | "date-fns": "2.15.0",
15 | "next": "latest",
16 | "react": "^16.13.1",
17 | "react-dom": "^16.13.1",
18 | "react-hook-form": "^6.0.8",
19 | "swr": "^0.2.3",
20 | "tailwind-forms": "^1.0.6"
21 | },
22 | "devDependencies": {
23 | "@fullhuman/postcss-purgecss": "^2.3.0",
24 | "postcss-preset-env": "^6.7.0",
25 | "tailwindcss": "^1.5.2"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/blog-frontend/pages/_app.js:
--------------------------------------------------------------------------------
1 | import '../styles/index.css'
2 |
3 | function MyApp({ Component, pageProps }) {
4 | return
5 | }
6 |
7 | export default MyApp
8 |
--------------------------------------------------------------------------------
/blog-frontend/pages/_document.js:
--------------------------------------------------------------------------------
1 | import Document, { Html, Head, Main, NextScript } from 'next/document'
2 |
3 | export default class MyDocument extends Document {
4 | render() {
5 | return (
6 |
7 |
9 |
10 |
11 |
12 |
13 | )
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/blog-frontend/pages/api/createComment.js:
--------------------------------------------------------------------------------
1 | import {previewClient} from '../../lib/sanity'
2 |
3 | export default async function createComment(req, res) {
4 | const { _id, name, email, comment} = JSON.parse(req.body)
5 | try {
6 | await previewClient.create({
7 | _type: 'comment',
8 | post: {
9 | _type: 'reference',
10 | _ref: _id,
11 | },
12 | name,
13 | email,
14 | comment
15 | })
16 | } catch (err) {
17 | console.error(err)
18 | return res.status(500).json({message: `Couldn't submit comment`, err})
19 | }
20 | return res.status(200).json({ message: 'Comment submitted' })
21 | }
22 |
--------------------------------------------------------------------------------
/blog-frontend/pages/api/exit-preview.js:
--------------------------------------------------------------------------------
1 | export default async function exit(_, res) {
2 | // Exit the current user from "Preview Mode". This function accepts no args.
3 | res.clearPreviewData()
4 |
5 | // Redirect the user back to the index page.
6 | res.writeHead(307, { Location: '/' })
7 | res.end()
8 | }
9 |
--------------------------------------------------------------------------------
/blog-frontend/pages/api/preview.js:
--------------------------------------------------------------------------------
1 | import { getPreviewPostBySlug } from '../../lib/api'
2 |
3 | export default async function preview(req, res) {
4 | // Check the secret and next parameters
5 | // This secret should only be known to this API route and the CMS
6 | if (
7 | req.query.secret !== process.env.SANITY_PREVIEW_SECRET ||
8 | !req.query.slug
9 | ) {
10 | return res.status(401).json({ message: 'Invalid token' })
11 | }
12 |
13 | // Fetch the headless CMS to check if the provided `slug` exists
14 | const post = await getPreviewPostBySlug(req.query.slug)
15 |
16 | // If the slug doesn't exist prevent preview mode from being enabled
17 | if (!post) {
18 | return res.status(401).json({ message: 'Invalid slug' })
19 | }
20 |
21 | // Enable Preview Mode by setting the cookies
22 | res.setPreviewData({})
23 |
24 | // Redirect to the path from the fetched post
25 | // We don't redirect to req.query.slug as that might lead to open redirect vulnerabilities
26 | res.writeHead(307, { Location: `/posts/${post.slug}` })
27 | res.end()
28 | }
29 |
--------------------------------------------------------------------------------
/blog-frontend/pages/index.js:
--------------------------------------------------------------------------------
1 | import Container from '../components/container'
2 | import MoreStories from '../components/more-stories'
3 | import HeroPost from '../components/hero-post'
4 | import Intro from '../components/intro'
5 | import Layout from '../components/layout'
6 | import { getAllPostsForHome } from '../lib/api'
7 | import Head from 'next/head'
8 | import { CMS_NAME } from '../lib/constants'
9 |
10 | export default function Index({ allPosts, preview }) {
11 | const heroPost = allPosts[0]
12 | const morePosts = allPosts.slice(1)
13 | return (
14 | <>
15 |
16 |
17 | Next.js Blog Example with {CMS_NAME}
18 |
19 |
20 |
21 | {heroPost && (
22 |
30 | )}
31 | {morePosts.length > 0 && }
32 |
33 |
34 | >
35 | )
36 | }
37 |
38 | export async function getStaticProps({ preview = false }) {
39 | const allPosts = await getAllPostsForHome(preview)
40 | return {
41 | props: { allPosts, preview },
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/blog-frontend/pages/posts/[slug].js:
--------------------------------------------------------------------------------
1 | import { useRouter } from 'next/router'
2 | import ErrorPage from 'next/error'
3 | import Container from '../../components/container'
4 | import PostBody from '../../components/post-body'
5 | import MoreStories from '../../components/more-stories'
6 | import Header from '../../components/header'
7 | import PostHeader from '../../components/post-header'
8 | import SectionSeparator from '../../components/section-separator'
9 | import Layout from '../../components/layout'
10 | import { getAllPostsWithSlug, getPostAndMorePosts } from '../../lib/api'
11 | import PostTitle from '../../components/post-title'
12 | import {Form} from '../../components/form'
13 | import {Comments} from '../../components/comments'
14 | import Head from 'next/head'
15 | import { CMS_NAME } from '../../lib/constants'
16 |
17 | export default function Post({ post, comments, morePosts, preview }) {
18 | const router = useRouter()
19 | if (!router.isFallback && !post?.slug) {
20 | return
21 | }
22 | return (
23 |
24 |
25 |
26 | {router.isFallback ? (
27 | Loading…
28 | ) : (
29 | <>
30 |
31 |
32 |
33 | {post.title} | Next.js Blog Example with {CMS_NAME}
34 |
35 | {/* */}
36 |
37 |
43 |
44 |
45 |
46 |
47 |
48 | {morePosts.length > 0 && }
49 | >
50 | )}
51 |
52 |
53 | )
54 | }
55 |
56 | export async function getStaticProps({ params, preview = false }) {
57 | const data = await getPostAndMorePosts(params.slug, preview)
58 | return {
59 | props: {
60 | preview,
61 | post: data?.post || null,
62 | morePosts: data?.morePosts || null,
63 | },
64 | }
65 | }
66 |
67 | export async function getStaticPaths() {
68 | const allPosts = await getAllPostsWithSlug()
69 | return {
70 | paths:
71 | allPosts?.map((post) => ({
72 | params: {
73 | slug: post.slug,
74 | },
75 | })) || [],
76 | fallback: true,
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/blog-frontend/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | 'tailwindcss',
4 | ...(process.env.NODE_ENV === 'production'
5 | ? [
6 | [
7 | '@fullhuman/postcss-purgecss',
8 | {
9 | content: [
10 | './pages/**/*.{js,jsx,ts,tsx}',
11 | './components/**/*.{js,jsx,ts,tsx}',
12 | ],
13 | defaultExtractor: (content) =>
14 | content.match(/[\w-/:]+(?
2 |
3 |
4 |
5 |
6 | #000000
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/blog-frontend/public/favicon/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sanity-io/nextjs-blog-with-comments/322eef02bc1f41372b56f4c72d08e807f3f55299/blog-frontend/public/favicon/favicon-16x16.png
--------------------------------------------------------------------------------
/blog-frontend/public/favicon/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sanity-io/nextjs-blog-with-comments/322eef02bc1f41372b56f4c72d08e807f3f55299/blog-frontend/public/favicon/favicon-32x32.png
--------------------------------------------------------------------------------
/blog-frontend/public/favicon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sanity-io/nextjs-blog-with-comments/322eef02bc1f41372b56f4c72d08e807f3f55299/blog-frontend/public/favicon/favicon.ico
--------------------------------------------------------------------------------
/blog-frontend/public/favicon/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sanity-io/nextjs-blog-with-comments/322eef02bc1f41372b56f4c72d08e807f3f55299/blog-frontend/public/favicon/mstile-150x150.png
--------------------------------------------------------------------------------
/blog-frontend/public/favicon/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 | Created by potrace 1.11, written by Peter Selinger 2001-2013
9 |
10 |
12 |
26 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/blog-frontend/public/favicon/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Next.js",
3 | "short_name": "Next.js",
4 | "icons": [
5 | {
6 | "src": "/favicons/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/favicons/android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#000000",
17 | "background_color": "#000000",
18 | "display": "standalone"
19 | }
20 |
--------------------------------------------------------------------------------
/blog-frontend/schemas/schema.js:
--------------------------------------------------------------------------------
1 | // First, we must import the schema creator
2 | import createSchema from 'part:@sanity/base/schema-creator'
3 |
4 | // Then import schema types from any plugins that might expose them
5 | import schemaTypes from 'all:part:@sanity/base/schema-type'
6 |
7 | // Then we give our schema to the builder and provide the result to Sanity
8 | export default createSchema({
9 | // We name our schema
10 | name: 'default',
11 | // Then proceed to concatenate our document type
12 | // to the ones provided by any plugins that are installed
13 | types: schemaTypes.concat([
14 | /* 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 |
33 | {
34 | name: 'post',
35 | type: 'document',
36 | title: 'Post',
37 | fields: [
38 | {
39 | name: 'title',
40 | title: 'Title',
41 | type: 'string',
42 | },
43 | {
44 | name: 'content',
45 | title: 'Content',
46 | type: 'array',
47 | of: [{ type: 'block' }],
48 | },
49 | {
50 | name: 'excerpt',
51 | title: 'Excerpt',
52 | type: 'string',
53 | },
54 | {
55 | name: 'coverImage',
56 | title: 'Cover Image',
57 | type: 'image',
58 | },
59 | {
60 | name: 'date',
61 | title: 'Date',
62 | type: 'datetime',
63 | },
64 | {
65 | name: 'author',
66 | title: 'Author',
67 | type: 'reference',
68 | to: [{ type: 'author' }],
69 | },
70 | {
71 | name: 'slug',
72 | title: 'Slug',
73 | type: 'slug',
74 | },
75 | ],
76 | },
77 | ]),
78 | })
79 |
--------------------------------------------------------------------------------
/blog-frontend/styles/index.css:
--------------------------------------------------------------------------------
1 | /* purgecss start ignore */
2 | @tailwind base;
3 | @tailwind components;
4 | /* purgecss end ignore */
5 | @tailwind utilities;
6 |
--------------------------------------------------------------------------------
/blog-frontend/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | theme: {
3 | extend: {
4 | colors: {
5 | 'accent-1': '#FAFAFA',
6 | 'accent-2': '#EAEAEA',
7 | 'accent-7': '#333',
8 | success: '#0070f3',
9 | cyan: '#79FFE1',
10 | },
11 | spacing: {
12 | 28: '7rem',
13 | },
14 | letterSpacing: {
15 | tighter: '-.04em',
16 | },
17 | lineHeight: {
18 | tight: 1.2,
19 | },
20 | fontSize: {
21 | '5xl': '2.5rem',
22 | '6xl': '2.75rem',
23 | '7xl': '4.5rem',
24 | '8xl': '6.25rem',
25 | },
26 | boxShadow: {
27 | small: '0 5px 10px rgba(0, 0, 0, 0.12)',
28 | medium: '0 8px 30px rgba(0, 0, 0, 0.12)',
29 | },
30 | },
31 | },
32 | plugins: [
33 | require('tailwind-forms')(),
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/studio/README.md:
--------------------------------------------------------------------------------
1 | # Sanity Blogging Content Studio
2 |
3 | Congratulations, you have now installed the Sanity Content Studio, an open source real-time content editing environment connected to the Sanity backend.
4 |
5 | Now you can do the following things:
6 |
7 | - [Read “getting started” in the docs](https://www.sanity.io/docs/introduction/getting-started?utm_source=readme)
8 | - Check out the example frontend: [React/Next.js](https://github.com/sanity-io/tutorial-sanity-blog-react-next)
9 | - [Read the blog post about this template](https://www.sanity.io/blog/build-your-own-blog-with-sanity-and-next-js?utm_source=readme)
10 | - [Join the community Slack](https://slack.sanity.io/?utm_source=readme)
11 | - [Extend and build plugins](https://www.sanity.io/docs/content-studio/extending?utm_source=readme)
12 |
--------------------------------------------------------------------------------
/studio/config/.checksums:
--------------------------------------------------------------------------------
1 | {
2 | "#": "Used by Sanity to keep track of configuration file checksums, do not delete or modify!",
3 | "@sanity/default-layout": "bb034f391ba508a6ca8cd971967cbedeb131c4d19b17b28a0895f32db5d568ea",
4 | "@sanity/default-login": "6fb6d3800aa71346e1b84d95bbcaa287879456f2922372bb0294e30b968cd37f",
5 | "@sanity/form-builder": "b38478227ba5e22c91981da4b53436df22e48ff25238a55a973ed620be5068aa",
6 | "@sanity/data-aspects": "d199e2c199b3e26cd28b68dc84d7fc01c9186bf5089580f2e2446994d36b3cb6"
7 | }
8 |
--------------------------------------------------------------------------------
/studio/config/@sanity/data-aspects.json:
--------------------------------------------------------------------------------
1 | {
2 | "listOptions": {}
3 | }
4 |
--------------------------------------------------------------------------------
/studio/config/@sanity/default-layout.json:
--------------------------------------------------------------------------------
1 | {
2 | "toolSwitcher": {
3 | "order": [],
4 | "hidden": []
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/studio/config/@sanity/default-login.json:
--------------------------------------------------------------------------------
1 | {
2 | "providers": {
3 | "mode": "append",
4 | "redirectOnSingle": false,
5 | "entries": []
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/studio/config/@sanity/form-builder.json:
--------------------------------------------------------------------------------
1 | {
2 | "images": {
3 | "directUploads": true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/studio/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mysanityproject",
3 | "private": true,
4 | "version": "1.0.0",
5 | "description": "",
6 | "main": "package.json",
7 | "author": "Knut Melvær ",
8 | "license": "UNLICENSED",
9 | "scripts": {
10 | "start": "sanity start",
11 | "test": "sanity check"
12 | },
13 | "keywords": [
14 | "sanity"
15 | ],
16 | "dependencies": {
17 | "@sanity/base": "^1.149.18",
18 | "@sanity/components": "^1.149.18",
19 | "@sanity/core": "^1.149.18",
20 | "@sanity/default-layout": "^1.149.18",
21 | "@sanity/default-login": "^1.149.18",
22 | "@sanity/desk-tool": "^1.149.18",
23 | "@sanity/vision": "^1.149.16",
24 | "prop-types": "^15.6",
25 | "react": "^16.2",
26 | "react-dom": "^16.2"
27 | },
28 | "devDependencies": {}
29 | }
30 |
--------------------------------------------------------------------------------
/studio/plugins/.gitkeep:
--------------------------------------------------------------------------------
1 | User-specific packages can be placed here
2 |
--------------------------------------------------------------------------------
/studio/sanity.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "project": {
4 | "name": "My Sanity Project"
5 | },
6 | "api": {
7 | "projectId": "xw41c6ju",
8 | "dataset": "production"
9 | },
10 | "plugins": [
11 | "@sanity/base",
12 | "@sanity/components",
13 | "@sanity/default-layout",
14 | "@sanity/default-login",
15 | "@sanity/desk-tool"
16 | ],
17 | "env": {
18 | "development": {
19 | "plugins": [
20 | "@sanity/vision"
21 | ]
22 | }
23 | },
24 | "parts": [
25 | {
26 | "name": "part:@sanity/base/schema",
27 | "path": "./schemas/schema"
28 | }
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/studio/schemas/author.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: 'author',
3 | title: 'Author',
4 | type: 'document',
5 | fields: [
6 | {
7 | name: 'name',
8 | title: 'Name',
9 | type: 'string'
10 | },
11 | {
12 | name: 'slug',
13 | title: 'Slug',
14 | type: 'slug',
15 | options: {
16 | source: 'name',
17 | maxLength: 96
18 | }
19 | },
20 | {
21 | name: 'image',
22 | title: 'Image',
23 | type: 'image',
24 | options: {
25 | hotspot: true
26 | }
27 | },
28 | {
29 | name: 'bio',
30 | title: 'Bio',
31 | type: 'array',
32 | of: [
33 | {
34 | title: 'Block',
35 | type: 'block',
36 | styles: [{title: 'Normal', value: 'normal'}],
37 | lists: []
38 | }
39 | ]
40 | }
41 | ],
42 | preview: {
43 | select: {
44 | title: 'name',
45 | media: 'image'
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/studio/schemas/blockContent.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This is the schema definition for the rich text fields used for
3 | * for this blog studio. When you import it in schemas.js it can be
4 | * reused in other parts of the studio with:
5 | * {
6 | * name: 'someName',
7 | * title: 'Some title',
8 | * type: 'blockContent'
9 | * }
10 | */
11 | export default {
12 | title: 'Block Content',
13 | name: 'blockContent',
14 | type: 'array',
15 | of: [
16 | {
17 | title: 'Block',
18 | type: 'block',
19 | // Styles let you set what your user can mark up blocks with. These
20 | // correspond with HTML tags, but you can set any title or value
21 | // you want and decide how you want to deal with it where you want to
22 | // use your content.
23 | styles: [
24 | {title: 'Normal', value: 'normal'},
25 | {title: 'H1', value: 'h1'},
26 | {title: 'H2', value: 'h2'},
27 | {title: 'H3', value: 'h3'},
28 | {title: 'H4', value: 'h4'},
29 | {title: 'Quote', value: 'blockquote'}
30 | ],
31 | lists: [{title: 'Bullet', value: 'bullet'}],
32 | // Marks let you mark up inline text in the block editor.
33 | marks: {
34 | // Decorators usually describe a single property – e.g. a typographic
35 | // preference or highlighting by editors.
36 | decorators: [{title: 'Strong', value: 'strong'}, {title: 'Emphasis', value: 'em'}],
37 | // Annotations can be any object structure – e.g. a link or a footnote.
38 | annotations: [
39 | {
40 | title: 'URL',
41 | name: 'link',
42 | type: 'object',
43 | fields: [
44 | {
45 | title: 'URL',
46 | name: 'href',
47 | type: 'url'
48 | }
49 | ]
50 | }
51 | ]
52 | }
53 | },
54 | // You can add additional types here. Note that you can't use
55 | // primitive types such as 'string' and 'number' in the same array
56 | // as a block type.
57 | {
58 | type: 'image',
59 | options: {hotspot: true}
60 | }
61 | ]
62 | }
63 |
--------------------------------------------------------------------------------
/studio/schemas/category.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: 'category',
3 | title: 'Category',
4 | type: 'document',
5 | fields: [
6 | {
7 | name: 'title',
8 | title: 'Title',
9 | type: 'string'
10 | },
11 | {
12 | name: 'description',
13 | title: 'Description',
14 | type: 'text'
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/studio/schemas/comment.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: 'comment',
3 | type: 'document',
4 | title: 'Comment',
5 | fields: [
6 | {
7 | name: 'name',
8 | type: 'string',
9 | },
10 | {
11 | name: 'email',
12 | type: 'string',
13 | },
14 | {
15 | name: 'comment',
16 | type: 'text',
17 | },
18 | {
19 | name: 'post',
20 | type: 'reference',
21 | to: [
22 | {type: 'post'}
23 | ]
24 | }
25 | ],
26 | preview: {
27 | select: {
28 | name: 'name',
29 | comment: 'comment',
30 | post: 'post.title'
31 | },
32 | prepare({name, comment, post}) {
33 | return {
34 | title: `${name} on ${post}`,
35 | subtitle: comment
36 | }
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/studio/schemas/post.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: 'post',
3 | title: 'Post',
4 | type: 'document',
5 | fields: [
6 | {
7 | name: 'title',
8 | title: 'Title',
9 | type: 'string'
10 | },
11 | {
12 | name: 'slug',
13 | title: 'Slug',
14 | type: 'slug',
15 | options: {
16 | source: 'title',
17 | maxLength: 96
18 | }
19 | },
20 | {
21 | name: 'author',
22 | title: 'Author',
23 | type: 'reference',
24 | to: {type: 'author'}
25 | },
26 | {
27 | name: 'mainImage',
28 | title: 'Main image',
29 | type: 'image',
30 | options: {
31 | hotspot: true
32 | }
33 | },
34 | {
35 | name: 'categories',
36 | title: 'Categories',
37 | type: 'array',
38 | of: [{type: 'reference', to: {type: 'category'}}]
39 | },
40 | {
41 | name: 'publishedAt',
42 | title: 'Published at',
43 | type: 'datetime'
44 | },
45 | {
46 | name: 'body',
47 | title: 'Body',
48 | type: 'blockContent'
49 | }
50 | ],
51 |
52 | preview: {
53 | select: {
54 | title: 'title',
55 | author: 'author.name',
56 | media: 'mainImage'
57 | },
58 | prepare(selection) {
59 | const {author} = selection
60 | return Object.assign({}, selection, {
61 | subtitle: author && `by ${author}`
62 | })
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/studio/schemas/schema.js:
--------------------------------------------------------------------------------
1 | // First, we must import the schema creator
2 | import createSchema from 'part:@sanity/base/schema-creator'
3 |
4 | // Then import schema types from any plugins that might expose them
5 | import schemaTypes from 'all:part:@sanity/base/schema-type'
6 |
7 | // We import object and document schemas
8 | import blockContent from './blockContent'
9 | import category from './category'
10 | import post from './post'
11 | import author from './author'
12 | import comment from './comment'
13 |
14 | // Then we give our schema to the builder and provide the result to Sanity
15 | export default createSchema({
16 | // We name our schema
17 | name: 'default',
18 | // Then proceed to concatenate our document type
19 | // to the ones provided by any plugins that are installed
20 | types: schemaTypes.concat([
21 | // The following are document types which will appear
22 | // in the studio.
23 | post,
24 | author,
25 | category,
26 | comment,
27 | // When added to this list, object types can be used as
28 | // { type: 'typename' } in other document schemas
29 | blockContent
30 | ])
31 | })
32 |
--------------------------------------------------------------------------------
/studio/static/.gitkeep:
--------------------------------------------------------------------------------
1 | Files placed here will be served by the Sanity server under the `/static`-prefix
2 |
--------------------------------------------------------------------------------
/studio/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sanity-io/nextjs-blog-with-comments/322eef02bc1f41372b56f4c72d08e807f3f55299/studio/static/favicon.ico
--------------------------------------------------------------------------------
/studio/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["./node_modules/@sanity/base/types/**/*.ts", "./**/*.ts", "./**/*.tsx"]
3 | }
4 |
--------------------------------------------------------------------------------