├── .gitignore ├── public ├── favicon.ico ├── nextjs-logo.svg └── sanity-logo.svg ├── utils └── imageUrlFor.js ├── lib └── sanity.js ├── package.json ├── styles └── list.js ├── .github └── workflows │ ├── node.js.yml │ └── tnode.js.yml ├── README.md ├── pages ├── people.js ├── index.js ├── person │ └── [id].js └── movie │ └── [id].js └── components ├── GithubCorner.js └── Layout.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanity-io/example-frontend-next-js/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /utils/imageUrlFor.js: -------------------------------------------------------------------------------- 1 | import sanity from "../lib/sanity"; 2 | import imageUrlBuilder from "@sanity/image-url"; 3 | 4 | const imageBuilder = imageUrlBuilder(sanity); 5 | 6 | const imageUrlFor = source => imageBuilder.image(source); 7 | 8 | export default imageUrlFor; 9 | -------------------------------------------------------------------------------- /lib/sanity.js: -------------------------------------------------------------------------------- 1 | import sanityClient from "@sanity/client"; 2 | 3 | export default sanityClient({ 4 | // Find your project ID and dataset in `sanity.json` in your studio project 5 | projectId: "zp7mbokg", 6 | dataset: "production", 7 | useCdn: true 8 | // useCdn == true gives fast, cheap responses using a globally distributed cache. 9 | // Set this to false if your application require the freshest possible 10 | // data always (potentially slightly slower and a bit more expensive). 11 | }); 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-frontend-next-js", 3 | "description": "Sanity + Next.js frontend example", 4 | "private": true, 5 | "dependencies": { 6 | "@sanity/block-content-to-react": "^1.3.12", 7 | "@sanity/client": "^0.132.10", 8 | "@sanity/image-url": "^0.132.8", 9 | "next": "^9.3.1", 10 | "react": "^16.13.0", 11 | "react-dom": "^16.13.0" 12 | }, 13 | "scripts": { 14 | "dev": "next", 15 | "build": "next build", 16 | "start": "next start" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /styles/list.js: -------------------------------------------------------------------------------- 1 | /* styles.js */ 2 | import css from "styled-jsx/css"; 3 | 4 | export default css` 5 | .list { 6 | display: grid; 7 | margin: 0; 8 | padding: 0; 9 | grid-gap: 1rem; 10 | grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); 11 | } 12 | 13 | .list > li { 14 | display: block; 15 | margin: 0; 16 | padding: 0; 17 | display: flex; 18 | align-items: stretch; 19 | } 20 | 21 | .list a { 22 | text-decoration: none; 23 | display: block; 24 | flex-grow: 1; 25 | color: #333; 26 | } 27 | 28 | .list h3 { 29 | margin: 0; 30 | padding: 0; 31 | line-height: 1em; 32 | } 33 | 34 | .list img { 35 | display: block; 36 | height: auto; 37 | width: 100%; 38 | margin-right: 0.5rem; 39 | } 40 | 41 | .list .noImage { 42 | border: 1px solid red; 43 | } 44 | 45 | .link { 46 | cursor: pointer; 47 | } 48 | `; 49 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ "master" ] 9 | pull_request: 10 | branches: [ "master" ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [12.x, 14.x, 16.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v3 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v3 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | cache: 'npm' 29 | - run: npm ci 30 | - run: npm run build --if-present 31 | - run: npm test --if-present 32 | -------------------------------------------------------------------------------- /.github/workflows/tnode.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ "master" ] 9 | pull_request: 10 | branches: [ "master" ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [12.x, 14.x, 16.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v3 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v3 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | cache: 'npm' 29 | - run: npm ci 30 | - run: npm run build --if-present 31 | - run: npm test --if-present 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sanity + Next.js frontend example 2 | 3 | DEMO 👉 https://example-frontend-next-js.sanity-io.now.sh 4 | 5 | This is an example [Sanity](https://www.sanity.io/) powered frontend for the movie dataset using [Next.js](https://github.com/zeit/next.js/). 6 | 7 | ## Prerequisites 8 | 9 | You will need [Node.js](https://nodejs.org) version 8.0 or greater installed on your system. 10 | 11 | ## Setup 12 | 13 | Get the code by either cloning this repository using git 14 | 15 | ``` 16 | git clone https://github.com/sanity-io/example-frontend-next-js.git 17 | ``` 18 | 19 | ... or [downloading source code](https://github.com/sanity-io/example-frontend-next-js/archive/master.zip) code as a zip archive. 20 | 21 | Once downloaded, open the terminal in the project directory, and install dependencies with: 22 | 23 | ``` 24 | npm install 25 | ``` 26 | 27 | If you're running your own Sanity project with the example movie dataset, go to `lib/sanity.js` and change the following lines: 28 | 29 | ``` 30 | projectId: 'YOUR_PROJECT_ID', 31 | dataset: 'NAME_OF_YOUR_DATASET', 32 | ``` 33 | 34 | You can locate the ID of your project in the header of the [management page for your project](https://manage.sanity.io/). 35 | 36 | You also need to enable `localhost:3000` in your CORS Origins settings! Either through the [management page](https://manage.sanity.io/) under `settings` or by running the below in the project folder you set up with `sanity init`: 37 | 38 | ``` 39 | sanity cors add http://localhost:3000 40 | ``` 41 | 42 | Then start the example app with: 43 | 44 | ``` 45 | npm run dev 46 | ``` 47 | 48 | The app should now be up and running at http://localhost:3000 🚀 49 | -------------------------------------------------------------------------------- /pages/people.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Link from "next/link"; 3 | import Layout from "../components/Layout"; 4 | import sanity from "../lib/sanity"; 5 | import listStyles from "../styles/list"; 6 | import imageUrlFor from "../utils/imageUrlFor"; 7 | 8 | const query = `*[_type == "person"] { 9 | _id, 10 | name, 11 | image, 12 | "imageAspect": image.asset->.metadata.dimensions.aspectRatio, 13 | }[0...50] 14 | `; 15 | 16 | const People = ({ people }) => { 17 | return ( 18 | 19 |
20 | 38 |
39 | 49 | 50 |
51 | ); 52 | }; 53 | 54 | export const getStaticProps = async () => { 55 | const people = await sanity.fetch(query); 56 | return { 57 | props: { people } // will be passed to the page component as props 58 | }; 59 | } 60 | 61 | export default People; 62 | -------------------------------------------------------------------------------- /public/nextjs-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 10 | 16 | 19 | 22 | 25 | 29 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /components/GithubCorner.js: -------------------------------------------------------------------------------- 1 | // Thanks @twholman! 2 | 3 | export default function GitHubCorner() { 4 | return ( 5 | 11 | 24 | 52 | 53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /components/Layout.js: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import Link from "next/link"; 3 | import GithubCorner from "./GithubCorner"; 4 | 5 | export default function Layout(props) { 6 | return ( 7 |
8 | 9 | 10 | 11 | Sanity + Next.js = 💖 12 | 13 | 21 | 22 |
{props.children}
23 | 36 | 69 | 79 |
80 | ); 81 | } 82 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Link from "next/link"; 3 | import Layout from "../components/Layout"; 4 | import sanity from "../lib/sanity"; 5 | import listStyles from "../styles/list"; 6 | import imageUrlFor from "../utils/imageUrlFor"; 7 | 8 | const query = `*[_type == "movie"] { 9 | _id, 10 | title, 11 | releaseDate, 12 | poster, 13 | "posterAspect": poster.asset->.metadata.dimensions.aspectRatio, 14 | "director": crewMembers[job == "Director"][0].person->name 15 | }[0...50] 16 | `; 17 | 18 | const Movies = ({ movies }) => { 19 | return ( 20 | 21 |
22 | 50 |
51 | 60 | 61 |
62 | ); 63 | }; 64 | 65 | export const getStaticProps = async () => { 66 | const movies = await sanity.fetch(query); 67 | return { 68 | props: { movies } // will be passed to the page component as props 69 | }; 70 | }; 71 | 72 | export default Movies; 73 | -------------------------------------------------------------------------------- /public/sanity-logo.svg: -------------------------------------------------------------------------------- 1 | Artboard 1 -------------------------------------------------------------------------------- /pages/person/[id].js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Link from "next/link"; 3 | import Layout from "../../components/Layout"; 4 | import sanity from "../../lib/sanity"; 5 | import listStyles from "../../styles/list"; 6 | import imageUrlFor from "../../utils/imageUrlFor"; 7 | 8 | const personsQuery = `*[_type == "person"] { _id }`; 9 | 10 | const singlePersonQuery = `*[_type == "person" && _id == $id] { 11 | _id, 12 | name, 13 | image, 14 | "actedIn": *[_type == "movie" && references(^._id)] { 15 | _id, 16 | title, 17 | releaseDate, 18 | poster 19 | } 20 | }[0] 21 | `; 22 | 23 | const Person = ({ person }) => { 24 | return ( 25 | 26 |
27 |
28 | {person.image && } 29 |
30 |
31 |

{person.name}

32 |

Related movies

33 | 53 |
54 |
55 | 99 | 100 |
101 | ); 102 | }; 103 | 104 | export const getStaticPaths = async () => { 105 | // Get the paths we want to pre-render based on persons 106 | const persons = await sanity.fetch(personsQuery); 107 | const paths = persons.map(person => ({ 108 | params: { id: person._id } 109 | })); 110 | 111 | // We'll pre-render only these paths at build time. 112 | // { fallback: false } means other routes should 404. 113 | return { paths, fallback: false }; 114 | }; 115 | 116 | // This function gets called at build time on server-side. 117 | export const getStaticProps = async ({ params }) => { 118 | const person = await sanity.fetch(singlePersonQuery, { id: params.id }); 119 | return { props: { person } }; 120 | }; 121 | 122 | export default Person; 123 | -------------------------------------------------------------------------------- /pages/movie/[id].js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Link from "next/link"; 3 | import BlockContent from "@sanity/block-content-to-react"; 4 | import Layout from "../../components/Layout"; 5 | import sanity from "../../lib/sanity"; 6 | import listStyles from "../../styles/list"; 7 | import imageUrlFor from "../../utils/imageUrlFor"; 8 | 9 | const moviesQuery = `*[_type == "movie"] { _id }`; 10 | 11 | const singleMovieQuery = `*[_type == "movie" && _id == $id] { 12 | _id, 13 | title, 14 | overview, 15 | releaseDate, 16 | poster, 17 | "cast": castMembers[] { 18 | _key, 19 | characterName, 20 | "person": person-> { 21 | _id, 22 | name, 23 | image 24 | } 25 | } 26 | }[0] 27 | `; 28 | 29 | const serializers = { 30 | types: { 31 | summaries: props => { 32 | const { node } = props; 33 | if (!node) { 34 | return false; 35 | } 36 | const { summaries } = node; 37 | if (!summaries || summaries.length === 0) { 38 | return false; 39 | } 40 | return ( 41 |
42 |

{node.caption}

43 | 56 | 86 |
87 | ); 88 | } 89 | } 90 | }; 91 | 92 | const Movie = ({ movie }) => { 93 | const { 94 | poster: { crop = { left: 0, top: 0 }, hotspot = { x: 0.5, y: 0.5 } } 95 | } = movie; 96 | return ( 97 | 98 |
99 |
107 |
108 |

{movie.title}

109 |
110 |
111 | 112 |
113 |
114 | {`Movie 121 |
122 |
123 |
124 | 130 |
131 |

Cast

132 | 155 |
156 |
157 |
158 | 366 | 367 |
368 | ); 369 | }; 370 | 371 | export const getStaticPaths = async () => { 372 | // Get the paths we want to pre-render based on persons 373 | const movies = await sanity.fetch(moviesQuery); 374 | const paths = movies.map(movie => ({ 375 | params: { id: movie._id } 376 | })); 377 | 378 | // We'll pre-render only these paths at build time. 379 | // { fallback: false } means other routes should 404. 380 | return { paths, fallback: false }; 381 | }; 382 | 383 | // This function gets called at build time on server-side. 384 | export const getStaticProps = async ({ params }) => { 385 | const movie = await sanity.fetch(singleMovieQuery, { id: params.id }); 386 | return { props: { movie } }; 387 | }; 388 | 389 | export default Movie; 390 | --------------------------------------------------------------------------------