BLOG

ブログ

ポートフォリオサイトをNuxt3でJamstack化してみた【GitHub Actions編】

こんにちは、中谷です。

前回【Nuxt×WP REST API編】を書きましたが、今回はWordPressのリニューアルのGitHub Actions編に入ります!

要件

サーバーXserver
CI/CD ツールGitHub Actions
ヘッドレスCMSWordPress(REST API)
フロントエンドNuxt3
Nodev18.4.0

SSG 静的ジェネレート

さて、Nuxtの動的サイトは完成しましたが、これをレンタルサーバーにFTPアップロードできる静的ファイルにする必要があります。

静的ファイル化すればアクセス時に余計なAPI通信が発生しなくて済むのでページを高速表示することができます。

ローカルでnpm run generateをすれば.output配下に静的ファイル群が生成されます。

が!しかし、htmlファイルが生成されて成功したかと思いきや、動的ページを検証ツールのネットワークタブで調べてみるとAPIリクエストが飛んでいるではないか!
動的ページをリロードするとエラーになるなどの事象も発生。

このままではちゃんと静的化されていないので、Nuxt.js 3で動的ルーティングのFull static generationをするを参考にnuxt.config.tsに追記していきます。

import axios from 'axios'

export default defineNuxtConfig({
  hooks: {
    // slugページのGenerate
    async 'nitro:config'(nitroConfig) {
      if (nitroConfig.dev) {
        return
      }

      // データをFetch
      async function fetchData(apiUrl: string, perPage: number) {
        try {
          // ページ総数を取得
          const response = await axios.get(apiUrl)
          const totalPages = Number(response.headers['x-wp-totalpages'])

          // 毎ページごとのデータを返す
          const dataList = []
          for (let page = 1; page <= totalPages; page++) {
            const pageResponse = await axios.get(`${apiUrl}?per_page=${perPage}&page=${page}`)
            dataList.push(...pageResponse.data)
          }
          return dataList
        } catch (error) {
          console.error('Error fetching data:', error)
          return []
        }
      }

      // Fetchしたデータのパスを配列にする
      function getPages(name: string, pageRoutes: string[], perpage: number) {
        const pageArray = []
        let pageNum = 1
        if (pageRoutes.length % perpage > 0) {
          pageNum = pageRoutes.length / perpage + 1
        }

        for (let page = 1; page <= pageNum; page++) {
          if (page !== 1) {
            pageArray.push(`/${name}/page/${page}`)
          }
        }
        return pageArray
      }

      // カスタム投稿タイプの全ページをFetch
      async function fetchAndProcessData() {
        const photoList = await fetchData('https://manage.shalcan.com/wp-json/wp/v2/photo', 10)
        const newsList = await fetchData('https://manage.shalcan.com/wp-json/wp/v2/news', 10)
        const artList = await fetchData('https://manage.shalcan.com/wp-json/wp/v2/art', 10)

        const photoRoutes = photoList.map((photo) => `/photo/${photo.id}`)
        const photoPages = getPages('photo', photoRoutes, 36)
        const newsRoutes = newsList.map((news) => `/news/${news.id}`)
        const newsPages = getPages('news', newsRoutes, 10)
        const artRoutes = artList.map((art) => `/art/${art.id}`)

        const pageRoutes = photoRoutes
          .concat(photoPages)
          .concat(newsRoutes)
          .concat(newsPages)
          .concat(artRoutes)

        return pageRoutes
      }

      if (nitroConfig.prerender?.routes === undefined) {
        return
      }

      // すべてのページURLの配列を渡す
      nitroConfig.prerender.routes = await fetchAndProcessData()
    },
  },
})

これで再度generateするとAPIリクエストが発生せず、完全に静的化されました!

ポイント

nitroConfig.prerender.routes = await fetchAndProcessData()

ここですべての動的ページURLの配列を渡すことで完全な静的ジェネレートができました。
[‘/photo/1′,’/photo/2′,’/art/1′,’/photo/2’]的な感じですね。

静的GenerateするとAPIレスポンスをファイルにまとめた_payload.jsonが各ページごとに生成されます。
ページアクセス時にAPIを読むかわりにローカルの_payload.jsonを読ませることで高速表示することができます。

検証ツールで違いを確認してみてください。

