Next.jsで作った静的サイトをGitHub Actionsで自動アップロードする

こんにちは。最近CIの素晴らしさを実感し始めたkitakkunです。

今回は、Next.jsで作ったウェブサイトの更新フローをGitHub Actionsを活用して自動化してみたのですが、まあまあ詰まってしまったので記事にまとめてみました。

## 前提事項

- サーバー環境: さくらレンタルサーバー（FTP)

## やりたいこと

以下のワークフローをGitHub Actionsで自動化していきます。
- Next.jsプロジェクトをビルド・エラーチェック
- Next.jsサイトを静的サイトとして書き出し（静的書き出しは事前に設定済みとします）
- 静的サイトデータをFTPでレンタルサーバーにアップロード

## GitHub Actionsを書く

僕自身も感覚で実際に書いて理解したので、細かくは説明しません。とりあえず冒頭部はこんな感じになります。

```yml
name: "Deploy Next.js App"

on:
  push:
    branches: [ develop ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v2
      # 以下に続く・・（今は省略）
```
developブランチへプッシュされると、最新版のUbuntu上で`steps`以下に定義されるタスクが順番に走っていきます。

### リポジトリのチェックアウト
Actionが走るブランチに切り替えます。`actions/*`はすでに提供されているタスクのショートカットみたいなものだと思います。
```yml
name: Checkout repository
uses: actions/checkout@v2
```

### Node.jsのセットアップ

Node.jsに関しても使えるactionがあるらしいのでそちらを活用します。バージョンはご自身のプロジェクトに合わせて変更してください。
```yml
name: Setup Node.js
uses: actions/setupj-node@v2
with:
  node-version: 18.x
```

### 依存npmパッケージのインストール

`package.json`に定義されている依存モジュールをCI環境にインストールします。細かいことはよくわかりませんが、`npm install`ではなく`npm ci`を使ったほうが`package-lock.json`との整合性などにもチェックが入ってより安全なようです。
```yml
name: Install NPM packages
run: npm ci
```

### Next.jsプロジェクトのビルド

ビルドを走らせます。プログラムにエラーとかあったらここで落ちます。
```yml
name: Build Next.js App
run: npm run build
```
### Next.jsプロジェクトのエクスポート

アップロード用にファイルをエクスポートします。
```yml
name: Export Next.js App
run: npm run export
```
`create-next-app`したときにexportコマンドは定義されていないと思うので、
`package.json`に追記しましょう。

```json:package.json
  "scripts": {
    "dev": "next dev",
    "build": "next build && next export",
    "start": "next start",
    "export": "next export",
    "lint": "next lint"
  }
```

追記しないと落ちます。

他のブログ記事とかに`export`のステップが書いてあったので、とりあえず入れてるけど、本当に必要なのかは疑問。ログ見た感じ`build`の時点で`out`に出力はされていたので、いらないような気もする。

### FTPサーバーへのアップロード（ここで超躓いた）

この時点でアップロード用のファイルが、`out`に入っています。これを、ウェブサイトをホストするFTPサーバーに移動します。

```yml
name: FTP Deploy
uses: SamKirkland/FTP-Deploy-Action@4.3.0
  with:
    server: ${{ secrets.FTP_SERVER }}
    username: ${{ secrets.FTP_USERNAME }}
    password: ${{ secrets.FTP_PASSWORD }}
    local-dir: ./out/
    server-dir: /home/${{ secrets.FTP_USERNAME }}/www/
    protocol: ftps
```

パット見て何やってるかよくわからないと思うので要所要所で分けて説明します。

#### 使っているActionについて

FTP接続・アップロードのタスクに関しては、こちらのアクションを使わせてもらっています。

https://github.com/SamKirkland/FTP-Deploy-Action

##### 各パラメータの補足

詳しいことはリポジトリのページを見てほしいのですが、サンプルで指定しているパラメータに関する説明は以下のとおりです。

