├── studio
├── plugins
│ └── .gitkeep
├── .eslintrc
├── config
│ ├── @sanity
│ │ ├── data-aspects.json
│ │ ├── default-layout.json
│ │ └── default-login.json
│ └── .checksums
├── static
│ └── .gitkeep
├── sanity.cli.ts
├── schemas
│ ├── index.ts
│ ├── category.ts
│ ├── author.ts
│ ├── post.ts
│ └── blockContent.ts
├── sanity.config.ts
├── .gitignore
├── tsconfig.json
├── README.md
└── package.json
├── frontend
├── .eslintrc.json
├── public
│ ├── favicon.ico
│ ├── vercel.svg
│ ├── thirteen.svg
│ └── next.svg
├── next.config.js
├── pages
│ ├── _app.tsx
│ ├── _document.tsx
│ ├── api
│ │ └── hello.ts
│ ├── index.tsx
│ └── post
│ │ └── [slug].tsx
├── client.ts
├── .gitignore
├── tsconfig.json
├── package.json
└── README.md
├── .gitignore
├── .editorconfig
└── README.md
/studio/plugins/.gitkeep:
--------------------------------------------------------------------------------
1 | User-specific packages can be placed here
2 |
--------------------------------------------------------------------------------
/frontend/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/studio/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@sanity/eslint-config-studio"
3 | }
4 |
--------------------------------------------------------------------------------
/studio/config/@sanity/data-aspects.json:
--------------------------------------------------------------------------------
1 | {
2 | "listOptions": {}
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | .next
4 | out
5 | .DS_Store
6 | sanitytutorialblog
--------------------------------------------------------------------------------
/studio/static/.gitkeep:
--------------------------------------------------------------------------------
1 | Files placed here will be served by the Sanity server under the `/static`-prefix
2 |
--------------------------------------------------------------------------------
/studio/config/@sanity/default-layout.json:
--------------------------------------------------------------------------------
1 | {
2 | "toolSwitcher": {
3 | "order": [],
4 | "hidden": []
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sanity-io/tutorial-sanity-blog-react-next/HEAD/frontend/public/favicon.ico
--------------------------------------------------------------------------------
/frontend/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | }
5 |
6 | module.exports = nextConfig
7 |
--------------------------------------------------------------------------------
/studio/config/@sanity/default-login.json:
--------------------------------------------------------------------------------
1 | {
2 | "providers": {
3 | "mode": "append",
4 | "redirectOnSingle": false,
5 | "entries": []
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = false
--------------------------------------------------------------------------------
/frontend/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import type { AppProps } from 'next/app'
2 |
3 | export default function App({ Component, pageProps }: AppProps) {
4 | return
5 | }
6 |
--------------------------------------------------------------------------------
/studio/sanity.cli.ts:
--------------------------------------------------------------------------------
1 | import {defineCliConfig} from 'sanity/cli'
2 |
3 | export default defineCliConfig({
4 | api: {
5 | projectId: 'your-project-id',
6 | dataset: 'production'
7 | }
8 | })
9 |
--------------------------------------------------------------------------------
/studio/schemas/index.ts:
--------------------------------------------------------------------------------
1 | import blockContent from './blockContent'
2 | import category from './category'
3 | import post from './post'
4 | import author from './author'
5 |
6 | export const schemaTypes = [post, author, category, blockContent]
7 |
--------------------------------------------------------------------------------
/frontend/client.ts:
--------------------------------------------------------------------------------
1 | import sanityClient from '@sanity/client'
2 |
3 | export default sanityClient({
4 | projectId: 'your-project-id', // you can find this in sanity.json
5 | dataset: 'production', // or the name you chose in step 1
6 | useCdn: true // `false` if you want to ensure fresh data
7 | })
--------------------------------------------------------------------------------
/frontend/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import { Html, Head, Main, NextScript } from 'next/document'
2 |
3 | export default function Document() {
4 | return (
5 |
6 |
8 |
9 |
10 |
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/frontend/pages/api/hello.ts:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 | import type { NextApiRequest, NextApiResponse } from 'next'
3 |
4 | type Data = {
5 | name: string
6 | }
7 |
8 | export default function handler(
9 | req: NextApiRequest,
10 | res: NextApiResponse
11 | ) {
12 | res.status(200).json({ name: 'John Doe' })
13 | }
14 |
--------------------------------------------------------------------------------
/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/data-aspects": "d199e2c199b3e26cd28b68dc84d7fc01c9186bf5089580f2e2446994d36b3cb6"
6 | }
7 |
--------------------------------------------------------------------------------
/studio/schemas/category.ts:
--------------------------------------------------------------------------------
1 | import {defineField, defineType} from 'sanity'
2 |
3 | export default defineType({
4 | name: 'category',
5 | title: 'Category',
6 | type: 'document',
7 | fields: [
8 | defineField({
9 | name: 'title',
10 | title: 'Title',
11 | type: 'string',
12 | }),
13 | defineField({
14 | name: 'description',
15 | title: 'Description',
16 | type: 'text',
17 | }),
18 | ],
19 | })
20 |
--------------------------------------------------------------------------------
/studio/sanity.config.ts:
--------------------------------------------------------------------------------
1 | import {defineConfig} from 'sanity'
2 | import {deskTool} from 'sanity/desk'
3 | import {visionTool} from '@sanity/vision'
4 | import {schemaTypes} from './schemas'
5 |
6 | export default defineConfig({
7 | name: 'default',
8 | title: 'sanity-tutorial-blog',
9 |
10 | projectId: 'your-project-id',
11 | dataset: 'your-dataset-name',
12 |
13 | plugins: [deskTool(), visionTool()],
14 |
15 | schema: {
16 | types: schemaTypes,
17 | },
18 | })
19 |
--------------------------------------------------------------------------------
/studio/.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 | # Compiled Sanity Studio
9 | /dist
10 |
11 | # Temporary Sanity runtime, generated by the CLI on every dev server start
12 | /.sanity
13 |
14 | # Logs
15 | /logs
16 | *.log
17 |
18 | # Coverage directory used by testing tools
19 | /coverage
20 |
21 | # Misc
22 | .DS_Store
23 | *.pem
24 |
25 | # Typescript
26 | *.tsbuildinfo
27 |
--------------------------------------------------------------------------------
/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 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/studio/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true
17 | },
18 | "include": ["**/*.ts", "**/*.tsx"],
19 | "exclude": ["node_modules"]
20 | }
21 |
--------------------------------------------------------------------------------
/frontend/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true,
17 | "baseUrl": ".",
18 | "paths": {
19 | "@/*": ["./*"]
20 | }
21 | },
22 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
23 | "exclude": ["node_modules"]
24 | }
25 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@next/font": "13.1.6",
13 | "@portabletext/react": "^2.0.1",
14 | "@sanity/client": "^4.0.1",
15 | "@sanity/image-url": "^1.0.2",
16 | "@types/node": "18.11.18",
17 | "@types/react": "18.0.27",
18 | "@types/react-dom": "18.0.10",
19 | "eslint": "8.33.0",
20 | "eslint-config-next": "13.1.6",
21 | "groq": "^3.2.6",
22 | "next": "13.1.6",
23 | "react": "18.2.0",
24 | "react-dom": "18.2.0",
25 | "typescript": "4.9.5"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/frontend/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link'
2 | import groq from 'groq'
3 | import client from '../client'
4 |
5 | const Index = ({posts}) => {
6 | return (
7 |
8 |
Welcome to a blog!
9 | {posts.length > 0 && posts.map(
10 | ({ _id, title = '', slug = '', publishedAt = '' }) =>
11 | slug && (
12 |
13 |
14 | {title}
15 | {' '}
16 | ({new Date(publishedAt).toDateString()})
17 |
18 | )
19 | )}
20 |
21 | )
22 | }
23 |
24 | export async function getStaticProps() {
25 | const posts = await client.fetch(groq`
26 | *[_type == "post" && publishedAt < now()] | order(publishedAt desc)
27 | `)
28 | return {
29 | props: {
30 | posts
31 | }
32 | }
33 | }
34 |
35 | export default Index
--------------------------------------------------------------------------------
/studio/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "remove-me-sanity-tutorial-blog",
3 | "private": true,
4 | "version": "1.0.0",
5 | "main": "package.json",
6 | "license": "UNLICENSED",
7 | "scripts": {
8 | "dev": "sanity dev",
9 | "start": "sanity start",
10 | "build": "sanity build",
11 | "deploy": "sanity deploy",
12 | "deploy-graphql": "sanity graphql deploy"
13 | },
14 | "keywords": [
15 | "sanity"
16 | ],
17 | "dependencies": {
18 | "@sanity/vision": "^3.0.0",
19 | "react": "^18.2.0",
20 | "react-dom": "^18.2.0",
21 | "react-is": "^18.2.0",
22 | "sanity": "^3.0.0",
23 | "styled-components": "^5.2.0"
24 | },
25 | "devDependencies": {
26 | "@sanity/eslint-config-studio": "^2.0.1",
27 | "eslint": "^8.6.0",
28 | "prettier": "^2.8.3",
29 | "typescript": "^4.0.0"
30 | },
31 | "prettier": {
32 | "semi": false,
33 | "printWidth": 100,
34 | "bracketSpacing": false,
35 | "singleQuote": true
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/studio/schemas/author.ts:
--------------------------------------------------------------------------------
1 | import {defineField, defineType} from 'sanity'
2 |
3 | export default defineType({
4 | name: 'author',
5 | title: 'Author',
6 | type: 'document',
7 | fields: [
8 | defineField({
9 | name: 'name',
10 | title: 'Name',
11 | type: 'string',
12 | }),
13 | defineField({
14 | name: 'slug',
15 | title: 'Slug',
16 | type: 'slug',
17 | options: {
18 | source: 'name',
19 | maxLength: 96,
20 | },
21 | }),
22 | defineField({
23 | name: 'image',
24 | title: 'Image',
25 | type: 'image',
26 | options: {
27 | hotspot: true,
28 | },
29 | }),
30 | defineField({
31 | name: 'bio',
32 | title: 'Bio',
33 | type: 'array',
34 | of: [
35 | {
36 | title: 'Block',
37 | type: 'block',
38 | styles: [{title: 'Normal', value: 'normal'}],
39 | lists: [],
40 | },
41 | ],
42 | }),
43 | ],
44 | preview: {
45 | select: {
46 | title: 'name',
47 | media: 'image',
48 | },
49 | },
50 | })
51 |
--------------------------------------------------------------------------------
/frontend/public/thirteen.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Blogging with Sanity and Next.js
2 |
3 | [Read the tutorial](https://www.sanity.io/blog/build-your-own-blog-with-sanity-and-next-js?utm_source=github&github_campaing=rbt)
4 |
5 | ## Get started
6 |
7 | ```sh
8 | # Install the Sanity command line interface
9 | ~/
10 | > npm i -g @sanity/cli
11 |
12 | # Initiate your own project in the studio folder
13 | ~/this-blog/studio
14 | > sanity init
15 |
16 | # Add a CORS-origin rule to allow the frontend to request data
17 | ~/this-blog/studio
18 | > sanity cors add http://localhost:3000 --no-credentials
19 |
20 | # Insert the projectId and dataset name from Sanity in client.js
21 | ~/this-blog/web
22 | > nano client.js
23 |
24 | # Install frontend dependencies
25 | ~/this-blog/web
26 | > npm install
27 |
28 | # Run Next.js in development mode
29 | ~/this-blog/web
30 | > npm run dev
31 | ```
32 |
33 | ## Deploy on vercel
34 |
35 | ```sh
36 | ~/this-blog/web
37 | > npm i -g vercel
38 | > vercel login
39 | > vercel
40 | ```
41 |
42 | ## Deploy as a static site on Netlify
43 |
44 | [Read the tutorial](https://www.sanity.io/blog/tutorial-host-your-sanity-based-next-js-project-on-netlify?utm_source=github&utm_campaign=netlifyexport)
45 |
46 | ```sh
47 | ~/this-blog/web
48 | npm run export
49 | # exports your site as static files in /out
50 | ```
51 |
--------------------------------------------------------------------------------
/frontend/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/studio/schemas/post.ts:
--------------------------------------------------------------------------------
1 | import {defineField, defineType} from 'sanity'
2 |
3 | export default defineType({
4 | name: 'post',
5 | title: 'Post',
6 | type: 'document',
7 | fields: [
8 | defineField({
9 | name: 'title',
10 | title: 'Title',
11 | type: 'string',
12 | }),
13 | defineField({
14 | name: 'slug',
15 | title: 'Slug',
16 | type: 'slug',
17 | options: {
18 | source: 'title',
19 | maxLength: 96,
20 | },
21 | }),
22 | defineField({
23 | name: 'author',
24 | title: 'Author',
25 | type: 'reference',
26 | to: {type: 'author'},
27 | }),
28 | defineField({
29 | name: 'mainImage',
30 | title: 'Main image',
31 | type: 'image',
32 | options: {
33 | hotspot: true,
34 | },
35 | }),
36 | defineField({
37 | name: 'categories',
38 | title: 'Categories',
39 | type: 'array',
40 | of: [{type: 'reference', to: {type: 'category'}}],
41 | }),
42 | defineField({
43 | name: 'publishedAt',
44 | title: 'Published at',
45 | type: 'datetime',
46 | }),
47 | defineField({
48 | name: 'body',
49 | title: 'Body',
50 | type: 'blockContent',
51 | }),
52 | ],
53 |
54 | preview: {
55 | select: {
56 | title: 'title',
57 | author: 'author.name',
58 | media: 'mainImage',
59 | },
60 | prepare(selection) {
61 | const {author} = selection
62 | return {...selection, subtitle: author && `by ${author}`}
63 | },
64 | },
65 | })
66 |
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | ```
14 |
15 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
16 |
17 | You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
18 |
19 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
20 |
21 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
22 |
23 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
24 |
25 | ## Learn More
26 |
27 | To learn more about Next.js, take a look at the following resources:
28 |
29 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
30 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
31 |
32 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
33 |
34 | ## Deploy on Vercel
35 |
36 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
37 |
38 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
39 |
--------------------------------------------------------------------------------
/studio/schemas/blockContent.ts:
--------------------------------------------------------------------------------
1 | import {defineType, defineArrayMember} from 'sanity'
2 |
3 | /**
4 | * This is the schema definition for the rich text fields used for
5 | * for this blog studio. When you import it in schemas.js it can be
6 | * reused in other parts of the studio with:
7 | * {
8 | * name: 'someName',
9 | * title: 'Some title',
10 | * type: 'blockContent'
11 | * }
12 | */
13 | export default defineType({
14 | title: 'Block Content',
15 | name: 'blockContent',
16 | type: 'array',
17 | of: [
18 | defineArrayMember({
19 | title: 'Block',
20 | type: 'block',
21 | // Styles let you set what your user can mark up blocks with. These
22 | // correspond with HTML tags, but you can set any title or value
23 | // you want and decide how you want to deal with it where you want to
24 | // use your content.
25 | styles: [
26 | {title: 'Normal', value: 'normal'},
27 | {title: 'H1', value: 'h1'},
28 | {title: 'H2', value: 'h2'},
29 | {title: 'H3', value: 'h3'},
30 | {title: 'H4', value: 'h4'},
31 | {title: 'Quote', value: 'blockquote'},
32 | ],
33 | lists: [{title: 'Bullet', value: 'bullet'}],
34 | // Marks let you mark up inline text in the block editor.
35 | marks: {
36 | // Decorators usually describe a single property – e.g. a typographic
37 | // preference or highlighting by editors.
38 | decorators: [
39 | {title: 'Strong', value: 'strong'},
40 | {title: 'Emphasis', value: 'em'},
41 | ],
42 | // Annotations can be any object structure – e.g. a link or a footnote.
43 | annotations: [
44 | {
45 | title: 'URL',
46 | name: 'link',
47 | type: 'object',
48 | fields: [
49 | {
50 | title: 'URL',
51 | name: 'href',
52 | type: 'url',
53 | },
54 | ],
55 | },
56 | ],
57 | },
58 | }),
59 | // You can add additional types here. Note that you can't use
60 | // primitive types such as 'string' and 'number' in the same array
61 | // as a block type.
62 | defineArrayMember({
63 | type: 'image',
64 | options: {hotspot: true},
65 | }),
66 | ],
67 | })
68 |
--------------------------------------------------------------------------------
/frontend/pages/post/[slug].tsx:
--------------------------------------------------------------------------------
1 | // [slug].tsx
2 |
3 | import groq from 'groq'
4 | import imageUrlBuilder from '@sanity/image-url'
5 | import {PortableText} from '@portabletext/react'
6 | import client from '../../client'
7 |
8 | function urlFor (source) {
9 | return imageUrlBuilder(client).image(source)
10 | }
11 |
12 | const ptComponents = {
13 | types: {
14 | image: ({ value }) => {
15 | if (!value?.asset?._ref) {
16 | return null
17 | }
18 | return (
19 |
24 | )
25 | }
26 | }
27 | }
28 |
29 | const Post = ({post}) => {
30 | const {
31 | title = 'Missing title',
32 | name = 'Missing name',
33 | categories,
34 | authorImage,
35 | body = []
36 | } = post
37 | return (
38 |
39 | {title}
40 | By {name}
41 | {categories && (
42 |
43 | Posted in
44 | {categories.map(category => - {category}
)}
45 |
46 | )}
47 | {authorImage && (
48 |
49 |
)
55 |
56 | )}
57 |
61 |
62 | )
63 | }
64 |
65 | const query = groq`*[_type == "post" && slug.current == $slug][0]{
66 | title,
67 | "name": author->name,
68 | "categories": categories[]->title,
69 | "authorImage": author->image,
70 | body
71 | }`
72 | export async function getStaticPaths() {
73 | const paths = await client.fetch(
74 | groq`*[_type == "post" && defined(slug.current)][].slug.current`
75 | )
76 |
77 | return {
78 | paths: paths.map((slug) => ({params: {slug}})),
79 | fallback: true,
80 | }
81 | }
82 |
83 | export async function getStaticProps(context) {
84 | // It's important to default the slug so that it doesn't return "undefined"
85 | const { slug = "" } = context.params
86 | const post = await client.fetch(query, { slug })
87 | return {
88 | props: {
89 | post
90 | }
91 | }
92 | }
93 | export default Post
--------------------------------------------------------------------------------