본문 바로가기

💬/ㅍㅅㅌㅋㅍㅅ 챌린지

47일차

🏕️

47일차

 

Part 15. Next.js로 블로그 만들기

Ch 1. Sanity로 Blog 모델 만들기

 


 

 

Ch 1. Sanity로 Blog 모델 만들기

 

 

https://slides.com/woongjae/nextjs2021

 

Next.js 2021

넥스트제이에스 2021

slides.com

 

https://www.sanity.io/

 

The Unified Content Platform – Sanity.io

Sanity is the unified content platform that lets your team work together in real-time to build engaging digital experiences across channels.

www.sanity.io

 

Content is Data
Sanity.io is the unified content platform that powers better digital experiences

 

 

 

 🔗 Sanity Project 만들고 Deploy 하기 

 

$# nextjs2021 (상위 폴더)
$ npm install -g @sanity/cli
$ sanity login

$# my-blog-contents (하위 폴더)
$ sanity init
$ sanity start # = $ npm start # local run
$ sanity deploy # sanity run

 

 

 🔗 Schema 만들기 (1) - author 

 

// ./schemas/author.js

export default {
  name: "author",
  title: "Author",
  type: "document",
  fields: [
    {
      name: "name",
      title: "Name",
      type: "string",
      validation: (Rule) => Rule.required(),
    },
    { name: "role", title: "Role", type: "string" },
    {
      name: "image",
      title: "Image",
      type: "image",
      options: {
        hotspot: true,
      },
      validation: (Rule) => Rule.required(),
    },
  ],
  preview: {
    select: {
      title: "name",
      media: "image",
    },
  },
};

 

 

 🔗 Schema 만들기 (2) - post 

 

// ./shemas/post.js

export default {
  name: "post",
  title: "Post",
  type: "document",
  fields: [
    {
      name: "title",
      title: "Title",
      type: "string",
      validation: (Rule) => Rule.required(),
    },
    {
      name: "slug",
      title: "Slug",
      type: "slug",
      options: {
        source: "title",
        maxLength: 96,
      },
      validation: (Rule) => Rule.required(),
    },
    {
      name: "subtitle",
      title: "Sub Title",
      type: "string",
      validation: (Rule) => Rule.required(),
    },
    {
      name: "author",
      title: "Author",
      type: "reference",
      to: { type: "author" },
      validation: (Rule) => Rule.required(),
    },
    {
      name: "content",
      title: "Content",
      type: "blockContent",
      validation: (Rule) => Rule.required(),
    },
    {
      name: "createdAt",
      title: "Created at",
      type: "datetime",
      validation: (Rule) => Rule.required(),
    },
    {
      name: "thumbnail",
      title: "Thumbnail",
      type: "image",
      options: {
        hotspot: true,
      },
      fields: [
        {
          name: "alt",
          type: "string",
          title: "alt",
          options: { isHighlighted: true },
          validation: (Rule) => Rule.required(),
        },
      ],
      validation: (Rule) => Rule.required(),
    },
    {
      name: "tag",
      title: "Tag",
      type: "reference",
      to: { type: "tag" },
      validation: (Rule) => Rule.required(),
    },
  ],

  preview: {
    select: {
      title: "title",
      author: "author.name",
      media: "thumbnail",
    },
    prepare(selection) {
      const { author } = selection;
      return Object.assign({}, selection, {
        subtitle: author && `by ${author}`,
      });
    },
  },
};

 

  • tag
// ./schemas/tag.js

export default {
  name: "tag",
  title: "Tag",
  type: "document",
  fields: [
    {
      name: "title",
      title: "Title",
      type: "string",
      validation: (Rule) => Rule.required(),
    },
    {
      name: "slug",
      title: "Slug",
      type: "slug",
      options: {
        source: "title",
        maxLength: 96,
      },
      validation: (Rule) => Rule.required(),
    },
  ],
  preview: {
    select: {
      title: "title",
      subtitle: "slug.current",
    },
  },
};
// ./shemas/schemas.js

...

// Then we give our schema to the builder and provide the result to Sanity
export default createSchema({
  // We name our schema
  name: "default",
  // Then proceed to concatenate our document type
  // to the ones provided by any plugins that are installed
  types: schemaTypes.concat([
    // The following are document types which will appear
    // in the studio.
    post,
    author,
    tag, // ⭐
    // When added to this list, object types can be used as
    // { type: 'typename' } in other document schemas
    blockContent,
  ]),
});

 

 

 🔗 Schema 만들기 (3) - home 

 

