strapi-starter-gatsby-blogでTailwind CSSを使ってみる
GridsomeはVueでできていましたが、そのもととなったReact製のGatsbyにもstrapiのスターターがあるのでこちらも試したいと思います。
バックエンドには下記のstrapiプロジェクトを使用します。
Gatsbyの開発環境構築
手順は下記のGridsomeのときと同様です。
ここではstrapi-starter-gridsome-blog/gatsby-blogディレクトリを作成し、VSCodeで開いて下記のファイルを作成します。
{
"name": "gatsby-blog",
"dockerComposeFile": "docker-compose.yml",
"service": "gatsby-blog",
"workspaceFolder": "/src",
"settings": {
},
"extensions": [
],
}
nameとserviceがgatsby-blogになっている以外はGridsomeのときと同じです。
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にコピーして下記のように修正します。
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.jsとpostcss.config.jsを作成します。
$ npx tailwindcss init -p
tailwind.config.jsの編集
作成したfrontend/tailwind.config.jsを下記のように編集してPurgeの設定を追加します。
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を追加します。
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を作成し、下記のようにします。
@tailwind base;
@tailwind components;
@tailwind utilities;
サイト全体にCSSを反映するため、frontend/gatsby-browser.jsを作成し、frontend/src/styles/global.cssをimportします。
import './src/styles/global.css';
Tailblocksのコンポーネントをベースにトップページを編集
Gridsomeの場合と同様に編集します。
frontend/src/pages/index.jsのmain.cssのimportを削除し、JSXを変更します。
また、allStrapiArticleのクエリにdescriptionを追加します。
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の読み込み部分を削除します。
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を変更します。
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を変更します。
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を変更します。
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;
変更前
変更後
まとめ
個人的にはVue製のGridsomeのほうが書きやすかったです。
ただ、Gatsbyの開発は活発に行われていますが、Gridsomeの開発は鈍いようで、まだバージョン1にもなっていません。
バックエンドに使用しているstrapiの管理画面もReactでできていることもあり、今後のことを考えるとGatsbyもいいかなと思いました。
Discussion