strapi-starter-gatsby-blogでTailwind CSSを使ってみる

13 min read読了の目安(約12300字

GridsomeはVueでできていましたが、そのもととなったReact製のGatsbyにもstrapiのスターターがあるのでこちらも試したいと思います。

バックエンドには下記のstrapiプロジェクトを使用します。

https://zenn.dev/mseto/articles/strapi-starter-blog

Gatsbyの開発環境構築

手順は下記のGridsomeのときと同様です。

ここではstrapi-starter-gridcome-blog/gatsby-blogディレクトリを作成し、VSCodeで開いて下記のファイルを作成します。

.devcontainer/devcontainer.json
{
  "name": "gatsby-blog",
  "dockerComposeFile": "docker-compose.yml",
  "service": "gatsby-blog",

  "workspaceFolder": "/src",

  "settings": {
  },

  "extensions": [
  ],
}

nameとserviceがgatsby-blogになっている以外はGridsomeのときと同じです。

.devcontainer/docker-compose.yml
version: "3"

services:
    gatsby-blog:
        image: node:14.16
        ports:
            - "8000:8000"
        volumes:
            - ../:/src
        working_dir: /src
        command: /bin/sh -c "while sleep 1000; do :; done"
        networks:
            - shared-network

networks:
    shared-network:
        external: true

strapiのプロジェクトと同じネットワークに接続するため、networkにはshared-networkを設定します。
ファイル作成後はVSCodeでRemote-Containers: Reopen in Containerを実行します。

yarn create strapi-starter . gatsby-blog

Gatsbyのインストールが終わると下記の様に続けてstrapiのインストールが開始されますが、ここではインストールしないのでcontrol + cで強制終了します。

Gatsbyの設定

Gatsbyはfrontendディレクトリにインストールされます。
frontend/.env.exampleをfrontend/.envにコピーして下記のように修正します。

frontend/.env
GATSBY_ROOT_URL=http://localhost:8000
API_URL=http://strapi-blog:1337

strapi-blogstrapiの.devcontainer/docker-compose.ymlで設定したコンテナ名です。

Gatsbyの起動

下記コマンドを実行してGatsbyを起動します。

$ cd frontend
$ yarn develop

Gatsbyが起動したらhttp://localhost:8000/にアクセスするとブログが表示されます。

Tailwind CSSのインストール

$ yarn add --dev gatsby-plugin-postcss tailwindcss@latest postcss@latest autoprefixer@latest

tailwind.config.jsの作成

下記コマンドを実行してtailwind.config.jsとpostcss.config.jsを作成します。

$ npx tailwindcss init -p

tailwind.config.jsの編集

作成したfrontend/tailwind.config.jsを下記のように編集してPurgeの設定を追加します。

frontend/tailwind.config.js
module.exports = {
  purge: ['./src/**/*.{js,jsx,ts,tsx}'],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
}

gatsby-plugin-postcssの有効化

frontend/gatsby-config.jsのpluginsにgatsby-plugin-postcssを追加します。

frontend/gatsby-config.js
require("dotenv").config({
  path: `.env`,
});

module.exports = {
  plugins: [
    "gatsby-plugin-react-helmet",
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images`,
      },
    },
    {
      resolve: "gatsby-source-strapi",
      options: {
        apiURL: process.env.API_URL || "http://localhost:1337",
        contentTypes: ["article", "category", "writer"],
        singleTypes: [`homepage`, `global`],
        queryLimit: 1000,
      },
    },
    "gatsby-plugin-image",
    "gatsby-transformer-sharp",
    "gatsby-plugin-sharp",
    {
      resolve: `gatsby-plugin-manifest`,
      options: {
        name: "gatsby-starter-default",
        short_name: "starter",
        start_url: "/",
        background_color: `#663399`,
        theme_color: `#663399`,
        display: `minimal-ui`,
        icon: `src/images/favicon.png`,
      },
    },
    "gatsby-plugin-offline",
    // 追加
    "gatsby-plugin-postcss"
  ],
};

CSSの読み込みを追加