※ちなみにいろいろやった結果アーカイブページのpage/hoge/page/[slug].vue以外はnitroConfigに渡さなくても静的化できるようになってて謎でした。
とりあえず動作がおかしくなるページだけnitroConfigに渡す形でもいい気がします。

GithubActionsを動かす

Nuxt側の準備が整ったので次はCI/CDツールのGitHub Actionsを使っていきます。

GitHubActionsとは

GitHubのイベントをトリガーにして様々な処理を行うことができるサービスです。
今回はGitHub上でビルドやFTPアップロードなどを自動で行う仕組みをつくります。

今回やりたいことはこの2つです。

  • GitをPushしたらActionsを動かす
  • WordPress側でActionsを動かせるようにする

PushしたらActionsを動かす

GitHub側の準備

GitHubのsettingsにてFTP情報を設定します。

Secrets and variablesタブからActionsを選択して、アップロード先のサーバー情報を入力します。

  • FTP_USERNAME
    FTPサーバのアカウント名
  • FTP_SERVER
    FTPサーバのホスト
  • FTP_PASSWORD
    FTPサーバのパスワード

Xserverの管理画面に行けばFTP情報が載っています。

ローカルファイルでの対応

まずはmainブランチをPushしたときにGitHubでCI/CDが動くようにします。

ルートに.github/workflowsフォルダを作成してワークフローを構成するYAMLファイルを作成します。

push.ymlとしていますが名前はなんでもいいです。

.github/workflows/push.yml

name: Node.js CI

on:  # トリガー
  push:
    branches: ["main"]

jobs:
  build:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [18.4.0]

    steps:
      - uses: actions/checkout@v3
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
          cache: "npm"
      - name: npm install # パッケージをインストール
        run: npm install
      - name: generate # SSG
        run: npm run generate

      - name: List output files
        run: ls -la dist/ # ファイルリストを表示

      - name: Deploy via FTP⏫
        uses: SamKirkland/FTP-Deploy-Action@4.0.0
        with:
          server: ${{ secrets.FTP_SERVER }}      # FTPサーバのホスト
          username: ${{ secrets.FTP_USERNAME }}  # FTPサーバのアカウント名
          password: ${{ secrets.FTP_PASSWORD }}  # FTPサーバのパスワード
          server-dir: /hogehoge.com/public_html/ # アップロード先のリモートパス
          local-dir: ./dist/ # ローカルのアップロードしたいフォルダのパス

このワークフローの流れとしては

  1. mainブランチにpushされたらActionsを動かす
  2. Node.jsのv18.4.0を使用する
  3. npm install
  4. npm run generate
  5. ファイルリストを表示
  6. XserverにFTPアップロード

これでPushされたらアップロードする仕組みができました。

実際にPushをして動くかどうか確認してみてください。

このようにGitHubの管理画面のActionsタブからビルドの状況を見ることができます。

ちなみにロリポップサーバーはFTPにIPアクセス制限がかかっていて今回の目的には使えませんでした。

Xserverでの注意点

XserverではREST APIのアクセス制限があります。

これをOFFにしないとGitHubからAPIの取得ができないので注意してください。

WordPress側からActionsを動かす

WordPressで記事や写真を投稿したらActionsを動かしたいと思ったのでこちらも設定を行います。

ローカル側

github/workflowsフォルダにYAMLファイルを作成します。

.github/workflows/save.yml

name: After saving wordpress

on:  # トリガー
  repository_dispatch:
    type: after_saving_wordpress

jobs:
  build:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [18.4.0]
        # See supported Node.js release schedule at https://nodejs.org/en/about/releases/

    steps:
      - uses: actions/checkout@v3
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'
      - name: npm install # パッケージをインストール
        run: npm install
      - name: generate # SSG
        run: npm run generate

      - name: List output files
        run: ls -la dist/ # ファイルリストを表示

      - name: Deploy via FTP⏫
        uses: SamKirkland/FTP-Deploy-Action@4.0.0
        with:
          server: ${{ secrets.FTP_SERVER }}      # FTPサーバのホスト
          username: ${{ secrets.FTP_USERNAME }}  # FTPサーバのアカウント名
          password: ${{ secrets.FTP_PASSWORD }}  # FTPサーバのパスワード
          server-dir: /hogehoge.com/public_html/ # アップロード先のリモートパス
          local-dir: ./dist/ # ローカルのアップロードしたいフォルダのパス

