やりたいこと
pnpm
で create next-app
した Next.js を Azure App Service に Deploy したい。Deploy にアホほど時間がかかるのもなんとかしたい。
しばらく前にやったのと、結構内容が変わったような気がするので、改めて。
前にやったヤツ
以前は、VS Code から Deploy したが、チーム開発の場合不便なので、今回は GitHub Actions で Deploy する。
環境
- Node.js: 20.17.0
- pnpm: 9.9.0
最初の Setup
Next.js アプリケーション
pnpm create next-app@canary deploy-test
今回は、訳あって version 15 の Next.js を使う。
- next.js: 15.0.0-canary.140
- react: 19.0.0-rc-7771d3a7-20240827
- react-dom: 19.0.0-rc-7771d3a7-20240827
がインストールされた。Next.js v14 等でも以下の話は同じである。
canary やら rc やら beta やらをサーバに Deploy したら、何か不具合あるかな?と思ったが、当然のように何の問題もなかった。
pnpm dev
, pnpm build
, pnpm start
などを一通り試し、問題なく動くことを確認。
GitHub にリポジトリを作成し、push。
Azure App Service
Azure Portal から GitHub と連携するように設定して App Service を立ち上げる。
Node のバージョンは、20 で。
そうすると、GitHub リポジトリに以下のファイルが追加され、GitHub Actions が走る。
# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
# More GitHub Actions for Azure: https://github.com/Azure/actions
name: Build and deploy Node.js app to Azure Web App - deploytest
on:
push:
branches:
- main
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js version
uses: actions/setup-node@v3
with:
node-version: '20.x'
- name: npm install, build, and test
run: |
npm install
npm run build --if-present
npm run test --if-present
- name: Zip artifact for deployment
run: zip release.zip ./* -r
- name: Upload artifact for deployment job
uses: actions/upload-artifact@v4
with:
name: node-app
path: release.zip
deploy:
runs-on: ubuntu-latest
needs: build
environment:
name: 'Production'
url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
permissions:
id-token: write #This is required for requesting the JWT
steps:
- name: Download artifact from build job
uses: actions/download-artifact@v4
with:
name: node-app
- name: Unzip artifact for deployment
run: unzip release.zip
- name: Login to Azure
uses: azure/login@v2
with:
client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_xxxx }}
tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_yyyy }}
subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_zzzz }}
- name: 'Deploy to Azure Web App'
id: deploy-to-webapp
uses: azure/webapps-deploy@v3
with:
app-name: 'deploytest'
slot-name: 'Production'
package: .
Dependencies のインストールや build などは npm
で行われるが、build 等でエラーは出ないので、いったんそのまま完了を待つ。
14分くらいで Deploy が完了したが…
残念。:(
ログを見ながら、問題点を潰していくところだが、npm を pnpm に変えたり、ポートを変えたり、先にやっておきたいことがあるので、まずはそれをやる。
基本設定
Node のバージョンを固定する
あんまり気にしなくても良さそうだが、Node.js は 20.17.0 に固定する。
Local
いらないかも知れないが、一応。
$ volta pin node@20.17.0
package.json に以下が追加される。
"volta": {
"node": "20.17.0"
}
弊社は、volta 利用。
"engines": {
"node": "20.17.0"
}
これも追加しておく。
GitHub Actions
Build 時に利用される Node のバージョンを指定する。
- name: Set up Node.js version
uses: actions/setup-node@v4
with:
node-version: '20.17.0'
20.x
を 20.17.0
に変更。actions/setup-node
も v3
から v4
に変更。v3と4の違いは、キャッシュ機能が強化されているようで、デフォルトで v3 が指定されるが、 v4 を使う方が良い。
npm ではなく pnpm を使うようにする
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.9.0
run_install: false
- name: Set up Node.js version
uses: actions/setup-node@v4
with:
node-version: "20.17.0"
cache: "pnpm"
- name: Install Dependencies
run: pnpm install
- name: Build Application
run: pnpm run build
App Service > 設定 > 構成 > スタックの設定 > スタートアップコマンド
は、 pnpm run start
ではうまく動かないそうなので(ちゃんと調べてない)、 npm run start
としておく。
PORT を変更する
Azure App Service は、デフォルトで 8080 ポートを Listen する。環境変数で、3000 を指定しても良いが、3000 じゃなきゃダメな理由もないので、Production は、8080 で立ち上がるように package.json に手を加える。
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start -p 8080",
"lint": "next lint"
},
一旦、ログを見るまでもなくやっとく設定は完了。
これで一旦 Deploy してみる。が、めちゃくちゃ時間がかかるようになってしまった。1時間くらいかかった。サイズが 86MB から 466MB に増えている。
エラーログを確認
Error: Could not find a production build in the '.next' directory. Try building your app with 'next build' before starting the production server. https://nextjs.org/docs/messages/production-start-no-build-id
.next
ディレクトリがないと。そりゃ動きません。本当にないのか?を確認するため、build job 中に Zip に圧縮したファイルをダウンロードして見てみると…確かにないです。つまり build したものが、App Service のコンテナに届いていないということですね。
原因は、
- name: Zip artifact for deployment
run: zip release.zip ./* -r
ここですね。.next
等の隠しディレクトリを含めたい場合は、
zip release.zip ./* -r
ではなく、zip release.zip . -r
とする必要がある。
のですが、リリースのたびに1時間も待ってられないので、それも含めて対応します。
対応方針
- まず、サイズが大きくなってしまっているので、
output: 'standalone'
にする - build したファイル郡(.next/standalone)だけを Zip 圧縮してやる
- スタートアップコマンドは、
node server.js
に変更する(.next/standalone 配下のみがリリースされるので) - PORT = 8080 を環境変数に設定する
standalone
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: "standalone",
};
export default nextConfig;
"scripts": {
"dev": "next dev",
"build": "next build && cp -r .next/static .next/standalone/.next/ && cp -r public .next/standalone/",
"start": "next start",
"lint": "next lint"
}
.next/standalone
配下に必要なものが全て揃うように、コピーをするようにした。start の -p 8080 は、start コマンドで起動しなくなったので、削除。
zip
- name: Zip artifact for deployment
run: |
cd .next/standalone
zip ../../release.zip . -qr
cd ../../
スタートアップコマンドと、PORT は Azure Portal から設定。
準備完了。
身に覚えのないモジュールでエラー
Error: Cannot find module 'styled-jsx/package.json'
Next.js が内部的に使っているらしい。GitHub Copilot に聞くと、styled-jsx をインストールすれば解決すると言われたが、解決しない。そして1つ解決しても次、次、と際限ないヤツっぽい。
これは、pnpm の node_modules の取り扱いと、Azure の symlink の取り扱いの不整合による。どちらかと言えば、Azure 側に原因があるようだ。
pnpm はデフォルトで、node_modules/.pnpm
配下にモジュールをインストールし、そこに対して symlink を作成する。
Azure は symlink をデプロイすると、symlink のままではなく、symlink の参照先を実体化する。その際に相対パスが壊れる。
どうやらこういうことらしい。
これに対するうまい解決策が思いつかなかったので、symlink でインストールしないように変更した。
ルートディレクトリに、.npmrc を設置。
node-linker=hoisted
こうすると、node_modules
配下にモジュールをインストールし、symlink を作らないようになる。この解決策が本当に良いのかどうかは、よく分からないが、Azure が「symlink を実体化してしまう」のであれば、容量的にはコンパクトになっている…かな。いったん良しとした。
結果
できました!Deploy にかかった時間も、前回(最後に Deploy には成功した時)が 64 分でしたが、今回は 5 分ジャストで完了。92%短縮!
人は、たたきがないと動き出す腰が重いものだが、自分が書いてないそれっぽいものの中から間違いを見つけるのは非常に大変なものである。
おまけ: 環境変数
なお、Build 時に必要な環境変数は、該当リポジトリの
settings > Secrets and variables > Actions > Repository secrets
で設定し、
GitHub Actions のファイル内にて、
- name: Build
run: pnpm run build
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
こんな感じで呼び出す。