fix(deploy): ISRキャッシュ削除を pm2 reload の後に実行しレースを解消 #157
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Deploy to VPS | |
| on: | |
| push: | |
| branches: [main] | |
| workflow_dispatch: {} | |
| concurrency: | |
| group: deploy-vps | |
| cancel-in-progress: false | |
| jobs: | |
| deploy: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 40 | |
| needs: [] | |
| defaults: | |
| run: | |
| working-directory: site | |
| env: | |
| DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }} | |
| DEPLOY_USER: ${{ secrets.DEPLOY_USER }} | |
| DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # 218ツール×17言語=約7,800ページのSSGでランナーのディスクが枯渇する(ENOSPC)。 | |
| # ビルド前に未使用のプリインストールSDKを削除して約30GB確保する。 | |
| - name: Free disk space | |
| working-directory: / | |
| run: | | |
| echo "Before:"; df -h / | tail -1 | |
| sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/.ghcup \ | |
| /usr/local/lib/android /usr/local/share/boost \ | |
| /usr/local/share/powershell /usr/share/swift "$AGENT_TOOLSDIRECTORY" || true | |
| sudo docker image prune -af || true | |
| echo "After:"; df -h / | tail -1 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: "24" | |
| cache: "npm" | |
| cache-dependency-path: site/package-lock.json | |
| - name: Install dependencies | |
| run: npm ci --no-fund --no-audit | |
| - name: Type check | |
| run: npm run typecheck | |
| - name: i18n / SEO audits | |
| run: | | |
| npm run audit:i18n | |
| npm run audit:seo | |
| - name: Unit tests | |
| run: npm test | |
| - name: Production build (standalone) | |
| run: npm run build | |
| env: | |
| NEXT_PUBLIC_SITE_URL: https://toolify365.com | |
| NEXT_PUBLIC_ORG_NAME: Toolify | |
| NEXT_PUBLIC_CONTACT_EMAIL: app.develop.sk@gmail.com | |
| NEXT_PUBLIC_ADSENSE_CLIENT: ca-pub-4927026308242118 | |
| NEXT_PUBLIC_FC_ID: "" | |
| NEXT_PUBLIC_BING_SITE_VERIFICATION: ${{ secrets.BING_SITE_VERIFICATION }} | |
| NEXT_PUBLIC_YANDEX_SITE_VERIFICATION: ${{ secrets.YANDEX_SITE_VERIFICATION }} | |
| # AdSense ad unit slot IDs (set after AdSense approval; empty = placeholder shown) | |
| NEXT_PUBLIC_ADSENSE_SLOT_BANNER: ${{ secrets.ADSENSE_SLOT_BANNER }} | |
| NEXT_PUBLIC_ADSENSE_SLOT_INARTICLE: ${{ secrets.ADSENSE_SLOT_INARTICLE }} | |
| NEXT_PUBLIC_ADSENSE_SLOT_STICKY: ${{ secrets.ADSENSE_SLOT_STICKY }} | |
| NEXT_PUBLIC_ADSENSE_SLOT_BELOWRESULT: ${{ secrets.ADSENSE_SLOT_BELOWRESULT }} | |
| NEXT_PUBLIC_GA_ID: ${{ secrets.GA_ID }} | |
| NEXT_PUBLIC_CLARITY_PROJECT_ID: ${{ secrets.CLARITY_PROJECT_ID }} | |
| # 楽天市場 価格比較ツール (/api/price-search) — サーバーサイドのみ | |
| RAKUTEN_APP_ID: ${{ secrets.RAKUTEN_APP_ID }} | |
| RAKUTEN_ACCESS_KEY: ${{ secrets.RAKUTEN_ACCESS_KEY }} | |
| RAKUTEN_AFFILIATE_ID: ${{ secrets.RAKUTEN_AFFILIATE_ID }} | |
| # 楽天アプリ登録のApplication URLと一致させる | |
| RAKUTEN_ORIGIN_URL: ${{ secrets.RAKUTEN_ORIGIN_URL }} | |
| # Amazon アソシエイト (承認後に設定) | |
| NEXT_PUBLIC_AMAZON_PARTNER_TAG: ${{ secrets.AMAZON_PARTNER_TAG }} | |
| - name: Set up SSH | |
| run: | | |
| mkdir -p ~/.ssh | |
| echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/deploy_key | |
| chmod 600 ~/.ssh/deploy_key | |
| ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts 2>/dev/null | |
| - name: Write runtime env file for VPS | |
| env: | |
| RAKUTEN_APP_ID: ${{ secrets.RAKUTEN_APP_ID }} | |
| RAKUTEN_ACCESS_KEY: ${{ secrets.RAKUTEN_ACCESS_KEY }} | |
| RAKUTEN_AFFILIATE_ID: ${{ secrets.RAKUTEN_AFFILIATE_ID }} | |
| RAKUTEN_ORIGIN_URL: ${{ secrets.RAKUTEN_ORIGIN_URL }} | |
| run: | | |
| printf 'RAKUTEN_APP_ID=%s\nRAKUTEN_ACCESS_KEY=%s\nRAKUTEN_AFFILIATE_ID=%s\nRAKUTEN_ORIGIN_URL=%s\n' \ | |
| "$RAKUTEN_APP_ID" "$RAKUTEN_ACCESS_KEY" "$RAKUTEN_AFFILIATE_ID" "$RAKUTEN_ORIGIN_URL" \ | |
| > .next/standalone/.env.local | |
| - name: rsync standalone artifacts to VPS | |
| run: | | |
| rsync -az --delete \ | |
| -e "ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=accept-new" \ | |
| .next/standalone/ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/" | |
| rsync -az --delete \ | |
| -e "ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=accept-new" \ | |
| .next/static/ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/.next/static/" | |
| rsync -az --delete \ | |
| -e "ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=accept-new" \ | |
| public/ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/public/" | |
| - name: Reload toolify on VPS via PM2 (zero-downtime) | |
| run: | | |
| ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=accept-new \ | |
| "$DEPLOY_USER@$DEPLOY_HOST" \ | |
| "pm2 reload toolify --update-env && pm2 save" | |
| # キャッシュ削除は reload の「後」に行う。先に消すと、reload 中の | |
| # 旧コードのワーカーがリクエストを処理して旧HTMLを再キャッシュし、 | |
| # s-maxage が長いため stale が固着する(レース)。新ワーカー稼働後に | |
| # 消すことで、次回の再生成が必ず新コードで行われる。 | |
| - name: Clear ISR / full-route cache on VPS (after reload) | |
| run: | | |
| ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=accept-new \ | |
| "$DEPLOY_USER@$DEPLOY_HOST" \ | |
| "rm -rf \"$DEPLOY_PATH/.next/cache/\" && pm2 reload toolify --update-env && echo 'cache cleared'" | |
| - name: Smoke test live URL | |
| run: | | |
| set -e | |
| for i in 1 2 3 4 5; do | |
| if curl -sSfL -o /dev/null -w "%{http_code}" --max-time 15 https://toolify365.com/en/tools/bmi-calculator | grep -q 200 && curl -sSf --max-time 10 https://toolify365.com/feed-ja.xml | grep -q "<rss"; then | |
| echo "✓ Live URL returning 200" | |
| exit 0 | |
| fi | |
| echo "Attempt $i failed, retrying in 5s..." | |
| sleep 5 | |
| done | |
| echo "✗ Live URL not returning 200 after 5 attempts" | |
| exit 1 | |
| - name: Cleanup SSH key | |
| if: always() | |
| run: rm -f ~/.ssh/deploy_key |