NEXT.JS ＋ Sanity.io　画像一覧アプリを作ってみた！ (最新2022年8月版)

![Desktop - 1next.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654025/54b3b2b8-4d05-0cb5-0703-0aade13b3662.png)


# 今回は、NEXT.JSとSanity.ioを使って簡単な画像一覧アプリを作成します。完成は下記の通りです:blush:

キャラクター画像は、こちらのサイトから使っています。

https://www.irasutoya.com/2021/01/onepiece.html


## 初期画面
### それぞれのカードをクリックすると、キャラクターの説明リンクに飛びます!

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654025/20021665-134b-c2f3-21b5-025579a37411.png)

### zoroをクリックすると、”localhost:3000/character/zoro"に遷移します。Homeボタンを押すと”localhost:3000/”に戻ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654025/09ad4b13-dcd2-02d7-4f57-7b7696f38d04.png)

### sanity.ioに新しい画像データを保存すると、ブラウザに表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654025/6929711a-9f6a-dab8-1258-bed0e0bcf56a.png)
:arrow_down:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654025/109b01a7-ea0e-d3ef-9329-34a55b30c708.png)

# 下記の動画を参考に作成しました。まずこちらの動画を参考にされてください。:arrow_down:

# 参考動画:iphone:
https://youtu.be/mx1dbMzd3tU


https://youtu.be/1WmNXEVia8I

# NEXT.JSのSet up
```terminal
npx create-next-app@latest frontend
```

https://nextjs.org/docs/getting-started

# Sanity.ioのSet up
```terminal
sanity init

>Create new project: "yes"

>Your project name: "backend"

>Use the default dtaset configuration? "yes"

>Project output path: "Enter"

>Select project template: "Clean project with no predefined schemas"

```

https://www.sanity.io/docs/old-getting-started


# モジュールをインストール
## nextjsアプリケーションに下記のモジュールをインストールします。
```terminal
npm install next-sanity @sanity/image-url
```

https://github.com/sanity-io/next-sanity


### ここまでで必要な準備は整ったので、次はコードを書いていきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654025/540814d3-b148-d643-a0d1-a895c202da4b.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654025/12254b50-aeaf-1bf2-66b1-92cd61b51992.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654025/52427e9a-531f-0aac-4432-7dda88ff22dc.png)


# index.js
```javascript:index.js
import { sanityClient, urlFor } from '../sanity'
import Link from 'next/link'

const Home = ({ characters }) => {

  return (
    <div>
      {characters && (
        <div>
          <h1>Characters</h1>
          <div
            style={{ display: 'grid', gap: '1px', gridTemplateColumns: '2fr 2fr 2fr' }}
          >

            {characters.map((character) => (
              <Link
                className='card'
                key={character._id}
                href={`character/${character.slug.current}`}>
                <div className='card'>
                  <img
                    src={urlFor(character.image)}
                    alt={character.name}
                    style={{ height: '70%', }}
                  />
                  <h3>{character.name}</h3>
                </div>
              </Link>
            ))}
          </div>
        </div>
      )}
    </div>
  )
}


export const getServerSideProps = async () => {
  const query = '*[_type == "character"]'
  const characters = await sanityClient.fetch(query);

  if (!characters.length) {
    return {
      props: {
        characters: []
      },
    }
  } else {
    return {
      props: {
        characters: characters,
      }
    }
  }
}

export default Home;
```

# [slug].js
```javascript:[slug].js
import Link from "next/link";
import { sanityClient, urlFor } from "../../sanity";

const Character = ({ name, image }) => {
  return (
    <div>
      <h1><b>{name}</b></h1>
      <img
        style={{ height: '300px' }}
        src={urlFor(image)} alt={name}
      />
      <br />
      <Link href="/">
        <button>Home</button>
      </Link>
    </div>
  )
}

export const getServerSideProps = async (pageContext) => {
  const pageSlug = pageContext.query.slug;
  const query = `*[ _type == "character" && slug.current == $pageSlug][0]{
    name,
    image,
  }`

  const character = await sanityClient.fetch(query, {
    pageSlug
  })
  if (!character) {
    return {
      props: null,
      notFound: true,
    }
  } else {
    return {
      props: {
        name: character.name,
        image: character.image,
      }
    }
  }
}

export default Character
```

# sanity.js
このファイルが設定ファイルになります。
```javascript:sanity.js
import { createClient } from 'next-sanity'
import createImageUrlBuilder from '@sanity/image-url'

const config = {
  dataset: process.env.NEXT_PUBLIC_SANITY_DATASET || 'production',
  projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
  apiVersion: '2021-10-21',
  useCdn: process.env.NODE_ENV === 'production',
}

export const sanityClient = createClient(config)

export const urlFor = (source) => createImageUrlBuilder(config).image(source)

```

# globals.css
cardのスタイルだけ追加しました。
```css:globals.css
html,
body {
  padding: 0;
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
    Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}

a {
  color: inherit;
  text-decoration: none;
}

* {
  box-sizing: border-box;
}

@media (prefers-color-scheme: dark) {
  html {
    color-scheme: dark;
  }
  body {
    color: white;
    background: black;
  }
}

.card{
  /* preview */
width: 200px;
height: 260px;
background-color: #fff;
margin: 5px auto;
padding: 5px;

/* border */
border: 2px solid #ccc;

/* border-radius */
border-radius: 15px;

/* box-shadow */
box-shadow: 0px 8px 16px -2px rgba(10,10,10,0.1), 0px 0px 0px 1px rgba(10,10,10,0.02);
}

```

# .env
```eml
NEXT_PUBLIC_SANITY_DATASET="datasetを記述"
NEXT_PUBLIC_SANITY_PROJECT_ID="projectIdを記述"
```

# schemas/character.js
```javascript:character.js
export default {
  name: 'character',
  title: 'Character',
  type: 'document',
  fields: [
    {
      name: 'name',
      title: 'Name',
      type: 'string',
    },
    {
      name: 'image',
      title: 'Image',
      type: 'image',
      options: {
        hotspot: true,
      }
    },
    {
      name: 'slug',
      title: 'Slug',
      type: 'slug',
      options: {
        source: 'name',
        maxLength: 100,
      }
    },
  ]
}
```


# 参考動画
https://youtu.be/mx1dbMzd3tU


https://youtu.be/1WmNXEVia8I

# さいごに
今度は、Airbnbクローンを自作してみたいですね！
参考動画では、Google maps Platformを使っています。こちらは、無料制限がありますのでご注意ください。

https://developers.google.com/maps?hl=ja