GAミント至上主義

Web Monomaniacal Developer.

Google Cloud RunでCIでデプロイしつつもリリースを手動で行えるようにする

Cloud RunとCloud Buildを使ってデプロイしていますが、アクセスが集中しているときや、申し込み中などデプロイタイミングを手動で行いたいときがありました。

gclour run depoyコマンドに--no-trafficをつけるだけで、リビジョンとして追加されつつ、トラフィックを入れない状態で置いておけます

cloudbuild.yml

# 省略
  - id: "deploy front to Cloud Run"
    name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    entrypoint: gcloud
    args: ['run', 'deploy', 'nuxt', '--image', 'your-region-docker.pkg.dev/${PROJECT_ID}/your/repository:${COMMIT_SHA}', '--region', 'your-region', '--no-traffic']

このビルドが完了するとトラフィック0%でリビジョンに追加されるので、

右の操作からトラフィックを変更すれば任意のタイミングでリリースできます

Nuxt3とNestJSで表示高速化&Google Search Console警告対応

求人サイト「シニアジョブ」をNuxt3, NestJSでゼロから開発&運用中ですが、Google Search Console(以下サチコ)のウェブに関する主な指標で「不良」、もしくは「改善が必要」の項目が出てくるようになりました。

seniorjob.jp

それぞれ詳しく書くと長くなって書き終わらないので箇条書き程度にやったことをメモ。

いろいろやった結果、PageSpeed Insights (以下PSI) で2023/4/21時点でパフォーマンスの点数は

PCは87点前後

モバイルは50点前後

となりました。

モバイルはかなり厳しいので個人的には黄色の50(50未満は赤)で及第点と納得してます。

数字だけでなく、主要なページは体感でもかなり早くなったのでさわってほしいところ。

まずはPSIの直接的でわかりやすい指摘から潰していきます。

Nuxt側

だいたいファイルサイズ小さくしてね系

画像ファイル

  • width, heightを付ける(styleで変えてる場合はなるべく実際に表示されるサイズに合わせる)
  • デザインで使っている画像ファイルサイズを小さくする (WEBツールで小さくしたり、webpにしたり)

求人画像もできればやりたいけど、未対応

HTML

  • 余計なタグ、classの削除
  • v-ifしかないspan, divをtemplateにしてDOM要素数を減らす(空になったりするとバグる場合もあり)
  • ループ内の直書きSVGタグをファイル化(useなんてあるんだ・・・)

/_nuxt/entry.*.js を小さくする

NuxtでHTMLのあと、一番最初に読み込まれる外部JSファイルです。ビルド時に生成され*にはハッシュ値が入ります。

NuxtやVue Router系、plugins、middlewereなどが含まれます。

pluginで大きめなパッケージなどを使ってると大きくなりがち。

具体的なパッケージはnuxi analyzeで検討をつけます。

nuxi analyze · Nuxt

こんな感じで一部でしか使ってないクソデカライブラリがentryに入ってしまったりしてるのを気づけるので、importする場所やタイミングを工夫したり、なるべくpluginsからcomposablesに処理を移し、必要最低限しかentryに入らないようにします。

nuxt.com

特にFirebaseのライブラリが大きかったですが、全体で使うためpluginに残しておきたい。dynamic importすることでentry.*.jsに含まれなくなりました。

/_nuxt/entry.*.css を小さくする

jsと同じくnuxt.config.cssで指定したCSSファイル, Tailwind CSSの生成物などがここに入ってしまいます。

デザインを外注したため、1つの大きないわゆるstyle.cssが存在してました。

ページによって使うものが変わるため、分割し、nuxt.configからは削除、pageやlayoutで読み込むようにしました。

Nuxt Tailwindを使ってますが、これは全ページで使うTailwindのclassをまとめて入れてしまい、大きくなっているのが課題として残ります。

理想としてはすべてコンポーネントにscopedとして書き、分割、結合はNuxtに任せることですが、共通CSSもないと辛いので難しいところ。

