블로그에서 Tailwind CSS 사용

Gridsome은 Vue로 만들어졌지만 그 원형인 React제인 Gatsby에도 strapi의 시동기가 있어서 저도 한번 해보고 싶어요.
백엔드에서 다음strapi 항목을 사용합니다.
https://zenn.dev/mseto/articles/strapi-starter-blog

Gatsby의 개발 환경 구축


단계는 아래 설명Gridsome 때였어요.과 같습니다.
여기에서 strappi-starter-gridsome-blog/gatsby-blog 디렉터리를 만들고 VScode에서 다음 파일을 만듭니다.
.devcontainer/devcontainer.json
{
  "name": "gatsby-blog",
  "dockerComposeFile": "docker-compose.yml",
  "service": "gatsby-blog",

  "workspaceFolder": "/src",

  "settings": {
  },

  "extensions": [
  ],
}
name과 서비스가gatsby 블로그인 것을 제외하고는 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 프로젝트와 같은 네트워크을 위해shared-network를 네트워크로 설정합니다.
파일이 작성되면 VScode에서 Remote-Contaainer:Reopen in Contaainer가 실행됩니다.
$ yarn create strapi-starter . gatsby-blog
Gatsby 설치가 끝난 후 다음과 같이strapi를 계속 설치하지만 여기에는 설치하지 않고 control+c로 강제로 종료합니다.

Gatsby 구성


Gatsby는 fronted 디렉토리에 설치됩니다.
frontend/.env.example fronte를/를 입력합니다.다음과 같이 env로 복사합니다.
frontend/.env
GATSBY_ROOT_URL=http://localhost:8000
API_URL=http://strapi-blog:1337
strapi-blog는 strapi의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.postcssconfig.제작
$ npx tailwindcss init -p

tailwind.config.js 편집


제작된 fronte/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-plugen-postcss의 유효성


frontend/gatsby-config.js의plugens에gatsby-plugen-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를 반영하기 위해fronte/gatsby-browser.js,fronte/src/styles/global을 만듭니다.import css.
frontend/gatsby-browser.js
import './src/styles/global.css';

TailBlocks 기반 구성 요소 편집 첫 페이지


Gridsome의 상황입니다.와 마찬가지로 편집합니다.
frontend/src/pages/index.js의main.css의 import을 삭제하고 JSX를 변경합니다.
또한 allStrapiArtical의 검색에 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의 관리 화면에도 리액트가 있어 향후 일을 고려하면 가츠비도 나쁘지 않다.

좋은 웹페이지 즐겨찾기