How to automate multilingual SEO validation with GitHub Actions before Cloudflare Pages deployment? #209519
Replies: 4 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: https://github.com/JustinBeckwith/linkinator https://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! |
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