frontend/src/styles/global.cssを作成し、下記のようにします。

frontend/src/styles/global.css
@tailwind base;
@tailwind components;
@tailwind utilities;

サイト全体にCSSを反映するため、frontend/gatsby-browser.jsを作成し、frontend/src/styles/global.cssをimportします。

frontend/gatsby-browser.js
import './src/styles/global.css';

Tailblocksのコンポーネントをベースにトップページを編集

Gridsomeの場合と同様に編集します。

frontend/src/pages/index.jsのmain.cssのimportを削除し、JSXを変更します。
また、allStrapiArticleのクエリにdescriptionを追加します。

frontend/src/pages/index.js
import React from "react";
import { graphql, useStaticQuery } from "gatsby";
import Layout from "../components/layout";
import ArticlesComponent from "../components/articles";

const IndexPage = () => {
  const data = useStaticQuery(query);

  return (
    <Layout seo={data.strapiHomepage.seo}>
      <ArticlesComponent articles={data.allStrapiArticle.edges} />
    </Layout>
  );
};

const query = graphql`
  query {
    strapiHomepage {
      hero {
        title
      }
      seo {
        metaTitle
        metaDescription
        shareImage {
          publicURL
        }
      }
    }
    allStrapiArticle(filter: { status: { eq: "published" } }) {
      edges {
        node {
          strapiId
          slug
          title
          category {
            name
          }
          image {
            childImageSharp {
              gatsbyImageData(width: 800, height: 500)
            }
          }
          author {
            name
            picture {
              childImageSharp {
                gatsbyImageData(width: 30, height: 30)
              }
            }
          }
        }
      }
    }
  }
`;

export default IndexPage;

frontend/src/components/seo.jsのUIkit CSSおよびJSの読み込み部分を削除します。

frontend/src/components/seo.js
import React from "react";
import PropTypes from "prop-types";
import { Helmet } from "react-helmet";
import { useStaticQuery, graphql } from "gatsby";

const SEO = ({ seo = {} }) => {
  const { strapiGlobal } = useStaticQuery(query);
  const { defaultSeo, siteName, favicon } = strapiGlobal;

  // Merge default and page-specific SEO values
  const fullSeo = { ...defaultSeo, ...seo };

  const getMetaTags = () => {
    const tags = [];

    if (fullSeo.metaTitle) {
      tags.push(
        {
          property: "og:title",
          content: fullSeo.metaTitle,
        },
        {
          name: "twitter:title",
          content: fullSeo.metaTitle,
        }
      );
    }
    if (fullSeo.metaDescription) {
      tags.push(
        {
          name: "description",
          content: fullSeo.metaDescription,
        },
        {
          property: "og:description",
          content: fullSeo.metaDescription,
        },
        {
          name: "twitter:description",
          content: fullSeo.metaDescription,
        }
      );
    }
    if (fullSeo.shareImage) {
      const imageUrl =
        (process.env.GATSBY_ROOT_URL || "http://localhost:8000") +
        fullSeo.shareImage.publicURL;
      tags.push(
        {
          name: "image",
          content: imageUrl,
        },
        {
          property: "og:image",
          content: imageUrl,
        },
        {
          name: "twitter:image",
          content: imageUrl,
        }
      );
    }
    if (fullSeo.article) {
      tags.push({
        property: "og:type",
        content: "article",
      });
    }
    tags.push({ name: "twitter:card", content: "summary_large_image" });

    return tags;
  };

  const metaTags = getMetaTags();

  return (
    <Helmet
      title={fullSeo.metaTitle}
      titleTemplate={`%s | ${siteName}`}
      link={[
        {
          rel: "icon",
          href: favicon.publicURL,
        },
        {
          rel: "stylesheet",
          href: "https://fonts.googleapis.com/css?family=Staatliches",
        },
      ]}
      script={[

      ]}
      meta={metaTags}
    />
  );
};

export default SEO;

SEO.propTypes = {
  title: PropTypes.string,
  description: PropTypes.string,
  image: PropTypes.string,
  article: PropTypes.bool,
};