// ./schemas/home.js

export default {
  name: "home",
  title: "Home",
  type: "document",
  fields: [
    {
      name: "title",
      title: "Title",
      type: "string",
      validation: (Rule) => Rule.required(),
    },
    {
      name: "mainPost",
      title: "Main Post",
      type: "reference",
      to: { type: "post" },
      validation: (Rule) => Rule.required(),
    },
  ],
  preview: {
    select: {
      title: "title",
      subtitle: "mainPost.title",
    },
  },
};
// ./schemas/shemas.js

...

// Then we give our schema to the builder and provide the result to Sanity
export default createSchema({
  // We name our schema
  name: "default",
  // Then proceed to concatenate our document type
  // to the ones provided by any plugins that are installed
  types: schemaTypes.concat([
    // The following are document types which will appear
    // in the studio.
    post,
    author,
    tag,
    home, // ⭐
    // When added to this list, object types can be used as
    // { type: 'typename' } in other document schemas
    blockContent,
  ]),
});

 

 

 🔗 Schema 만들기 (4) - blockContent 

 

https://www.sanity.io/plugins/sanity-plugin-url-metadata-input

 

URL Metadata Input | Sanity.io plugin

URL input for Sanity that retrieves metadata (title, description) along with open graph information.

www.sanity.io

$ sanity install url-metadata-input

 

// ./schemas/blockContent.js

...
export default {
  title: "Block Content",
  name: "blockContent",
  type: "array",
  of: [
    {
      ...
      styles: [
        { title: "Normal", value: "normal" },
        { title: "H1", value: "h1" },
        { title: "H2", value: "h2" },
        { title: "H3", value: "h3" },
        { title: "H4", value: "h4" },
        { title: "H5", value: "h5" },
        { title: "Quote", value: "blockquote" },
      ],
      lists: [
        { title: "Bullet", value: "bullet" },
        { title: "Numbered", value: "number" },
      ],
      // Marks let you mark up inline text in the block editor.
      marks: {
        // Decorators usually describe a single property – e.g. a typographic
        // preference or highlighting by editors.
        decorators: [
          { title: "Strong", value: "strong" },
          { title: "Emphasis", value: "em" },
        ],
        // Annotations can be any object structure – e.g. a link or a footnote.
        annotations: [
          {
            title: "URL",
            name: "link",
            type: "object",
            fields: [
              {
                title: "URL",
                name: "href",
                type: "url",
              },
            ],
          },
        ],
      },
    },
    // You can add additional types here. Note that you can't use
    // primitive types such as 'string' and 'number' in the same array
    // as a block type.
    {
      type: "image",
      options: { hotspot: true },
      fields: [
        {
          name: "caption",
          title: "Caption",
          type: "string",
          options: { isHighlighted: true },
        },
        {
          name: "alt",
          title: "alt",
          type: "string",
          options: { isHighlighted: true },
          validation: (Rule) => Rule.required(),
        },
      ],
    },
    {
      type: "video",
    },
    {
      type: "code",
    },
    { type: "link" },
    { type: "imageGallery" },
  ],
};
// ./schemas/video.js

export default {
  name: "video",
  title: "Video",
  type: "object",
  fields: [
    { name: "caption", title: "Caption", type: "string" },
    { name: "metadata", title: "MetaData", type: "urlWithMetadata" },
  ],
  preview: {
    select: {
      title: "caption",
      subtitle: "metadata.url",
    },
  },
};
// ./schemas/code.js

export default {
  name: "code",
  title: "Code",
  type: "object",
  fields: [
    {
      name: "title",
      title: "Title",
      type: "string",
      validation: (Rule) => Rule.required(),
    },
    {
      name: "language",
      title: "Language",
      type: "string",
      validation: (Rule) => Rule.required(),
    },
    {
      name: "code",
      title: "Code",
      type: "string",
      validation: (Rule) => Rule.required(),
    },
  ],
};
// ./schemas/link.js

export default {
  name: "link",
  title: "Link",
  type: "object",
  fields: [{ name: "metadata", title: "Metadata", type: "urlWithMetadata" }],
  preview: {
    select: {
      title: "metadata.openGraph.title",
      subtitle: "metadata.openGraph.url",
    },
  },
};
// ./schemas/imageGallery.js