GitHub側

管理画面でGitHubトークンを取得します。

Settingsページのサイドメニュー下部の「Developer settings」をクリックします。

今までのトークンはclassicとなり、Fine-grained tokensが新しく出来ていたのでこちらの解説をします。

Fine-graind tokens

きめ細やかなアクセス権の制御ができるセキュリティの観点が向上したトークンです。

Beta版となっていますが、現在はこちらの使用が推奨されているのでこちらを使いましょう。

右上のGenerate new tokenで新規作成します。

Expirationはとりあえず更新がめんどいので90日にしますが、30日にしておいた方がセキュリティ上はよさそうですね。

今回使用しているリポジトリを選択します。

PermissionsのRepository permissionsを開いて、Contentsの項目でRead and writeを選択。
GithubActionsを動かしたいだけなのでこれを変更するだけでOK。

トークンを発行したら使用するのでコピーしてメモっておきましょう。

WordPress側

記事を公開したタイミングでデプロイしてもいいのですが、写真を複数アップロードしたかったのでWordPressの管理画面にフックの実行ボタンをつけたいと思いました。

ここではウェブスタジオTANIさんの記事を参考にさせて頂きます!
WordPress+Nuxt(SSG)+GitHub Actionsでレンタルサーバーに自動デプロイする方法

※ちなみにリンク先には実行ボタンのほか、WordPressの記事を投稿した段階で自動でデプロイできるようにする方法も載っているのでチェック!

WordPressテーマでおなじみfunctions.phpに以下を追記します。

/* -------------------------- */
/* GitHub ActionsのHookを実行 */
/* -------------------------- */
function dispatch_github_actions() {
  $token = 'xxxxxxxxxxxxxxxxxxxx'; // トークンを設定
  $url = 'https://api.github.com/repos/{User}/{Repository}/dispatches'; // repos/ユーザー名/リポジトリ名/dispatches
  $headers = [
    'Authorization: bearer ' . $token,
    'Accept: application/vnd.github.v3+json',
    'User-Agent: after_saving_wordpress',
    'X-GitHub-Api-Version: 2022-11-28'
  ];
  $data = [
    'event_type' => 'after_saving_wordpress',
    'client_payload' => [
      'unit' => false,
      'integration' => true
    ]
  ];

  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL, $url);
  curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
  curl_setopt($ch, CURLOPT_HEADER, true);
  curl_setopt($ch, CURLOPT_POST, true);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_exec($ch);
  curl_close($ch);
}

/* -------------------------- */
/* GitHub ActionsへのHook実行を管理画面に追加 */
/* -------------------------- */
add_action('admin_menu', 'custom_menu_page');
function custom_menu_page() {
  add_menu_page(
    'GitHub Actions',
    'GitHub Actions',
    'manage_options', // WPのアカウント権限を変えたい場合はここを変更する
    'custom_menu_page',
    'add_custom_menu_page',
    'dashicons-admin-generic',
    30 // メニュー項目の表示位置
  );
}
function add_custom_menu_page() {
  if ( !current_user_can( 'manage_options' ) ) { // WPのアカウント権限を変えたい場合はここを変更する
    wp_die( __( 'You do not have sufficient permissions to access this page.' ) );
  }

  echo '<div class="wrap">';
  echo '<h2>GitHub Actionsフック実行画面</h2>';

  if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty( $_POST['run'] ) && $_POST['run'] === 'run') {
    dispatch_github_actions();
    echo "GitHub Actionsのフックが実行されました。";
  } else {
    echo '<form method="post" action="">';
    echo '<button type="submit" name="run" value="run">実行を開始</button>';
    echo '</form>';
  }
  echo '</div>';
}

これでActionsがWordPress側から実行できるようになりました。

ボタンを押したらGitHub側で動いているのを確認しましょう!

まとめ

これでNuxt3 ✕ WP REST API ✕ GithubActionsでのJamstack構成が完成しました!
改めて完成したサイトがこちらです。

自分のサイトを持つと技術的な実験がしやすいのでオススメです♪

それではまた!

RELATED ARTICLE