SEO.defaultProps = {
  title: null,
  description: null,
  image: null,
  article: false,
};

const query = graphql`
  query {
    strapiGlobal {
      siteName
      favicon {
        publicURL
      }
      defaultSeo {
        metaTitle
        metaDescription
        shareImage {
          publicURL
        }
      }
    }
  }
`;

frontend/src/components/nav.jsのJSXを変更します。

frontend/src/components/nav.js
import React from "react";
import { Link, StaticQuery, graphql } from "gatsby";

const Nav = () => (
  <StaticQuery
    query={graphql`
      query {
        strapiGlobal {
          siteName
        }
        allStrapiCategory {
          edges {
            node {
              slug
              name
            }
          }
        }
      }
    `}
    render={(data) => (
      <header className="text-gray-600 body-font">
        <div className="container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center">
          <Link to="/" className="flex title-font font-medium items-center text-gray-900 mb-4 md:mb-0">{data.strapiGlobal.siteName}</Link>
          <nav className="md:ml-auto flex flex-wrap items-center text-base justify-center">
            {data.allStrapiCategory.edges.map((category, i) => (
              <Link to={`/category/${category.node.slug}`} key={`category__${category.node.slug}`} className="mr-5 text-gray-600 hover:text-gray-900">
                {category.node.name}
              </Link>
            ))}
          </nav>
        </div>
      </header>
    )}
  />
);

export default Nav;

frontend/src/components/articles.jsのJSXを変更します。

frontend/src/components/articles.js
import React from "react";
import Card from "./card";

const Articles = ({ articles }) => {
  return (
    <section className="text-gray-600 body-font">
      <div className="container px-5 py-4 mx-auto">
        <div className="flex flex-wrap -m-4">
          {articles.map((article, i) => {
            return (
              <Card
                article={article}
                key={`article__left__${article.node.slug}`}
              />
            );
          })}
        </div>
      </div>
    </section>
  );
};

export default Articles;

frontend/src/components/card.jsのJSXを変更します。

frontend/src/components/card.js
import React from "react";
import { Link } from "gatsby";
import { GatsbyImage } from "gatsby-plugin-image";

const Card = ({ article }) => {
  return (
    <div className="p-4 md:w-1/3">
      <Link to={`/article/${article.node.slug}`}>
        <div className="h-full border-2 border-gray-200 border-opacity-60 rounded-lg overflow-hidden">
          <GatsbyImage
            image={article.node.image.childImageSharp.gatsbyImageData}
            alt={`Hero image`}
            className="lg:h-48 md:h-36 w-full object-cover object-center"
          />
          <div className="p-6">
            <h2 className="tracking-widest text-xs title-font font-medium text-gray-400 mb-1">{article.node.category.name}</h2>
            <h1 className="title-font text-lg font-medium text-gray-900 mb-3">{article.node.title}</h1>
            <p className="leading-relaxed mb-3 text-gray-600">{article.node.description }</p>
            <div className="flex items-center flex-wrap ">

              <span className="text-gray-400 inline-flex items-center lg:ml-auto md:ml-0 ml-auto leading-none text-sm py-1">
                {article.node.author.name}
                {article.node.author.picture && (
                    <GatsbyImage
                    image={article.node.author.picture.childImageSharp.gatsbyImageData}
                    alt={`Picture of ${article.node.author.name}`}
                    className="rounded-full h-12 w-12 flex items-center justify-center ml-3"
                    />
                )}
              </span>
            </div>
          </div>
        </div>
      </Link>
    </div>
  );
};

export default Card;

変更前
strapi-blog変更前

変更後
strapi-blog変更後

アバター画像の左上だけなぜか角丸になりませんでした。

まとめ

個人的にはVue製のGridsomeのほうが書きやすかったです。
ただ、Gatsbyの開発は活発に行われていますが、Gridsomeの開発は鈍いようで、まだバージョン1にもなっていません。
バックエンドに使用しているstrapiの管理画面もReactでできていることもあり、今後のことを考えるとGatsbyもいいかなと思いました。