export default {
  name: "imageGallery",
  title: "Image Gallery",
  type: "object",
  fields: [
    {
      name: "caption",
      title: "Caption",
      type: "string",
      validation: (Rule) => Rule.required(),
    },
    {
      name: "images",
      title: "Images",
      type: "array",
      options: { layout: "gird" },
      of: [
        {
          name: "image",
          title: "Image",
          type: "image",
          hotspot: true,
          fields: [
            {
              name: "alt",
              title: "alt",
              type: "string",
              options: { isHighlighted: true },
              validation: (Rule) => Rule.required(),
            },
          ],
          validation: (Rule) => Rule.required(),
        },
      ],
      validation: (Rule) => Rule.required().max(4),
    },
  ],
};
// ./schemas/schema.js

...

// Then we give our schema to the builder and provide the result to Sanity
export default createSchema({
  ...
  types: schemaTypes.concat([
    // The following are document types which will appear
    // in the studio.
    post,
    author,
    tag,
    home,
    // When added to this list, object types can be used as
    // { type: 'typename' } in other document schemas
    blockContent,
    video,
    code,
    link,
    imageGallery,
  ]),
});

 

 

 🔗 Stdio 의 input 창 커스터마이징

 

https://www.sanity.io/docs/custom-input-widgets

 

Custom input components

This article will explore the pieces and steps necessary to create a custom input component from scratch.

www.sanity.io

 

$ npm i yarn -g

$ yarn add react-ace

 

// ./components/CodeInput.jsx

import React, { useCallback } from "react";
import { FormField } from "@sanity/base/components";
import AceEditor from "react-ace";
import PatchEvent, { set, unset } from "@sanity/form-builder/PatchEvent";

import "ace-builds/src-noconflict/theme-github";
import "ace-builds/src-noconflict/mode-javascript";

const CodeInput = React.forwardRef((props, ref) => {
  const {
    type, // Schema information
    value, // Current field value
    markers, // Markers including validation rules
    presence, // Presence information for collaborative avatars
    compareValue, // Value to check for "edited" functionality
    onChange,
  } = props;

  const codeChange = useCallback(
    (code) => {
      onChange(PatchEvent.from(code ? set(code) : unset()));
    },
    [onChange]
  );

  return (
    <FormField
      description={type.description} // Creates description from schema
      title={type.title} // Creates label from schema title
      __unstable_markers={markers} // Handles all markers including validation
      __unstable_presence={presence} // Handles presence avatars
      compareValue={compareValue} // Handles "edited" status
    >
      <AceEditor
        mode="javascript"
        name="ace-editor-code"
        width="100%"
        theme="github"
        style={{
          boxShadow: "0 0 0 1px #cad1dc",
          lineHeight: 1.6,
        }}
        value={value}
        tabSize={2}
        setOptions={{ useWorker: false }}
        ref={ref}
        onChange={codeChange}
      />
    </FormField>
  );
});

export default CodeInput;
// ./schemas/code.js

import CodeInput from "../components/CodeInput";

export default {
  name: "code",
  title: "Code",
  type: "object",
  fields: [
    ...
    {
      name: "code",
      title: "Code",
      type: "string",
      validation: (Rule) => Rule.required(),
      inputComponent: CodeInput, // ⭐
    },
  ],
};

 

 

 🔗 query 사용하기 ⭐ 

 

*[_type == 'home'][0] {
  'mainPostUrl': mainPost -> slug.current
}

 

*[_type=='post'] {
  title, 
  subtitle, 
  createdAt, 
  'content': content[] {
    ..., 
    ...select(_type == 'imageGallery' => {'images': images[]{..., 'url': asset -> url}})
  },
  'slug': slug.current,
  'thumbnail': {
    'alt': thumbnail.alt,
    'imageUrl': thumbnail.asset -> url
  },
  'author': author -> {
    name,
    role,
    'image': image.asset -> url
  },
  'tag': tag -> {
    title,
    'slug': slug.current
    }
}

 

'💬 > ㅍㅅㅌㅋㅍㅅ 챌린지' 카테고리의 다른 글

49, 50일차  (0) 2022.03.30
48일차  (0) 2022.03.30
46일차  (0) 2022.03.29
45일차  (0) 2022.03.28
43, 44일차  (0) 2022.03.26