- `server`: サーバーのドメイン・URL
- `username`: 認証用ユーザ名
- `password`: 認証用パスワード
- `local-dir`: リモートに反映させたいディレクトリのパス（反映元）
- `server-dir`: サーバー上の対応するディレクトリのパス（反映先）
- `protocol`: 接続プロトコル（デフォルトは`ftp`）

#### `secrets`って何？

GitHub Actionsのファイル内に認証情報を書いても動くことには動きますが、公開すべきデータではないので隠蔽するべきです。

Secretsのパラメータとして定義してやれば、具体的な中身を隠したまま認証情報の記述ができます。
Secretsはリポジトリの「Settings -> Secrets -> Actions」から設定できます（詳細は割愛）。

例えば、
```
${{ secrets.FTP_SERVER }}
```

はCI実行時自動的にSecretsに定義された`FTP_SERVER`の値に置き換わります。

#### 躓きポイント1: 海外IPからのサーバーアクセス

「さくらレンタルサーバー」は、（おそらくデフォルトで）海外IPからのアクセスを遮断する設定になっています。GitHub Actionsは海外サーバーから実行されますから、海外IPからのアクセスを許可しないと、FTPアクセスを拒絶されてワークフローが落ちます。

#### 躓きポイント2: `server-dir`に指定するパス

これもおそらく「さくらレンタルサーバー」特有の問題だと思うのですが、`server-dir`のパスには`/home/[ユーザー名]/www/`を指定しないと、更新処理時にディレクトリ削除が発生したとき`no such file or directory`のエラーが出てCIが落ちました。

ターミナルからサーバー接続したとき、`/home/[ユーザー名]/`のパスにつながっていたので、`./www/`（相対パス）で問題ないと思っていたのですが、2回目の差分アップロード時に失敗するという事象が起きてしまいました。

<a href="https://arrown-blog.com/githubactions-ftp-deploy/">こちらの記事</a>でも以下のように説明されているので、ここはレンタルサーバーによって指定の仕方に注意が必要かもしれません。

> - ロリポップ→ホスト名の後にWebサイトデータの存在するディレクトリ（フォルダ）名
>（例：●●.lolipop.jp/サイトのフォルダ名）
> - エックスサーバ→ドメイン名の後に/public_htm/ Webサイトデータの存在するディレクトリ名
> - さくらインターネット→/home/アカウント名/www/ Webサイトデータの存在するディレクトリ名
>
> 出典：[GitHub Actionsを使ってFTP自動デプロイ（Webサイト公開）を実現！ソフトを使った手動アップロードを卒業する](https://arrown-blog.com/githubactions-ftp-deploy/)

なお、`./www/`で指定しても、1回目だけはちゃんと動作するので、原因特定にとても時間がかかりました。同じようなことで躓く人が減ってほしい。

## 完成したymlコードの全体像

完成したGitHub Actionsのコードはこんな感じです。developが更新されたとき、自動でサーバーの内容に変更が反映されるので、とても便利です。

```yml
name: Deploy Next.js App

on:
  push:
    branches: [ develop ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v2

      - name: Setup Node.js
        uses: actions/setup-node@v2
        with:
          node-version: 18.x

      - name: Install NPM packages
        run: npm ci

      - name: Build Next.js App
        run: npm run build

      - name: Export Next.js app
        run: npm run export

      - name: FTP Deploy
        uses: SamKirkland/FTP-Deploy-Action@4.3.0
        with:
          server: ${{ secrets.FTP_SERVER }}
          username: ${{ secrets.FTP_USERNAME }}
          password: ${{ secrets.FTP_PASSWORD }}
          local-dir: ./out/
          server-dir: /home/${{ secrets.FTP_USERNAME }}/www/
          protocol: ftps
```

## 参考文献

- [GitHub Actionsを使ってFTP自動デプロイ（Webサイト公開）を実現！ソフトを使った手動アップロードを卒業する](https://arrown-blog.com/githubactions-ftp-deploy/) (Arrownさま)
- [Next.js アプリを GitHub Actions でビルドして GitHub Pages で公開する｜まくろぐ](https://maku.blog/p/au8ju6g/) (まくろぐさま)