AstroでWordPressを爆速Headless化してみる

最近流行りの[Astroフレームワーク](https://docs.astro.build/ja/getting-started/)で、WordPressの静的Headless化がかなりお手軽にできたのでまとめてみました。

## 開発環境
- [Astro 2.0](https://docs.astro.build/ja/getting-started/)
- [WordPress 6.1.1](https://ja.wordpress.org/)
- [Local](https://localwp.com/)
- node 18.12.1
- Mac OS 13.2(Ventura)
- Visual Studio Code([Astroプラグイン](https://marketplace.visualstudio.com/items?itemName=astro-build.astro-vscode)入り）

## Astroとは？
>Astroは、コンテンツにフォーカスした高速なWebサイトを構築するためのオールインワンWebフレームワークです。

([公式ドキュメント](https://docs.astro.build/ja/getting-started/)より)

## LocalでWordPressを構築
まずLocalを使って、ローカル環境にWordPressを構築します。
![local.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151789/0dc78fc8-213b-acaf-3963-350698a600ff.png)

<dl>
<dt>サイト名</dt>
<dd>Astro Blog（お好みで）</dd>
<dt>サイトドメイン</dt>
<dd>astro-blog.local</dd>
</dl>

## WordPressの設定
WP AdminでWordPressのダッシュボードを立ち上げ、いくつかの設定を行います。
![wp-setting1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151789/624f68fd-0370-bd8d-a02d-4b8b8c92f80e.png)

サイトアドレスを、Astro上の構造に合わせてサブディレクトリを足します（Astroでもサイト直下に展開する場合は、WordPressアドレスと同じでかまいません）。
ここでは、`/blog`を追加しました。

その他の設定は、通常のWordPressと同じくお好みで。

### WordPressテーマを適用

最終的にフロントエンド側はAstroで構築するので、テーマはなんでも良いのですが、スタイリングしやすい文書構造だったり、機能的なブロックエディタを持つテーマを選択すると良いと思います。

ここでは、シンプルで使いやすい「[Arkhe](https://arkhe-theme.com/ja/)」テーマをインストールして適用しました。

テーマをインストールしたら、テスト用にいくつか記事を作成して公開しておきます。
![wp-page.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151789/9b690b9a-3125-4136-8076-0545f58b332d.png)

公開するときのURLパーマリンクが、AstroでHeadless化したときのファイル名になるため、英数字で構成しておきましょう。
![wp-url.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151789/3fda8861-f983-4179-b983-4bf4fa9713d7.png)


ひとまずWordPress側は一旦完了です。Localを起動したまま、Astroの構築に移ります。

## Astroをインストールする

Astroは[日本語のドキュメント](https://docs.astro.build/ja/install/auto/)が充実しているので、迷ったらドキュメントを見ましょう。

まず、Localで構築したWordPressブログのフォルダー（以下astro-blog）にアクセスできるようにします。
（Localのメニュー[Go to site folder]でフォルダーが開きます）
![wp-folder.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151789/a6b7b090-f497-ac4e-9dff-30c70dceea71.png)

Macの場合、

`[ユーザー] > Local Sites > [構築したWordPressのフォルダー]`

にあります。

次に、ターミナルを開き、「astro-blog」に移動し、以下のコマンドでAstroをインストールします。

```
npm create astro@latest
```

いくつか質問されるので、以下を参考に入力・選択してください。

>Where should we create your new project?

Astroがインストールされるフォルダ名を入力します（後述しますが、最終的にフォルダから外に出すのでなんでも良いです）。`astro`と入力します。

>How would you like to start your new project?

Astroに慣れている人ならどれでも良いですが、ここでは`Include sample files`を選択します。

>Install dependencies?

お好みで。

> Initialize a new git repository?

gitを使うかどうか。お好みで。

> Do you plan to write TypeScript?

TypeScriptを使うかどうか。お好みで。

>How strict should TypeScript be?

お好みで。

問題なければ、「astro」フォルダーの中にAstro一式が展開されていると思います。

```
astro-blog/
├── app
├── astro
│    ├── astro.config.mjs
│    ├── node_modules
│    ├── package.json
│    ├── public
│    ├── README.md
│    ├── src
│    └── tsconfig.json
├── conf
└── logs
```

次に、せっかく「astro」フォルダーにまとまっていますが、中のファイルを全て選択し、「astro-blog」直下に移動して、「astro」フォルダーは削除します。
（AstroからWordPress関連のファイルにアクセスするため）

```
astro-blog/
├── app
├── astro.config.mjs
├── conf
├── logs
├── node_modules
├── package.json
├── public
├── README.md
├── src
└── tsconfig.json
```

ここからVisual Studio Codeでコーディングしていきます。
事前に[Astroプラグイン](https://marketplace.visualstudio.com/items?itemName=astro-build.astro-vscode)をインストールしておきましょう。

エディタを立ち上げて、「astro-blog」を開き、

```
npm run dev
```

を実行して、Astroのサンプルページが立ち上がることを確認しましょう。
![astro-dev.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151789/5a332661-40a8-5e7f-e0e7-dab219f21e98.png)


## AstroとWordPressをつなげる

一旦`npm run dev`を停止し、「astro-blog」直下に.envファイルを作成し、以下のようにWordPress Rest APIのURLを記述しておきます。

```
PUBLIC_API_URL = http://astro-blog.local/wp-json/wp/v2/
```
（`http://astro-blog.local`の部分は、Localで作成したWordPressのURLになります）

次に、`src/pages/index.astro`を開き、コードをいくつか追加します。

```diff
---
import Layout from '../layouts/Layout.astro'
import Card from '../components/Card.astro'


+ const res = await fetch(`${import.meta.env.PUBLIC_API_URL}posts`);
+ const posts = await res.json();
---

<Layout title="Welcome to Astro.">
  <main>
    <h1>Welcome to <span class="text-gradient">Astro</span></h1>
+   {
+     posts.map((post:any) => (
+       <p><a href={`/blog/${post.slug}`} set:html={post.title.rendered} /></p>
+     ))
+   }
    <p class="instructions">
    　　To get started, open the directory <code>src/pages</code> in your project.<br />
    　　<strong>Code Challenge:</strong> Tweak the "Welcome to Astro" message above.
    </p>
// 以下略
```

再びnpm run devを実行すると、先ほどのAstroのサンプルページにWordPressで作成したページのタイトルがリンクとして追加されていると思います。
![astro-dev2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151789/79289b84-9357-ce97-dfff-871d3ad1f4de.png)

このままだとまだリンク先が存在しないので、各ページを構築していきます。

`src/pages/blog/[slug].astro`ファイルを作成します。

```
src/
├─ pages
│   ├── blog
│   │ 　 　 └─ [slug].astro
│   └─ index.astro
```

`[slug].astro`ファイルに以下の記述をします。

```
---
import Layout from '../../layouts/Layout.astro'

const { slug } = Astro.params;
const res = await fetch(`${import.meta.env.PUBLIC_API_URL}posts?_embed&slug=${slug}`);
const [post] = await res.json();
const renderedContent = post.content.rendered;

export async function getStaticPaths() {
  const data = await fetch(`${import.meta.env.PUBLIC_API_URL}posts`);
  const posts = await data.json();

  return posts.map((post:any) => ({
    params: { slug: post.slug },
    props: { post: post },
  }));
}
---

<Layout title={post.title.rendered}>
  <a href="/">Astro Blog</a>
  <h1 set:html={post.title.rendered} />
  <Fragment set:html={renderedContent} />
</Layout>
```

ちなみにこのへんの情報もすべて[公式ドキュメント](https://docs.astro.build/ja/guides/cms/wordpress/)にも載っています。

`[slug].astro`のコードを記述したら、先ほどのトップページのリンクから各ページにアクセスしてみましょう。
![astro-page.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151789/c5621fb3-6191-68ad-67c7-3f86165ab55a.png)

WordPressで作成したページがAstroの静的HTMLとして構築されています。
しかし、WordPressから持ってきたのはHTML構造だけなので、Astroで少しスタイリングを行います。

## Astroでページをスタイリングする

今回WordPressには、「[Arkhe](https://arkhe-theme.com/ja/)」テーマを適用しているので、このテーマを活かしたスタイリングを行います。

```diff
---
import Layout from '../../layouts/Layout.astro'
+ import '../../../app/public/wp-includes/css/dist/block-library/style.min.css'
+ import '../../../app/public/wp-content/themes/arkhe/dist/css/main.css'

const { slug } = Astro.params;
const res = await fetch(`${import.meta.env.PUBLIC_API_URL}posts?_embed&slug=${slug}`);
const [post] = await res.json();
const renderedContent = post.content.rendered;

export async function getStaticPaths() {
  const data = await fetch(`${import.meta.env.PUBLIC_API_URL}posts`)
  const posts = await data.json();

  return posts.map((post:any) => ({
    params: { slug: post.slug },
    props: { post: post },
  }));
}
---
<Layout title={post.title.rendered}>
+  <div class="l-content__body l-container">
+    <a href="/">Astro Blog</a>
+
+    <main class="l-main">
+      <article class="l-main__body">
+        <h1 class="c-pageTitle__main" set:html={post.title.rendered} />
+        <div class="c-postContent">
+          <Fragment set:html={renderedContent} />
+        </div>
+      </article>
+    </main>
+  </div>
</Layout>
```

Local内にあるWordPressのblock-libraryのCSSと、「Arkhe」テーマのメインCSSを直接importしています。
これによって、WordPress側でテーマのアップデートによってCSSの変更があった場合も、Astro側に自動的に適用されます。

そして、`<Layout title={post.title.rendered}>`内の文書構造を、「Arkhe」テーマで生成される文書構造と合わせます（最小限のclassのみ）。

ここまで変更したら、もう一度devでビルドされたページを見ると、
![astro-page2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151789/531ce0dd-3904-fa59-15a4-162df2ae08e0.png)

ほぼWordPress上と同じような見た目にスタイリングできました。
（実際はインラインで生成されるstyleなどもあるため、WordPress上とは異なる場合もあります）

## Localのリンクを修正する

あともう少しだけ修正です。

見た目はほぼ再現できているように見えますが、ページ上の画像はLocalで生成された絶対パスのままになっています。
（例：http://astro-blog.local/wp-content/uploads/2023/02/hoge.png）

このままだとHeadless化で公開できないので、すべての画像をWordPressからAstro側にコピーすることにします。
手動でやるととても面倒なので、NPMスクリプトに任せましょう。

事前準備として、`fs-extra`をインストールしておきます。

```
npm install fs-extra
```

`fs-extra`をインストールしたら、package.jsonに、WordPressでアップロードした画像が入っているフォルダーを、Astroのpublicフォルダーにコピーするスクリプトを追加します。

```diff
"scripts": {
+ "copy": "node -e \"require('fs-extra').copySync('./app/public/wp-content/uploads', './public/wp-content/uploads')\"",
  "dev": "astro dev",
  "start": "astro dev",
  "build": "astro build",
  "preview": "astro preview",
  "astro": "astro"
},
```

これで、

```
npm run copy
```
を実行すると、WordPressの画像フォルダーがAstro用にコピーされます。
ついでに、devとbuildしたときに最初にcopyが実行されるようにしておきます。

```diff
"scripts": {
  "copy": "node -e \"require('fs-extra').copySync('./app/public/wp-content/uploads', './public/wp-content/uploads')\"",
  "dev": "npm run copy && astro dev",
  "build": "npm run copy && astro build",
  "preview": "astro preview",
  "astro": "astro"
},
```

最後に、`[slug].astro`ファイルで、WordPressのコンテンツを流し込むときに、正規表現ですべての`http://astro-blog.local`を置換（削除）して、絶対パスをルートパスに変更します。

```
const renderedContent = post.content.rendered.replace(/http:\/\/astro-blog.local/g, '');
```

これにより、
```
src="http://astro-blog.local/wp-content/uploads/2023/02/hoge.jpeg"
```
みたいなコードが
```
src="/wp-content/uploads/2023/02/hoge.jpeg"
```
に置き換わります。
（サイト内リンクのhrefなども同様）

最後に、

```
npm run build
```
をすると、distフォルダーに静的HTMLファイル一式が出力されるので、サーバーにアップロードすれば完成です。

最終的にはCSSやJSに諸々の調整が必要だと思いますが、このように、Astroフレームワークを使うことで、ローカル環境だけでWordPressの静的Headless化がお手軽に行えるので、試してみてください。

おしまい。