LoginSignup
6

More than 1 year has passed since last update.

Next.jsとChakra UIとmicroCMSでMarkdownで書けるページを作る

Posted at

概要

サンプル記事

こういう感じのMarkdownで書けるページをNext.jsとChakra UIとmicroCMSで実装する。

実装

プロジェクトの準備

上記を参考にプロジェクトの準備をする。
TypeScriptとChakra UIも入れておく。

ターミナル
$ npx create-next-app microcms-next-jamstack-blog

$ cd microcms-next-jamstack-blog

$ yarn add -D typescript @types/react @types/node

$ touch tsconfig.json

# ここで_app.jsとindex.jsの拡張子を.tsxに書き換える

$ yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion

$ yarn dev

microCMSの準備

アカウント登録、ログイン、サービスの作成、APIの作成などこちらを参考に進める。
API テンプレートからブログを選択しておく。

「ブログ」と「カテゴリ」のAPIが作られるのでブログを選択する。

microCMS

「ブログ」のAPI設定からAPIスキーマを選択し、contentが「リッチエディタ」になっていることを確認する。
microCMSではこのリッチエディタがMarkdownに対応しているエディタである。

content

「ブログ」の中に入っているサンプル記事をリッチエディタで編集する。
#で見出し、*で箇条書きなど基本的なMarkdown記法が使える。
リンクも[リンクテキスト](リンクURL)の書き方で問題ない。
リッチエディタ上ではスタイルとして変換後の状態で表示される。

Markdown

書き終わったら右上の「APIプレビュー」から「取得」を選択し、
見出しがh1などhtmlに変換されているか確認する。

プロジェクトとAPIの接続

APIキーの設定を下記を参考に進める。
APIキーは先ほど開いた「APIプレビュー」の画面に書かれている。

同じく上記記事を参考に「microcms-js-sdk」を導入する。

ターミナル
$ yarn add microcms-js-sdk

libsディレクトリを作成しclient.jsファイルを作成する。
serviceDomainXXXX.microcms.ioのXXXXの文字列を入力する。

client.js
import { createClient } from 'microcms-js-sdk';

export const client = createClient({
  serviceDomain: 'service-domain',
  apiKey: process.env.API_KEY,
});

表示のための整備

_app.tsxをChakra UIの書き方に合わせて修正する。

_app.tsx
import "../styles/globals.css";

import { ChakraProvider } from "@chakra-ui/react";

function MyApp({ Component, pageProps }) {
  return (
    <ChakraProvider>
      <Component {...pageProps} />
    </ChakraProvider>
  );
}

export default MyApp;

index.tsxも修正する。

index.tsx
import { Box, Heading } from "@chakra-ui/react";
import React from "react";
import { MarkdownTemplate } from "../components/MarkdownTemplate";
import { client } from "../libs/client";
import type { Blog } from "../types/blog";

type Props = {
  blog: Array<Blog>;
};

export default function Home({ blog }: Props) {
  const title = blog[0].title;
  const content = blog[0].content;

  return (
    <>
      <Box p="10">
        <Heading
          as="h1"
          fontWeight={"200"}
          lineHeight={1.25}
          mb={".67em"}
          pb={".3em"}
          size="4xl"
        >
          {title}
        </Heading>
        <MarkdownTemplate source={content} />
      </Box>
    </>
  );
}

export const getStaticProps = async () => {
  const data = await client.get({ endpoint: "blog" });

  return {
    props: {
      blog: data.contents,
    },
  };
};

typesディレクトリを作成し、blog.tsファイルを作成する。

blog.ts
export type Blog = {
  id: string
  createdAt: string
  updatedAt: string
  publishedAt: string
  revisedAt: string
  title: string
  content: string
}

ここで「html-react-parser」を導入する。
remarkablemark/html-react-parser: HTML to React parser.

ターミナル
$ yarn add html-react-parser

componentsディレクトリを作成してMarkdownTemplate.tsxを作成する。
Chakra UIではaタグをLinkと表記するなど、タグの書き方もそのままでは使えないのでこのファイルで変換している。
また、見た目はGitHubのスタイルを参考にしている。

MarkdownTemplate.tsx
import {
  BoxProps,
  Box,
  Text,
  UnorderedList,
  ListItem,
  Link,
} from "@chakra-ui/react";
import parse, { domToReact, HTMLReactParserOptions } from "html-react-parser";

type MarkdownTemplateProps = {
  source: string;
} & BoxProps;

const h1 = {
  component: Text,
  props: {
    mt: "24px",
    mb: "16px",
    lineHeight: 1.25,

    color: "#202020",
    fontWeight: "600",
    pb: ".3em",
    fontSize: "2em",
    borderBottom: "1px solid #E7ECF2",
  },
};

const h2 = {
  component: Text,
  props: {
    mt: "24px",
    mb: "16px",
    lineHeight: 1.25,

    color: "#202020",
    fontWeight: "600",
    pb: ".3em",
    fontSize: "1.5em",
    borderBottom: "1px solid #E7ECF2",
  },
};

const h3 = {
  component: Text,
  props: {
    mt: "24px",
    mb: "16px",
    lineHeight: 1.25,

    color: "#202020",
    fontWeight: "600",
    fontSize: "1.25em",
  },
};

const p = {
  component: Text,
  props: {
    lineHeight: "1.5",
    mb: "10px",
    fontSize: "16px",
    color: "##000",
  },
};

const ul = {
  component: UnorderedList,
  props: {
    color: "#000",
    mt: 0,
    mb: 0,
    pl: "2em",
    lineHeight: "1.6",
  },
};

const li = {
  component: ListItem,
};

const a = {
  component: Link,
  props: {
    isExternal: true,
    textDecoration: "none",
    color: "#0058B3",
    _hover: {
      textDecoration: "none",
      color: "#4593e6",
    },
  },
};

const options: HTMLReactParserOptions = {
  replace: (domNode: any) => {
    if (domNode.type === "tag") {
      if (domNode.name === "h1") {
        return (
          <Text as="h1" {...h1.props}>
            {domToReact(domNode.children, options)}
          </Text>
        );
      }
      if (domNode.name === "h2") {
        return (
          <Text as="h2" {...h2.props}>
            {domToReact(domNode.children, options)}
          </Text>
        );
      }
      if (domNode.name === "h3") {
        return (
          <Text as="h3" {...h3.props}>
            {domToReact(domNode.children, options)}
          </Text>
        );
      }
      if (domNode.name === "ul") {
        return (
          <UnorderedList {...ul.props}>
            {domToReact(domNode.children, options)}
          </UnorderedList>
        );
      }
      if (domNode.name === "li") {
        return <ListItem>{domToReact(domNode.children, options)}</ListItem>;
      }
      if (domNode.name === "a") {
        return (
          <Link {...a.props} href={domNode.attribs.href}>
            {domToReact(domNode.children, options)}
          </Link>
        );
      }
      if (domNode.name === "p") {
        return (
          <Text {...p.props}>{domToReact(domNode.children, options)}</Text>
        );
      }
    }
  },
};

export const MarkdownTemplate = (props: MarkdownTemplateProps) => {
  return <Box {...props}>{parse(props.source, options)}</Box>;
};

反映の確認

ターミナル
$ yarn dev

すると下記のような見た目になるはず。
Markdownで編集・更新できるページの完成🎉

完成した見た目

参考資料

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
6