Tailwindプラグイン Tailwind Elements削除

アコーディオンなどで一部使っていたんですが、Tailwindのプラグインということで分割importはできず、 使っていない全部のCSS, JSが読み込まれてしまい非常にファイルサイズが大きかった。

https://tailwind-elements.com/

アコーディオンなどは自前で実装し、削除

CLS (Cumulative Layout Shift) 読み込み中のボタンなどのズレ

CLSについてはこちら

ClientOnlyにfalkbackを設定する

<ClientOnly> · Nuxt Components

サーバー側とブラウザ側でDOMが違うと起きるVueのWarn Hydration node mismatch 対策として雑にClientOnlyを使ってましたが、大きな表示ずれの原因になるのでfallbackを指定し、ずれないようにしました

たとえばログイン状態↓こうすることで「非ログイン中に出すやつ」がSSRされるようになります。

<ClientOnly>
  <div v-if="isLogin">ログイン中にだすやつ</div>
  <div v-else>非ログイン中に出すやつ</div>
  <template #fallback>
    <div>非ログイン中に出すやつ</div>
  </template>
</ClientOnly>

ログイン有無はPSIでは指摘してくれないので、自分で気づく必要がありました。

layoutをやめる

詳し動きは下記ツイートなんですが、Nuxt本体のバグが仕様か、layoutsを使うとサイト内遷移のCSR時にどうしても一瞬slot部分が空になってしまいました。

  • 2023/6/15追記 page内でuseAsyncDataなどをawaitし、navigationをブロックするのが原因。awaitをとりスケルトンローディングを表示するなどで対応可能でした

フッターの位置が大きくずれて、減点MAX(1.0)の原因になってました。

ひとまず主要なページはpage側に移して回避。

こちらのページ遷移も単一しかみてくれないPSIは指摘してくれません

いい対策が思いつかないのでlayoutでヘッダー・フッターを入れるのをやめ、改善を確認。 のちに読み込むCSSだけ切り替えるようにlayoutは使用するようにしました。

Google Tag Manager

Google Analyticsの他、いろいろ広告やコンバージョン測定系をGTMで挿入してます。

Nuxtで読み込むJSより先に動くと、処理がブロックされてページ表示速度に影響がでてPSIでも下記警告が出ます。

ビジネス的に挿入しないわけにはいかないので、ページビュートリガーをドキュメントにあるうち一番遅い「ウィンドウの読み込み」に変更し、多少マシになりました。

ページビュー トリガー - タグ マネージャー ヘルプ

NestJS

重い処理はキャッシュ

都道府県、市区町村やら職種などのいわゆるマスターデータは毎回DBから取らずにメモリにキャッシュするようにしました。

サーバーコストを抑えるためもあり、まだRedisも入れてない理由もあって、メモリキャッシュを使ってますが、ネットワークのオーバーヘッドもないし、JSONなどにシリアライズせずにJSのオブジェクト等を持てる利点もあり、今後も用途に合わせて使っていく予定。

マネージドRedisが高い & 必要なサーバー数がまだ少ない & Cloud Runが安すぎて、キャッシュ用にメモリ1GB増やしても全然安い。

求人一覧ではDBを使わずにElasticsearchだけで必要な情報を返す

サイトので一番重要でもある求人一覧ページは、Elasticsearchで条件にあう求人を検索しIDを取得、そのIDを使ってDBから詳細取得の2段階をやっていましたが、 Elasticsearchに一覧で必要な情報だけいれたフィールドを追加し、sourceから取得することで、DBアクセスを不要にしました。

レスポンスをgzip圧縮

選択肢データなどテキストなので圧縮することで大きく通信量を減らせます。 後述するCloud CDNを通すのも可能ですが、NestJSのドキュメントどおりの設定で簡単にでき今のところ負荷も問題なさそうなので追加。

Compression | NestJS - A progressive Node.js framework

