strapi-starter-next-blogでTailwind CSSを使ってみる
Gridsomeの開発・メンテナンス状況があまり思わしくないことと自由度の高さから、Next.jsのstrapiのスターターを試してみました。
バックエンドには下記のstrapiプロジェクトを使用します。
Next.jsの開発環境構築
手順はGridsomeのときと同様です。
ここではstrapi-starter-gridsome-blog/next-blogディレクトリを作成し、VSCodeで開いて下記のファイルを作成します。
{
"name": "next-blog",
"dockerComposeFile": "docker-compose.yml",
"service": "next-blog",
"workspaceFolder": "/src",
"settings": {
},
"extensions": [
],
}
GridsomeのときにはDockerのネットワークを作成してコンテナ名での通信でうまくいっていたのですが、Next.jsでは通信はできるものの、画像が表示されなかったため、network_mode
をhostにしました。
version: "3"
services:
next:
image: node:14-slim
volumes:
- ../:/src
working_dir: /src
command: /bin/sh -c "while sleep 1000; do :; done"
network_mode: "host"
これに合わせてstrapiプロジェクト側のdocker-compose.ymlも下記のように変更しています。
version: "3"
services:
strapi-blog:
container_name: strapi-blog
image: node:14-slim
volumes:
- ../:/src
working_dir: /src
command: /bin/sh -c "while sleep 1000; do :; done"
depends_on:
- mysql
network_mode: "host"
mysql:
image: mysql:5.7
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
environment:
MYSQL_DATABASE: strapi-blog
MYSQL_USER: strapi-blog
MYSQL_PASSWORD: strapi-blog
MYSQL_ROOT_PASSWORD: strapi-blog
TZ: "Asia/Tokyo"
volumes:
- db-data:/var/lib/mysql
network_mode: "host"
volumes:
db-data:
また、strapiプロジェクトのbackend/config/database.jsも下記のように'host'のmysqlをlocalhostに変更しています。
module.exports = ({ env }) => ({
defaultConnection: 'default',
connections: {
default: {
connector: 'bookshelf',
settings: {
client: 'mysql',
host: env('DATABASE_HOST', 'localhost'),
port: env.int('DATABASE_PORT', 3306),
database: env('DATABASE_NAME', 'strapi-blog'),
username: env('DATABASE_USERNAME', 'root'),
password: env('DATABASE_PASSWORD', 'strapi-blog'),
ssl: env.bool('DATABASE_SSL', false),
},
options: {}
},
},
});
ファイル作成後はVSCodeでRemote-Containers: Reopen in Containerを実行します。
$ yarn create strapi-starter . next-blog
Next.jsのインストールが終わると下記の様に続けてstrapiのインストールが開始されますが、ここではいったんQuickstart
を選択し、インストールが始まったらcontrol + cで強制終了します。
その後、作成されたbackendディレクトリを削除します。
Next.jsの起動
下記コマンドを実行してNext.jsを起動します。
$ cd frontend
$ yarn install
$ yarn yarn develop
Next.jsのバージョンアップ
strapi-starter-next-blogのNext.jsはバージョンが9なので最新のバージョン11にしてみます。
いったんNext.jsを停止し、下記を実行します。
$ yarn add react@latest react-dom@latest
$ yarn add next@latest
バージョンアップ後、問題なく起動することを確認します。
$ yarn yarn develop
動作確認したらNext.jsを停止します。
TypeScriptに対応する
下記を実行してtypescriptと@types/react、@types/nodeをインストールします。
$ yarn add --dev typescript @types/react @types/node
下記コマンドを実行し、空のtsconfig.jsonを作成し、Next.jsを起動するとnext-env.d.ts
が作成され、tsconfig.jsonにデフォルトの設定が出力されます。
$ touch tsconfig.json
$ yarn develop
動作確認したらNext.jsを停止します。
ESLintを設定する
下記コマンドでeslintとeslint-config-nextをインストールします。
$ yarn add --dev eslint eslint-config-next
strapi-starter-next-blogにもともとある.eslintrc
を削除し、下記コマンドを実行します。
$ yarn run next lint
Next.js用の.eslintrc
が作成されます。
Tailwind CSSのインストール
$ yarn add --dev tailwindcss@latest postcss@latest autoprefixer@latest
tailwind.config.jsの作成
下記コマンドを実行してtailwind.config.jsとpostcss.config.jsを作成します。
$ yarn run tailwindcss init -p
tailwind.config.jsの編集
作成したfrontend/tailwind.config.jsを下記のように編集してPurgeの設定を追加します。
module.exports = {
purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [],
}
Tailblocksのコンポーネントをベースにトップページを編集
Gridsomeの場合と同様に編集します。
下記のようにfrontend/pages/_app.jsのimport "../assets/css/style.css";
をimport 'tailwindcss/tailwind.css'
に変更し、UIkitのCSS部分を削除します。
import App from "next/app";
import Head from "next/head";
import 'tailwindcss/tailwind.css'
import { createContext } from "react";
import { getStrapiMedia } from "../lib/media";
import { fetchAPI } from "../lib/api";
// Store Strapi Global object in context
export const GlobalContext = createContext({});
const MyApp = ({ Component, pageProps }) => {
const { global } = pageProps;
return (
<>
<Head>
<link rel="shortcut icon" href={getStrapiMedia(global.favicon)} />
</Head>
<GlobalContext.Provider value={global}>
<Component {...pageProps} />
</GlobalContext.Provider>
</>
);
};
// getInitialProps disables automatic static optimization for pages that don't
// have getStaticProps. So article, category and home pages still get SSG.
// Hopefully we can replace this with getStaticProps once this issue is fixed:
// https://github.com/vercel/next.js/discussions/10949
MyApp.getInitialProps = async (ctx) => {
// Calls page's `getInitialProps` and fills `appProps.pageProps`
const appProps = await App.getInitialProps(ctx);
// Fetch global site settings from Strapi
const global = await fetchAPI("/global");
// Pass the data to our page via props
return { ...appProps, pageProps: { global } };
};
export default MyApp;
frontend/pages/index.jsのJSX部分を下記のように変更します。
import React from "react";
import Articles from "../components/articles";
import Layout from "../components/layout";
import Seo from "../components/seo";
import { fetchAPI } from "../lib/api";
const Home = ({ articles, categories, homepage }) => {
return (
<Layout categories={categories}>
<Seo seo={homepage.seo} />
<Articles articles={articles} />
</Layout>
);
};
export async function getStaticProps() {
// Run API calls in parallel
const [articles, categories, homepage] = await Promise.all([
fetchAPI("/articles"),
fetchAPI("/categories"),
fetchAPI("/homepage"),
]);
return {
props: { articles, categories, homepage },
revalidate: 1,
};
}
export default Home;
frontend/components/nav.jsのJSXを変更します。
import React from "react";
import Link from "next/link";
const Nav = ({ categories }) => {
return (
<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 href="/" className="flex title-font font-medium items-center text-gray-900 mb-4 md:mb-0"><a>Strapi Blog</a></Link>
<nav className="md:ml-auto flex flex-wrap items-center text-base justify-center">
{categories.map((category) => {
return (
<Link key={category.id} as={`/category/${category.slug}`} href="/category/[id]">
<a className="mr-5 text-gray-600 hover:text-gray-900">{category.name}</a>
</Link>
);
})}
</nav>
</div>
</header>
);
};
export default Nav;
frontend/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.slug} />
);
})}
</div>
</div>
</section>
);
};
export default Articles;
frontend/components/card.jsのJSXを変更します。
import React from "react";
import Link from "next/link";
import Image from "./image";
const Card = ({ article }) => {
return (
<div className="p-4 md:w-1/3">
<Link as={`/article/${article.slug}`} href="/article/[id]">
<a>
<div className="h-full border-2 border-gray-200 border-opacity-60 rounded-lg overflow-hidden">
<Image
image={article.image}
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.category.name}</h2>
<h1 className="title-font text-lg font-medium text-gray-900 mb-3">{article.title}</h1>
<p className="leading-relaxed mb-3 text-gray-600">{article.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.author.name}
{article.author.picture && (
<Image
image={article.author.picture}
alt={`Picture of ${article.author.name}`}
className="rounded-full h-12 w-12 flex items-center justify-center ml-3"
/>
)}
</span>
</div>
</div>
</div>
</a>
</Link>
</div>
);
};
export default Card;
frontend/components/image.jsを下記のように変更します。
import { getStrapiMedia } from "../lib/media";
const Image = ({ image, style, className }) => {
const imageUrl = getStrapiMedia(image);
return (
<img
src={imageUrl}
alt={image.alternativeText || image.name}
style={style}
className={className}
/>
);
};
export default Image;
変更前
変更後
まとめ
今回Next.jsのバージョンを上げてみたり、TypeScriptに対応するためのパッケージのインストールはしましたが、結局TypeScriptに対応まではしませんでした。
スターターは便利ですが、最新の状態にはメンテナンスされてなく、これをベースにするよりは最初から作ったほうがいいかなと思いました。
Discussion