Docker・Next.js・WordPressでヘッドレスCMS
はじめに
初めてZenn記事を投稿します。
こちらの記事を参考に同じように作成していきました。
ここまで詳しく分かりやすい記事がなかったのでほんとにありがたかったです。
WordPressからのデータ取得は別の方法で書いてます。
記録用です。
DockerでWordPressを作成していく。
ディレクトリ構造
next-wordpress/
├── wp/
└── compose.yml
WordPressの設置
先にDockerを起動。
next-wordpressという名前の空のフォルダを作成。
services:
db:
image: mariadb:latest
volumes:
- db_data:/var/lib/mysql
restart: always
environment:
- MYSQL_ROOT_PASSWORD=password
- MYSQL_DATABASE=wordpress
- MYSQL_USER=root
- MYSQL_PASSWORD=password
wordpress:
depends_on:
- db
image: wordpress:latest
volumes:
# wordpressのファイルを一式マウント
- ./wordpress:/var/www/html
ports:
- "8000:80"
restart: always
environment:
- WORDPRESS_DB_HOST=db:3306
- WORDPRESS_DB_USER=root
- WORDPRESS_DB_PASSWORD=password
volumes:
db_data:
wordpressコンテナのvolumesでマウントしないとローカルに反映されないので書いておく。
二つのコンテナのUSERとPASSWORDは一致させる。
compose.ymlがある階層で↓で、コンテナが作成される。
※たまに作成してすぐに接続するとエラーになることがあるので、少し時間をおいて接続してみる。
docker compose up -d
(http://localhost:8000/)
にアクセス、インストールしてトップページが表示されるようにする。
WordPressの新規テーマ作成
wpの下にwordpressフォルダが作成されるので、テーマを作成して3つのファイルを作成。
wordpress/
├── wp-content/
│ └── themes/
│ └── [独自テーマ名/] # 新規作成
│ ├── index.php # 新規作成(中は空でOK)
│ ├── style.css # 新規作成(中は空でOK)
│ └── functions.php # 新規作成(中は空でOK)
├── compose.yml
テーマを有効化。
他のデフォルトテーマは使用しないので削除。
WordPress初期設定
サムネイル有効化
<?php
add_theme_support('post-thumbnails');
パーマリンク変更
設定>パーマリンク>投稿名
nvmのインストール
nodeは初めから入ってることとします。
nodeのバージョンを簡単に変更できるということなのでnvmをインストールしました。
ちょくちょくエラーが出てたのですが、nvmを使うことで簡単にnodeのバージョンを変更できてとても便利だと思いました。
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
source ~/.zshrc
Next.js作成
next-wordpress/
├── wp/
│ ├── wordpress/
│ └── compose.yml
├── front/ # これから作成します
インストール
nameはfront
TypeScriptはNo
TailWindCSSはYes
srcフォルダはYes
AppRouterはNo
yarn create next-app
eslintモジュールの依存関係が、現在使用しているNode.jsのバージョンと互換性がないので、nvmを使いnodeのバージョンを変更。
現在のNode.jsバージョンがv19.0.0だったので、
# 必要なバージョンをインストール
nvm install 18.18.0
# 必要なバージョンに切り替え
nvm use 18.18.0
frontフォルダに移動
cd front
各パッケージをインストール
yarn add next react react-dom
アプリを立ち上げる
yarn dev
http://localhost:3000/
にアクセス。
ヘッダーとフッターをコンポーネント化
フォルダ構成
src/
├── components/
│ ├── Header.jsx
│ └── Footer.jsx
├── pages/
│ └── posts/
│ ├── [id].jsx
│ └── index.jsx
├── styles/
│ └── globals.css
.env.local
globals.css
@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@300;400;700&display=swap");
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
*,
*::before,
*::after {
@apply box-border;
}
body {
@apply font-body;
}
}
.env.local
環境変数を定義するためのもの。
NEXT_PUBLIC_WP_ENDPOINT=http://localhost:8088/wp-json/wp/v2/posts
ヘッダー
ヘッダーとフッターを共通化する。
export const Header = () => {
return (
<header>
<h2>ヘッダーコンポーネント</h2>
</header>
);
};
フッター
export const Footer = () => {
return (
<footer>
<p>フッターコンポーネント</p>
</footer>
);
};
WordPressのデータを取得
fetchでWP REST APIのURLからデータを取得。
記事一覧ページ
import { Footer } from "@/components/Footer";
import { Header } from "@/components/Header";
import Link from "next/link";
// 動的パスの生成(全投稿を取得)
export async function getStaticProps() {
const res = await fetch(`${process.env.NEXT_PUBLIC_WP_ENDPOINT}?_embed`);
const posts = await res.json();
return {
props: {
posts, // 投稿データをpropsとして渡す
},
revalidate: 10, // 一定間隔を超えた時点でページを再生成する
};
}
// 投稿一覧ページ
const Posts = ({ posts }) => {
return (
<>
<Header />
<ul className="w-[95%] mx-auto py-8">
{posts.map((post) => {
return (
<>
{/* 記事一覧 */}
<li className="border-t first:border-b-0 border-b py-8 border-gray-300">
<Link
href={`/posts/${post.id}`}
passHref
key={post.id}
className="hover:opacity-50"
>
<p className="md:text-[16px] pt-[8px]">
{post.title.rendered}
</p>
</Link>
</li>
</>
);
})}
</ul>
<Footer />
</>
);
};
export default Posts;
http://localhost:3000/posts
にアクセス。
const res = await fetch(
${process.env.NEXT_PUBLIC_WP_ENDPOINT}?_embed);
の部分は.env.local
ファイルに環境変数として保存し呼びだす。
https://[ドメイン名]/wp-json/wp/v2/posts
この/wp-json/wp/v2/posts
を指定するだけでWordPressから情報を取得することができる。
/v2/posts
の後に?_embed
を入れることによって、追加の情報(投稿に関連する画像や著者情報など)も含まれるようになる。
props
として投稿一覧ページにposts
を渡し記事をmapさせ取り出していく。
記事詳細ページ
import { Header } from "@/components/Header";
import { Footer } from "@/components/Footer";
// URL(パス)を指定する
export async function getStaticPaths() {
const res = await fetch(process.env.NEXT_PUBLIC_WP_ENDPOINT);
const posts = await res.json();
// 各投稿のIDをパラメータとして渡す
const paths = posts.map((post) => ({
params: { id: post.id.toString() }, // idを文字列に変換して渡す
}));
return {
paths,
fallback: false, // 追加されたIDに対して404を返す
};
}
// データを取得して渡す
export async function getStaticProps({ params }) {
const postRes = await fetch(
`${process.env.NEXT_PUBLIC_WP_ENDPOINT}/${params.id}?_embed`
);
const post = await postRes.json();
return {
props: {
post, // 投稿データをpropsとして渡す
},
revalidate: 10, // 一定間隔を超えた時点でページを再生成する
};
}
// 記事が存在しない場合の処理
const PostDetail = ({ post }) => {
if (!post) {
return <div>記事がありません。</div>;
}
return (
<>
<Header />
<div className="mt-8 border-t pt-8 border-gray-300 max-w-3xl px-4 pt-6 lg:pt-10 pb-12 sm:px-6 lg:px-8 mx-auto">
<div className="max-w-2xl">
<div className="grow mb-6 mt-8">
<h2 className="text-2xl text-center mb-8 font-bold md:text-3xl dark:text-white">
{post.title.rendered}
</h2>
<div>
投稿日:{" "}
{`${new Date(post.date).getFullYear()}.${String(
new Date(post.date).getMonth() + 1
).padStart(2, "0")}.${String(
new Date(post.date).getDate()
).padStart(2, "0")}`}
</div>
</div>
<main>
{/* 本文 */}
<blockquote className="p-6 sm:px-7">
<div className="text-left leading-7">
<div
dangerouslySetInnerHTML={{
__html: post.content.rendered,
}}
></div>
</div>
</blockquote>
</main>
</div>
</div>
<Footer />
</>
);
};
export default PostDetail;
getStaticPaths
でURL(パス)を指定し、getStaticProps
でparams
を受け取りprops
として渡す。
getStaticPropsとは
ビルド時にgetStaticPropsを使用してページの事前レンダリングを行う。
ビルド時にデータの取得を行うので、ブログ更新などリアルタイム性のあるコンテンツには適しておらず、更新の必要があるたびに再ビルドが必要。
簡単な方法としては、revalidateオプションを使用する。
- getStaticPropsはサーバーサイドで実行される
- pagesフォルダのみで利用できる
- propsを返さないといけない
- propsで返す値もjsオブジェクトでなければならない(res.json()でjsオブジェクトに変換しているのはそのため)
- サーバーサイドで動くので、ログを確認するときはブラウザではなくターミナルを確認する。
Discussion