GraphQLはPOSTなのでCDNキャッシュがやりづらいのもあり。Nuxt側でキャッシュするのもありな気もしてる。

クラウド側 (GCP)

Cloud CDNで静的ファイルを圧縮して配信する

Nuxt側のビルド時にgzip生成も試しましたが、ビルド時間が長くなるなど支障もあったので、こちらを利用。 ポチポチだけでできました。

動的圧縮を有効にする  |  Cloud CDN  |  Google Cloud

非同期読み込み+スケルト

CLSを防ぎつつ、ページ表示速度を上げるため、ファーストビューに入らず、取得にも時間がかかるようなコンテンツはサーバー側で処理させず、読込前後で高さが変わらないようにスケルトンローディングを表示するようにしました

サイト内リンク系はSEO的にSSRさせたいけど、結構重い処理もあったりして悩みどころ。

Google Cloud Run Jobsをコピーする

GoogleのCloud Runでタスクを実行できるジョブ(Jobs)だけど、まだプレビュー版なせいか、Cloudコンソールでサービス側にあるコピーボタンがまだジョブ側にはない(2023年2月14日時点) ジョブの作成  |  Cloud Run のドキュメント  |  Google Cloud

2023年9月26日更新:2023年9月時点でalpha無しに追加されてるのでalphaを消しました

サービス

ジョブ

シンプルなやつなら問題ないけど、環境変数が大量にあったり、シークレットを使ってると手作業でのコピーは非常につらいので、gcloudコマンドでなんとかやる方法をメモ。

ローカルのセットアップは面倒なのでCloud Shellで行う。

バージョン

$ gcloud version
Google Cloud SDK 416.0.0
alpha 2023.01.30

1,コピー元となる既存のジョブ情報をyamlで書き出す

必要なdescribeコマンドがalphaにしかないのでalphaを使う 追記:2023年9月時点で追加されてるのでalpha不要

途中リージョンを聞かれたら指定したコピー元jobのものを指定する

そのまま>でファイルに書き出す

$ gcloud run jobs describe --format=yaml your-from-job-name --region=your-region > your-from-job-name_20230214.yml

2, 新しいjobを作っておく

Cloudコンソールでも、コマンドでもいいので作っておく。

今回はCloudコンソールでイメージ、ジョブ名、リージョンだけ指定して作成。

後述のreplaceコマンドでもいっしょにできるかもしれない。

3, 新しいjob用yamlを作成

1で作ったファイルをわかりやすい名前にコピーする

cp your-from-job-name_20230214.yml your-new-job-name_20230214.yml

vimでいじる

vim your-new-job-name_20230214.yml

replaceで使えなさそうなmetadataのname以外とstatusを消して、apiVersion, kind, metadataのname, specだけにする。

4, 作ったyamlでreplaceする

yamlが使えるgcloud run jobs replaceを使う

--helpはこんな感じ

NAME
    gcloud run jobs replace - create or replace a job from a YAML job
        specification

SYNOPSIS
    gcloud run jobs replace FILE [--async] [--region=REGION]
        [GCLOUD_WIDE_FLAG ...]

DESCRIPTION
    Creates or replaces a job from a YAML job specification.

EXAMPLES
    To replace the specification for a job defined in myjob.yaml

        $ gcloud run jobs replace myjob.yaml

POSITIONAL ARGUMENTS
     FILE
        The absolute path to the YAML file with a Cloud Run job definition for
        the job to update or create.

FLAGS
     --async
        Return immediately, without waiting for the operation in progress to
        complete.

     --region=REGION
        Region in which the resource can be found. Alternatively, set the
        property [run/region].

2のリージョン、3のyamlファイルを指定して実行する

gcloud run jobs replace --region=your-region your-new-job-name_20230214.yml 

完了したらコンソールで環境変数やらいろいろできてるか確認したり、コマンドを書き換えるなど必要なのをポチポチして完成。