English
VitePress Blog Instant Indexing: Bing IndexNow + GitHub Actions Automation in Practice

Recently, I have been consulting Bing Webmaster Tools While in the background, the system pushed an optimization suggestion to me:
Learn how IndexNow can improve your website's visibility in search engines with a simple 5-minute setup
Looking at the backend data, although total impressions were 5.6K, clicks were only 202, and the data curve fluctuates greatly. This indicates that the website's new content has not been crawled by search engines in a timely manner. Because my blog is based on VitePress The pure static site I built did not have a ready-made plugin ecosystem like WordPress, so I decided to do it myself and implement automatic indexing through API pushes and a CI workflow.
Today, let's get hands-on with how to integrate into your VitePress blog.IndexNow, delivering a silky-smooth experience where content is indexed as soon as it is published.
What is IndexNow?
In the traditional SEO process, after we publish an article, we can only passively wait for search engine “spider” crawlers to periodically come and crawl it (which may take days to weeks).
IndexNow It is an open-source protocol jointly launched by Microsoft Bing and Yandex. Its core philosophy is:Active push When your website content is updated, you immediately notify search engines through the API, and after receiving the notification, the search engines will process it as soon as possible.
Simply put:turned "waiting for the courier" into "sending via SF Express Priority".
Step 1: Generate and deploy API Key
The first step is always to obtain a dedicated Key in Bing Webmaster Tools and verify website ownership.
Login Bing Webmaster Tools.
Find and click in the left menu IndexNow.
The system will generate an API Key string for you (for example:
74c02e84c5fe401bb1768e0eae41deb6).Core verification steps (VitePress hands-on):
Your Key needs to be directly accessible from the public internet:
https://fishfinal.com/74c02e84c5fe401bb1768e0eae41deb6.txt.Since the output generated after VitePress build is in
.vitepress/dist, you only need to74c02e84c5fe401bb1768e0eae41deb6.txtPut the file into yourdocs/public/In the directory.The file content only needs to contain this Key string itself (note: do not include a line break).
Rebuild and deploy your VitePress site, and confirm in the browser that the link is accessible; this means verification has passed.
Step 2: Basic solution (create a new single-file Action)
If you just want to "get it running quickly" and don't have a complex deployment pipeline (such as automatic hosting via Vercel/Netlify), you can create a separate indexnow.yml.
1. Write a basic push script
at scripts/indexnow.mjs Write the following code in it. This version is simple enough: get the changed files and push directly:
javascript
// ====== Configuration Section ======
const HOST = 'fishfinal.com'
const KEY = '74c02e84c5fe401bb1768e0eae41deb6' // Replace with your API Key
const KEY_LOCATION = `https://${HOST}/${KEY}.txt`
const ENDPOINT = 'https://api.indexnow.org/indexnow'
// ====================
const changedFiles = process.env.CHANGED_FILES
? process.env.CHANGED_FILES.split(' ').filter(Boolean)
: []
// Filter out the .md files under the docs/posts directory and convert them to URLs.
const urlList = changedFiles
.filter(file => file.startsWith('docs/posts/') && file.endsWith('.md'))
.map(file => {
let urlPath = file.replace(/^docs/, '').replace(/\.md$/, '.html')
return `https://${HOST}${urlPath}`
})
const uniqueUrls = [...new Set(urlList)]
if (uniqueUrls.length === 0) {
console.log('ℹ️ No new articles, skipping push.')
process.exit(0)
}
console.log('📤 Preparing to push to IndexNow:', uniqueUrls)
const payload = {
host: HOST,
key: KEY,
keyLocation: KEY_LOCATION,
urlList: uniqueUrls,
}
const response = await fetch(ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json; charset=utf-8' },
body: JSON.stringify(payload),
})
if ([200, 202].includes(response.status)) {
console.log(`✅ [${response.status}] Push successful!`)
} else {
console.error(`❌ Push failed, status code: ${response.status}`)
process.exit(1)
}2. Configure basic Action
New .github/workflows/indexnow.yml, monitoring push Event direct execution:
yaml
name: Push to IndexNow
on:
push:
branches: [ master ]
jobs:
indexnow:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Get changed files
run: |
FILES=$(git diff --name-only HEAD^ HEAD --diff-filter=AM | tr '\n' ' ')
echo "CHANGED_FILES=$FILES" >> $GITHUB_ENV
- name: Push to IndexNow
run: node scripts/indexnow.mjs
env:
CHANGED_FILES: ${{ env.CHANGED_FILES }}⚠️ Drawbacks: If your server/CDN hasn't been updated at this time, the Bing crawler will be turned away (404) when it arrives, severely damaging your SEO reputation.
Step 3: Advanced Solution (Integrate into the Existing Deployment Pipeline)
Real Homelab or enterprise deployments often have their own pipelines (such as your existing release-bump.yaml). Must Wait until the deployment is completely successful and the site is accessible online, then notify Bing..
1. Upgrade the ultimate version script (add real liveness probing and status output)
rewrite your scripts/indexnow.mjs, adding three major moats:Strict filtering, real HEAD liveness probing, and output data for email invocation:
javascript
// ====== Configuration Section ======
const HOST = 'fishfinal.com'
const KEY = '74c02e84c5fe401bb1768e0eae41deb6' // Replace with your API Key
const KEY_LOCATION = `https://${HOST}/${KEY}.txt`
const ENDPOINT = 'https://api.indexnow.org/indexnow'
// Strictly limit to only pushing articles under the posts directory.
const POSTS_PREFIX = 'docs/posts/'
// ====================
import fs from 'node:fs'
function writeOutput(key, value) {
if (!process.env.GITHUB_OUTPUT) return
fs.appendFileSync(process.env.GITHUB_OUTPUT, `${key}=${value}\n`)
}
function writeUrlsToOutput(urls) {
const html = urls.map(u => `<li><a href="${u}">${u}</a></li>`).join('')
writeOutput('pushed_urls_html', html)
writeOutput('pushed_count', String(urls.length))
}
function writeErrorToOutput(msg) {
writeOutput('error_msg', msg.replace(/\n/g, '<br>'))
}
// ====== 1. Parse filter file ======
const changedFiles = process.env.CHANGED_FILES
? process.env.CHANGED_FILES.split(' ').filter(Boolean)
: []
// Keep only the .md files under docs/posts/, and filter out configuration files, scripts, etc.
const mdFiles = changedFiles.filter(
file => file.startsWith(POSTS_PREFIX) && file.endsWith('.md')
)
if (mdFiles.length === 0) {
console.log('ℹ️ No new or modified Markdown articles under docs/posts/, skipping IndexNow push.')
writeOutput('pushed_count', '0')
process.exit(0)
}
// ====== 2. Convert to full URL ======
let urlList = mdFiles.map(file => {
// docs/posts/seo/xxx.md -> /posts/seo/xxx
let urlPath = file.replace(/^docs/, '').replace(/\.md$/, '.html')
urlPath = urlPath.replace(/\/index$/, '/')
if (urlPath === '') urlPath = '/'
return `https://${HOST}${urlPath}`
})
// Filter out 404 and deduplicate
urlList = [...new Set(urlList.filter(url => !url.includes('/404')))]
// ====== 3. Real availability check (to prevent pushing 404 to Bing) ======
async function verifyUrls(urls) {
const valid = []
console.log('\n🔍 Starting to verify whether the URL is actually accessible (HEAD request):')
for (const url of urls) {
try {
const res = await fetch(url, { method: 'HEAD' })
if (res.ok) {
console.log(` ✅ [${res.status}] ${url}`)
valid.push(url)
} else {
console.warn(` ❌ [${res.status}] ${url} (skipped push)`)
}
} catch (e) {
console.warn(` ❌ Request failed ${url} (skipped push)`)
}
}
return valid
}
// ====== 4. Send request ======
async function main() {
const verifiedUrls = await verifyUrls(urlList)
if (verifiedUrls.length === 0) {
console.log('\n⚠️ All URL validations failed (the website may not be fully deployed yet or there may be CDN caching), terminating push.')
writeErrorToOutput('URL validation failed, cannot access the actual page')
process.exit(1)
}
const payload = {
host: HOST,
key: KEY,
keyLocation: KEY_LOCATION,
urlList: verifiedUrls,
}
// Print the complete Request Body you requested
console.log('\n📤 Full request body about to be pushed to IndexNow:')
console.log('─────────────────────────────────────────')
console.log(JSON.stringify(payload, null, 2))
console.log('─────────────────────────────────────────')
try {
const response = await fetch(ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json; charset=utf-8' },
body: JSON.stringify(payload),
})
const responseText = await response.text()
console.log(`\n📨 Response status code: ${response.status}`)
if (responseText) console.log(`📨 Response content: ${responseText}`)
// Strictly follow the official documentation: only 200 is success.
if (response.status === 200) {
console.log('\n✅ [200 OK] URL submitted successfully!')
writeUrlsToOutput(verifiedUrls)
process.exit(0)
} else if (response.status === 202) {
// Special notes for 202
console.log('\n⚠️ [202 Accepted] Request has been received, queued for processing (Bing will usually complete the crawl asynchronously later).')
writeUrlsToOutput(verifiedUrls)
process.exit(0) // Still considered a successful process.
}
// failure branch
let errMsg = ''
switch (response.status) {
case 400: errMsg = '[400 Bad Request] Invalid request format'; break;
case 403: errMsg = `[403 Forbidden] Invalid key. Please check:\n 1. Is the file accessible via ${KEY_LOCATION}?\n 2. Does the file content exactly match the KEY?`; break;
case 422: errMsg = '[422 Unprocessable Entity] URL does not belong to this host, or Key does not match the protocol schema'; break;
case 429: errMsg = '[429 Too Many Requests] Pushing too frequently'; break;
default: errMsg = `[${response.status}] Unknown status code, response: ${responseText}`;
}
console.error(`\n❌ ${errMsg}`)
writeErrorToOutput(errMsg)
process.exit(1)
} catch (error) {
const errMsg = `Network request exception: ${error.message}`
console.error(`\n❌ ${errMsg}`)
writeErrorToOutput(errMsg)
process.exit(1)
}
}
main()2. Integrate into the existing release pipeline
Do not create a new Action file., directly in your .github/workflows/release-bump.yaml in, find the steps for a successful deployment (such as Execute Remote Deployment), in it after Insert the following steps:
yaml
# ============================================
# 13.5 IndexNow Push (push only after successful deployment to ensure the live site is already on the new version)
# ============================================
- name: Push to IndexNow
id: indexnow
if: success()
continue-on-error: true
env:
CHANGED_FILES: ${{ env.CHANGED_FILES }}
run: |
if [ -z "$CHANGED_FILES" ]; then
echo "ℹ️ No Markdown changes in this release, skipping IndexNow push"
echo "pushed_count=0" >> $GITHUB_OUTPUT
exit 0
fi
node scripts/indexnow.mjs
# ============================================
# 13.6 IndexNow push success email (lists all submitted URLs)
# ============================================
- name: Send IndexNow success email
if: steps.indexnow.outcome == 'success' && steps.indexnow.outputs.pushed_count != '0'
uses: dawidd6/action-send-mail@v3
with:
# ... your SMTP configuration ...
html_body: |
<h2>🚀 IndexNow Push Successful</h2>
<ul>
${{ steps.indexnow.outputs.pushed_urls_html }}
</ul>FAQs and Pitfall Avoidance Guide
Why does it keep returning 403 Forbidden? Go to the browser and visit your
https://域名/Key.txt, ensure the file is in the root directory, and inside it Absolutely must not contain any extra spaces or line breaks..Does the URL include it or not?
.html? This depends on your VitePress configuration. If it is not enabledcleanUrls, must be added in the conversion script.htmlA failed liveness probe usually means the URL was misspelled.Does receiving 202 count as success? Calculate.
200indicates that the synchronization processing was successful,202Indicates it has been received and entered the asynchronous queue; both are fine.
Summary: SEO is a long-distance race, but the starting line must be automated.
After configuration is complete, you can view the push history in the IndexNow menu in the Bing backend. Now, every time your VitePress blog is published, Bing can learn about it within minutes.
⚠️Important reminder: Fast indexing ≠ good rankings. IndexNow can only help you solve the problem of “being seen” (accelerating indexing), while “ranking higher” (improving rankings) still depends on high-quality content and quality backlink building.
Hurry up and give it a try!
Appendix: Complete release-bump.yaml
For ease of comparison, here is the complete set actually used in my blog.release-bump.yaml. It includes version number extraction, build, compression, SCP, remote Nginx deployment, and IndexNow push.
yaml
name: Auto Bump Version on Release
on:
release:
types: [created]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
# 1. Check out the code
- uses: actions/checkout@v4
with:
ref: ${{ github.event.release.target_commitish }}
fetch-depth: 0
# 2. Set up the Node.js environment
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
# 3. Install dependencies
- name: Install dependencies
run: npm ci
# 4. Extract the version number
- name: Extract Version
id: extract-version
run: |
TAG=${GITHUB_REF#refs/tags/}
VERSION=$(echo "$TAG" | sed 's/^v//')
echo "version=$VERSION" >> $GITHUB_OUTPUT
# 5. Extract project name
- name: Extract Project Name
id: project-name
run: |
PROJECT_NAME=$(node -p "require('./package.json').name")
echo "project_name=$PROJECT_NAME" >> $GITHUB_OUTPUT
# 6. Calculate the Markdown changes involved in this release (for IndexNow)
- name: Get changed md files since last release
id: changed-files
run: |
# Disable path escaping to prevent Chinese filenames from being converted to octal.
git config core.quotepath false
CURRENT_TAG=${GITHUB_REF#refs/tags/}
# Get the nearest tag before the current tag (excluding the current tag itself)
PREV_TAG=$(git describe --tags --abbrev=0 "${CURRENT_TAG}^" 2>/dev/null || echo "")
if [ -z "$PREV_TAG" ]; then
echo "ℹ️ First release, unable to compare with the previous tag, skipping IndexNow change calculation"
echo "CHANGED_FILES=" >> $GITHUB_ENV
else
echo "📌 Comparison range: ${PREV_TAG} .. ${CURRENT_TAG}"
FILES=$(git diff --name-only "${PREV_TAG}" "${CURRENT_TAG}" --diff-filter=AM | tr '\n' ' ')
echo "Changed files: ${FILES}"
echo "CHANGED_FILES=${FILES}" >> $GITHUB_ENV
fi
# 7. Update the package.json version number
- name: Update Version
run: |
npm version ${{ steps.extract-version.outputs.version }} --no-git-tag-version
cat package.json | grep version
# 8. Build a VitePress project
- name: Build VitePress
env:
NODE_OPTIONS: "--max-old-space-size=4096"
run: npm run docs:build
# 9. Create a compressed archive
- name: Create Distribution Archive
id: create-archive
run: |
if [ ! -d "docs/.vitepress/dist" ]; then
echo "Error: ./docs/.vitepress/dist/ directory not found!"
exit 1
fi
echo "Contents of ./docs/.vitepress/dist/ directory:"
ls -la ./docs/.vitepress/dist/
PROJECT_NAME="${{ steps.project-name.outputs.project_name }}"
VERSION="${{ steps.extract-version.outputs.version }}"
ARCHIVE_NAME="${PROJECT_NAME}-${VERSION}.tar.gz"
tar -czf ${ARCHIVE_NAME} -C ./docs/.vitepress/dist/ .
echo "Archive created successfully: ${ARCHIVE_NAME}"
echo "archive_name=${ARCHIVE_NAME}" >> $GITHUB_OUTPUT
ls -la *.tar.gz
# 10. Upload to GitHub Release
- name: Upload Release Assets
uses: ncipollo/release-action@v1
with:
artifacts: "./${{ steps.create-archive.outputs.archive_name }}"
allowUpdates: true
omitBody: true
omitName: true
token: ${{ secrets.ACCESS_TOKEN }}
makeLatest: "true"
# 11. Commit version number change
- name: Commit Changes
run: |
git config user.name "GitHub Actions"
git config user.email "[email protected]"
git add package.json package-lock.json
git commit -m "chore(release): bump to ${{ steps.extract-version.outputs.version }} [skip ci]"
git push origin HEAD:${{ github.event.release.target_commitish }}
# ============================================
# Build success email
# ============================================
- name: Send build success email
if: success()
uses: dawidd6/action-send-mail@v3
with:
server_address: smtp.gmail.com
server_port: 587
username: ${{ vars.GMAIL_SMTP_USER }}
password: ${{ secrets.GMAIL_SMTP_PASSWORD }}
subject: "✅ VitePress Build Successful - ${{ github.repository }}"
to: ${{ vars.NOTIFY_EMAILS }}
from: "GitHub Actions <${{ vars.GMAIL_SMTP_USER }}>"
body: |
🎉 VitePress project built successfully!
Repository: ${{ github.repository }}
Version: ${{ steps.extract-version.outputs.version }}
Triggered by: ${{ github.actor }}
Time: ${{ github.event.repository.updated_at }}
Status: ✅ Success
Details: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
# ============================================
# Build failure email
# ============================================
- name: Send build failure email
if: failure()
uses: dawidd6/action-send-mail@v3
with:
server_address: smtp.gmail.com
server_port: 587
username: ${{ vars.GMAIL_SMTP_USER }}
password: ${{ secrets.GMAIL_SMTP_PASSWORD }}
❌ VitePress Build Failed - ${{ github.repository }}
to: ${{ vars.NOTIFY_EMAILS }}
from: "GitHub Actions <${{ vars.GMAIL_SMTP_USER }}>"
body: |
🚨 VitePress project build failed, please check immediately!
Repository: ${{ github.repository }}
Version: ${{ steps.extract-version.outputs.version }}
Triggered by: ${{ github.actor }}
Time: ${{ github.event.repository.updated_at }}
Status: ❌ Failed
Details: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
Please click the link above to view detailed error information.
# ============================================
# Deployment phase begins
# ============================================
# 12. SCP transfer to the server
- name: Deploy to Server (SCP)
uses: appleboy/[email protected]
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
port: ${{ secrets.DEPLOY_PORT || 22 }}
source: "./${{ steps.create-archive.outputs.archive_name }}"
target: "/tmp/${{ steps.project-name.outputs.project_name }}-build-${{ steps.extract-version.outputs.version }}/"
strip_components: 1
# 13. SSH Execute Remote Deployment
- name: Execute Remote Deployment
uses: appleboy/[email protected]
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
port: ${{ secrets.DEPLOY_PORT || 22 }}
script: |
set -e
PROJECT_NAME="${{ steps.project-name.outputs.project_name }}"
VERSION="${{ steps.extract-version.outputs.version }}"
ARCHIVE_NAME="${PROJECT_NAME}-${VERSION}.tar.gz"
DEPLOY_DIR="/var/www/${PROJECT_NAME}"
BUILD_DIR="/tmp/${PROJECT_NAME}-build-${VERSION}"
echo "=========================================="
echo "Deploying ${PROJECT_NAME} version ${VERSION}"
echo "=========================================="
# 1. Create a temporary directory and extract
mkdir -p ${BUILD_DIR}
echo "📦 Extracting ${ARCHIVE_NAME}..."
tar -xzf "/tmp/${PROJECT_NAME}-build-${VERSION}/${ARCHIVE_NAME}" -C ${BUILD_DIR}
# 2. Back up the current version
if [ -d "$DEPLOY_DIR" ]; then
BACKUP_DIR="/var/www/${PROJECT_NAME}-backup-$(date +%Y%m%d_%H%M%S)"
echo "📦 Creating backup: ${BACKUP_DIR}"
cp -r "$DEPLOY_DIR" "$BACKUP_DIR"
# Keep the last 5 backups
ls -t /var/www/${PROJECT_NAME}-backup-* 2>/dev/null | tail -n +6 | xargs rm -rf || true
else
📁 Creating deployment directory: ${DEPLOY_DIR}
mkdir -p "$DEPLOY_DIR"
fi
# 3. Deploy new files
echo "📤 Deploying files..."
rm -rf ${DEPLOY_DIR}/*
cp -r ${BUILD_DIR}/* ${DEPLOY_DIR}/
# 4. Set permissions
chown -R www-data:www-data ${DEPLOY_DIR} || chown -R $(whoami):$(whoami) ${DEPLOY_DIR}
chmod -R 755 ${DEPLOY_DIR}
# 5. Restart the NGINX service
echo "🔄 Reloading nginx service..."
if command -v nginx &> /dev/null; then
nginx -s reload || systemctl reload nginx || echo "⚠️ Nginx reload skipped (service not found)"
else
echo "⚠️ Nginx not found, skipping reload"
fi
# 6. Clean up temporary files
echo "🧹 Cleaning up..."
rm -rf ${BUILD_DIR}
rm -rf "/tmp/${PROJECT_NAME}-build-${VERSION}"
# 7. Display deployment results
echo "=========================================="
echo "✅ Deployment completed successfully!"
📍 Deployed to: ${DEPLOY_DIR}
echo "📌 Version: ${VERSION}"
echo "=========================================="
# ============================================
# 13.5 IndexNow Push (push only after successful deployment to ensure the live site is already on the new version)
# ============================================
- name: Push to IndexNow
id: indexnow
if: success()
continue-on-error: true
env:
CHANGED_FILES: ${{ env.CHANGED_FILES }}
run: |
if [ -z "$CHANGED_FILES" ]; then
echo "ℹ️ No Markdown changes in this release, skipping IndexNow push"
echo "pushed_count=0" >> $GITHUB_OUTPUT
exit 0
fi
node scripts/indexnow.mjs
# ============================================
# 13.6 IndexNow push success email (lists all submitted URLs)
# ============================================
- name: Send IndexNow success email
if: steps.indexnow.outcome == 'success' && steps.indexnow.outputs.pushed_count != '0'
uses: dawidd6/action-send-mail@v3
with:
server_address: smtp.gmail.com
server_port: 587
username: ${{ vars.GMAIL_SMTP_USER }}
password: ${{ secrets.GMAIL_SMTP_PASSWORD }}
subject: 🚀 IndexNow push successful (${{ steps.extract-version.outputs.version }}) - ${{ steps.indexnow.outputs.pushed_count }} URLs
to: ${{ vars.NOTIFY_EMAILS }}
from: "GitHub Actions <${{ vars.GMAIL_SMTP_USER }}>"
html_body: |
<h2>🚀 IndexNow Push Successful</h2>
<p>The following <strong>${{ steps.indexnow.outputs.pushed_count }}</strong> URLs have been successfully submitted to Bing and are awaiting crawling:</p>
<ul>
${{ steps.indexnow.outputs.pushed_urls_html }}
</ul>
<hr>
<p><strong>Repository:</strong>${{ github.repository }}</p>
<p><strong>Version:</strong>${{ steps.extract-version.outputs.version }}</p>
<p><strong>Triggered by:</strong>${{ github.actor }}</p>
<p><strong>Details:</strong><a href="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}">View Action run record</a></p>
# ============================================
# 13.7 IndexNow Push Failure Email (with error reasons and troubleshooting tips)
# ============================================
- name: Send IndexNow failure email
if: steps.indexnow.outcome == 'failure'
uses: dawidd6/action-send-mail@v3
with:
server_address: smtp.gmail.com
server_port: 587
username: ${{ vars.GMAIL_SMTP_USER }}
password: ${{ secrets.GMAIL_SMTP_PASSWORD }}
⚠️ IndexNow push failed (${{ steps.extract-version.outputs.version }}) - needs investigation
to: ${{ vars.NOTIFY_EMAILS }}
from: "GitHub Actions <${{ vars.GMAIL_SMTP_USER }}>"
html_body: |
<h2>⚠️ IndexNow Push Failed</h2>
<p>This release has been <strong>successfully deployed</strong>, but the IndexNow push failed. Bing will not receive new content notifications for now, and you need to manually investigate.</p>
<h3>Error Message</h3>
<pre style="background:#f5f5f5;padding:12px;border-radius:4px;">${{ steps.indexnow.outputs.error_msg }}</pre>
<h3>Common troubleshooting directions</h3>
<ul>
<li><strong>403</strong>: Check whether <code>docs/public/74c02e84c5fe401bb1768e0eae41deb6.txt</code> is publicly accessible over the internet and whether its content exactly matches the Key (no trailing newline)</li>
<li><strong>422</strong>: Check whether the URL generated by the script belongs to the <code>fishfinal.com</code> domain</li>
<li><strong>429</strong>: Push too frequent, manually rerun later</li>
<li><strong>Network error</strong>: Check the GitHub Actions egress network, or rerun later</li>
</ul>
<hr>
<p><strong>Repository:</strong>${{ github.repository }}</p>
<p><strong>Version:</strong>${{ steps.extract-version.outputs.version }}</p>
<p><strong>Triggered by:</strong>${{ github.actor }}</p>
<p><strong>Details:</strong><a href="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}">View Action run record</a></p>
# 14. Clean up local compressed packages
- name: Cleanup Local Archive
if: always()
run: |
rm -f ${{ steps.create-archive.outputs.archive_name }} 2>/dev/null || true
echo "🧹 Local archive cleaned up"
# ============================================
# Deployment successful email
# ============================================
- name: Send deploy success email
if: success()
uses: dawidd6/action-send-mail@v3
with:
server_address: smtp.gmail.com
server_port: 587
username: ${{ vars.GMAIL_SMTP_USER }}
password: ${{ secrets.GMAIL_SMTP_PASSWORD }}
subject: ✅ VitePress Deployment Successful - ${{ github.repository }}
to: ${{ vars.NOTIFY_EMAILS }}
from: "GitHub Actions <${{ vars.GMAIL_SMTP_USER }}>"
body: |
🎉 VitePress project deployed successfully!
Repository: ${{ github.repository }}
Version: ${{ steps.extract-version.outputs.version }}
Triggered by: ${{ github.actor }}
Time: ${{ github.event.repository.updated_at }}
Status: ✅ Success
🌐 Visit URL: ${{ vars.DEPLOY_WEBSITE_DOMAIN }}
Details: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
# ============================================
# Deployment failure email
# ============================================
- name: Send deploy failure email
if: failure()
uses: dawidd6/action-send-mail@v3
with:
server_address: smtp.gmail.com
server_port: 587
username: ${{ vars.GMAIL_SMTP_USER }}
password: ${{ secrets.GMAIL_SMTP_PASSWORD }}
subject: "❌ VitePress deployment failed - ${{ github.repository }}"
to: ${{ vars.NOTIFY_EMAILS }}
from: "GitHub Actions <${{ vars.GMAIL_SMTP_USER }}>"
body: |
🚨 VitePress project deployment failed, please check immediately!
Repository: ${{ github.repository }}
Version: ${{ steps.extract-version.outputs.version }}
Triggered by: ${{ github.actor }}
Time: ${{ github.event.repository.updated_at }}
Status: ❌ Failed
Details: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
Please click the link above to view detailed error information.