How to automate multilingual SEO validation with GitHub Actions before Cloudflare Pages deployment? #209519
Replies: 5 comments
|
A good approach is to run all SEO checks in GitHub Actions before allowing a merge to main. |
Validating Multilingual SEO Before DeploymentHi @sargutportfolio, Validating multilingual SEO and static assets before reaching production is best handled with a two-tier pipeline:
Recommended Open-Source Tooling
1. Custom Pre-Deployment SEO AuditorA dedicated Node.js script using Create import fs from 'node:fs';
import path from 'node:path';
import * as cheerio from 'cheerio';
import { globSync } from 'glob';
const distDir = path.resolve('out'); // Adjust to your static output folder (e.g. dist, build, out)
const htmlFiles = globSync(`${distDir}/**/*.html`);
let hasError = false;
console.log(`Auditing SEO across ${htmlFiles.length} HTML files...`);
for (const file of htmlFiles) {
const content = fs.readFileSync(file, 'utf-8');
const $ = cheerio.load(content);
// 1. Validate Canonical URL
const canonical = $('link[rel="canonical"]').attr('href');
if (!canonical) {
console.error(`❌ [Missing Canonical] in ${file}`);
hasError = true;
}
// 2. Validate Hreflang Tags (Reciprocal check)
const hreflangs = {};
$('link[rel="alternate"][hreflang]').each((_, el) => {
hreflangs[$(el).attr('hreflang')] =$(el).attr('href');
});
const requiredLocales = ['tr', 'en', 'ar', 'ru', 'x-default'];
for (const locale of requiredLocales) {
if (!hreflangs[locale]) {
console.warn(`⚠️ [Missing hreflang="${locale}"] in ${file}`);
}
}
// 3. Validate Images (alt tags and explicit dimensions)
$('img').each((_, el) => {
const alt = $(el).attr('alt');
const width = $(el).attr('width');
const height = $(el).attr('height');
const src = $(el).attr('src');
if (alt === undefined) {
console.error(`❌ [Missing alt attribute] on image ${src} in ${file}`);
hasError = true;
}
if (!width || !height) {
console.warn(`⚠ [Missing dimensions / CLS risk] on image ${src} in ${file}`);
}
});
// 4. Validate JSON-LD Schema syntax
$('script[type="application/ld+json"]').each((_, el) => {
try {
JSON.parse($(el).text());
} catch (e) {
console.error(`❌ [Malformed JSON-LD Syntax] in ${file}: ${e.message}`);
hasError = true;
}
});
}
if (hasError) {
process.exit(1);
} else {
console.log('✅ All pre-deployment SEO audits passed.');
}2. GitHub Actions Gatekeeper WorkflowThis workflow triggers on pull requests targeting Create name: Multilingual SEO & Quality Gatekeeper
on:
pull_request:
branches: [main]
jobs:
seo-and-performance-audit:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install Dependencies
run: npm ci
- name: Build Static Site
run: npm run build # Ensure this outputs to ./out or ./dist
- name: Run Custom SEO & Hreflang Auditor
run: node .github/scripts/seo-audit.mjs
- name: Check Broken Internal Links & Sitemaps with Lychee
uses: lycheeverse/lychee-action@v2
with:
args: --offline --include-fragments ./out/**/*.html
fail: true
- name: Audit Mobile Performance & SEO via Lighthouse CI
run: |
npm install -g @lhci/cli
lhci autorun
env:
LHCI_BUILD_CONTEXT__CURRENT_HASH: ${{ github.sha }}Create a {
"ci": {
"collect": {
"staticDistDir": "./out",
"numberOfRuns": 1
},
"assert": {
"assertions": {
"categories:seo": ["error", { "minScore": 0.95 }],
"categories:performance": ["warn", { "minScore": 0.85 }],
"document-title": "error",
"html-has-lang": "error",
"valid-lang": "error"
}
}
}
}3. Enforcing Merges & Keeping Secrets Safe
If this provides a solid foundation for your multilingual validation pipeline, please consider marking this reply as Answered so others in the community can find it easily. |
|
I'd keep the validation separate from the Cloudflare deployment itself. The earlier suggestions cover the right tools, but there are two details I'd pay particular attention to. First, checking that an For example, if I'd build a small page index from the generated HTML and check each relationship: // pages is a Map keyed by absolute canonical URL
for (const [url, page] of pages) {
if (page.canonical !== url) {
throw new Error(`Incorrect canonical: ${url}`);
}
for (const [lang, alternateUrl] of Object.entries(page.hreflang)) {
if (lang === "x-default") continue;
const target = pages.get(alternateUrl);
if (!target || target.hreflang[page.locale] !== url) {
throw new Error(`Broken hreflang relationship: ${url} -> ${alternateUrl}`);
}
}
}That assumes the page index contains the generated URLs, each page's locale, canonical URL and alternate links. I wouldn't require all four languages on every page unless every page actually has four translations. Second, a passing GitHub Actions check and a Cloudflare preview deployment are two different things. Cloudflare Pages can deploy preview branches automatically, even while a PR's checks are failing. For a private project, I'd use this sequence:
One more small distinction: parsing JSON-LD confirms valid JSON, not necessarily valid Schema.org properties. I'd report those as separate checks. This gives you a predictable PR gate without granting Cloudflare credentials to the validation job or touching production before review. |
|
Hi! I'd recommend running SEO checks on the generated static files through GitHub Actions before merging anything into your production branch. Here's how I'd approach it:
For a basic GitHub Actions workflow, you can start with: name: SEO Checks
on: [pull_request]
jobs:
seo:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run build
- run: npx html-validate "dist/**/*.html"
- run: npx linkinator ./dist --recurseReplace Cloudflare Pages can automatically generate preview deployments for pull requests without affecting production. You can also configure required status checks on your main branch, provided your GitHub plan supports branch protection for private repositories. Useful resources: http://github.com/JustinBeckwith/linkinator http://github.com/GoogleChrome/lighthouse-ci https://developers.cloudflare.com/pages/configuration/preview-deployments/ One important detail: preview deployments can happen before CI finishes. Required checks will protect merging, but preventing production deployment also requires controlling how your production branch is updated. Hope this helps! |
|
Hi everyone,
Thank you for the detailed, practical guidance. We used your
recommendations to add a read-only GitHub Actions SEO audit to both
the Serdar Armin Sargut and Duru Mualla Sargut portfolio projects,
adapting it to each site's actual static build structure instead of
copying the example npm/dist commands directly.
The audit now checks sitemap URLs, self-referencing canonical URLs,
reciprocal hreflang relationships where applicable, JSON-LD syntax,
robots directives, and image dimension warnings. Our latest checks
passed for 90 sitemap pages on Serdar's site and 5 on Duru's site,
with zero failures and zero warnings. After review and explicit
approval, both changes were merged, and the Cloudflare production
deployments were confirmed successful.
We are also keeping technical SEO validation separate from deployment
permissions and real search-engine indexing, as you advised. Your
suggestions genuinely helped us put a safer, repeatable process in
place.
Thanks again to everyone who contributed!
…On Sat, 10 Oct 2026 03:52:05 -0700, Navid Zaman Khan ***@***.***> wrote:
Hi! I'd recommend running SEO checks on the generated static files through GitHub Actions before merging anything into your production branch.
Here's how I'd approach it:
- Use HTML-Validate to catch invalid HTML, missing image attributes, and structural issues.
- Use Linkinator to detect broken internal links across generated pages.
- Write a small Node.js script to check that every language version has a self-referencing canonical URL and reciprocal hreflang links.
- Validate JSON-LD syntax and required structured-data properties. Also check that sitemap.xml and robots.txt reference the correct URLs.
- Use Lighthouse CI to catch mobile performance and accessibility regressions.
For a basic GitHub Actions workflow, you can start with:
name: SEO Checks
on: [pull_request]
jobs:
seo:
runs-on: ubuntu-latest
steps:
- uses: ***@***.***
- uses: ***@***.***
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run build
- run: npx html-validate "dist/**/*.html"
- run: npx linkinator ./dist --recurse
Replace dist with your actual build directory.
Cloudflare Pages can automatically generate preview deployments for pull requests without affecting production.
You can also configure required status checks on your main branch, provided your GitHub plan supports branch protection for private repositories.
Useful resources:
http://github.com/JustinBeckwith/linkinator
https://html-validate.org/
http://github.com/GoogleChrome/lighthouse-ci
https://developers.cloudflare.com/pages/configuration/preview-deployments/
One important detail: preview deployments can happen before CI finishes. Required checks will protect merging, but preventing production deployment also requires controlling how your production branch is updated.
Hope this helps!
—
Reply to this email directly, [view it on GitHub](#209519?email_source=notifications&email_token=CM2C5CPK7Q2RY2EJM4OZAUT5TIIFLA5CNFSNUABIM5UWIORPF5TWS5BNNB2WEL2ENFZWG5LTONUW63SDN5WW2ZLOOQXTCOBYGQ4TMNBSUZZGKYLTN5XKO3LFNZ2GS33OUVSXMZLOOSWGM33PORSXEX3DNRUWG2Y#discussioncomment-18849642), or [unsubscribe](http://github.com/notifications/unsubscribe-auth/CM2C5CIPPUWQPW2QS7QR7EL5TIIFLAVCNFSNUABIKJSXA33TNF2G64TZHMZTAMJVG4ZTGNBUHNCGS43DOVZXG2LPNY5TCMBZGUYDOMZQUF3AE).
Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for [iOS](http://github.com/notifications/mobile/ios/CM2C5CITOZYKNA4KCUPTX3L5TIIFLA5CNFSNUABIM5UWIORPF5TWS5BNNB2WEL2ENFZWG5LTONUW63SDN5WW2ZLOOQXTCOBYGQ4TMNBSUZZGKYLTN5XKO3LFNZ2GS33OUVSXMZLOOSVGM33PORSXEX3JN5ZQ) and [Android](http://github.com/notifications/mobile/android/CM2C5CLB3ACUV6OX7KKCONL5TIIFLA5CNFSNUABIM5UWIORPF5TWS5BNNB2WEL2ENFZWG5LTONUW63SDN5WW2ZLOOQXTCOBYGQ4TMNBSUZZGKYLTN5XKO3LFNZ2GS33OUVSXMZLOOSXGM33PORSXEX3BNZSHE33JMQ). Download it today!
You are receiving this because you were mentioned.
|
Uh oh!
There was an error while loading. Please reload this page.
🏷️ Discussion Type
Question
Body
Hello GitHub Community,
I'm managing a multilingual portfolio website hosted on Cloudflare Pages, with its source code stored in a private GitHub repository.
The website supports Turkish, English, Arabic and Russian. We want to improve code quality and prevent deployment regressions.
I would appreciate practical programming guidance on implementing GitHub Actions checks for:
Which free, maintained open-source tools or GitHub Actions workflows would you recommend? A minimal example workflow or implementation approach would be especially helpful.
We would like to test everything in a private preview environment and avoid any changes to production until the results have been reviewed.
The repository is private, so please suggest approaches that do not require making its contents public.
This question was drafted with assistance from ChatGPT (OpenAI, GPT-5.6 Sol) and reviewed before posting.
Thank you.
Guidelines
All reactions