diff --git a/.github/scripts/check-seo-images.cjs b/.github/scripts/check-seo-images.cjs
new file mode 100755
index 00000000000000..969a868b6d789d
--- /dev/null
+++ b/.github/scripts/check-seo-images.cjs
@@ -0,0 +1,155 @@
+#!/usr/bin/env node
+
+const fs = require("fs");
+const path = require("path");
+
+const sourceExtensions = new Set([".js", ".jsx", ".ts", ".tsx", ".mdx"]);
+
+function walk(directory) {
+ if (!fs.existsSync(directory)) return [];
+
+ return fs.readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
+ const absolutePath = path.join(directory, entry.name);
+ return entry.isDirectory() ? walk(absolutePath) : [absolutePath];
+ });
+}
+
+function lineNumberAt(source, index) {
+ return source.slice(0, index).split(/\r?\n/).length;
+}
+
+function findSeoOpeningTags(source) {
+ const tags = [];
+ const startPattern = /])/g;
+
+ for (const startMatch of source.matchAll(startPattern)) {
+ const start = startMatch.index || 0;
+ let quote = null;
+ let escaped = false;
+ let braceDepth = 0;
+
+ for (let index = start + startMatch[0].length; index < source.length; index++) {
+ const character = source[index];
+
+ if (quote) {
+ if (escaped) {
+ escaped = false;
+ } else if (character === "\\") {
+ escaped = true;
+ } else if (character === quote) {
+ quote = null;
+ }
+ continue;
+ }
+
+ if (character === '"' || character === "'" || character === "`") {
+ quote = character;
+ } else if (character === "{") {
+ braceDepth++;
+ } else if (character === "}" && braceDepth > 0) {
+ braceDepth--;
+ } else if (character === ">" && braceDepth === 0) {
+ tags.push({
+ index: start,
+ text: source.slice(start, index + 1),
+ });
+ break;
+ }
+ }
+ }
+
+ return tags;
+}
+
+function findLocalSeoImages(source) {
+ const references = [];
+ const imagePropPattern =
+ /\bimage\s*=\s*(?:"([^"]+)"|'([^']+)'|\{\s*["']([^"']+)["']\s*\})/;
+
+ for (const tag of findSeoOpeningTags(source)) {
+ const imageMatch = tag.text.match(imagePropPattern);
+ if (!imageMatch) continue;
+
+ const imagePath = imageMatch[1] || imageMatch[2] || imageMatch[3];
+ if (/^(?:[a-z][a-z0-9+.-]*:)?\/\//i.test(imagePath)) continue;
+
+ references.push({
+ imagePath,
+ line: lineNumberAt(source, tag.index),
+ });
+ }
+
+ return references;
+}
+
+function validateSeoImages({ repoRoot, sourceRoot, assetRoots }) {
+ const failures = [];
+
+ for (const sourceFile of walk(sourceRoot)) {
+ if (!sourceExtensions.has(path.extname(sourceFile))) continue;
+
+ const source = fs.readFileSync(sourceFile, "utf8");
+ const relativeSource = path.relative(repoRoot, sourceFile);
+
+ for (const reference of findLocalSeoImages(source)) {
+ if (!reference.imagePath.startsWith("/")) {
+ failures.push(
+ `${relativeSource}:${reference.line} invalid local SEO image ${reference.imagePath} (use a root-relative path backed by static or public)`,
+ );
+ continue;
+ }
+
+ const pathname = reference.imagePath.split(/[?#]/, 1)[0];
+ const relativeAsset = path.posix
+ .normalize(pathname)
+ .replace(/^\/+/, "");
+
+ if (relativeAsset.startsWith("../") || path.isAbsolute(relativeAsset)) {
+ failures.push(
+ `${relativeSource}:${reference.line} invalid local SEO image path ${reference.imagePath}`,
+ );
+ continue;
+ }
+
+ const candidates = assetRoots.map((root) => path.join(root, relativeAsset));
+ if (!candidates.some((candidate) => fs.existsSync(candidate))) {
+ failures.push(
+ `${relativeSource}:${reference.line} missing local SEO image ${reference.imagePath} (expected ${path.relative(repoRoot, candidates[0])} or ${path.relative(repoRoot, candidates[1])})`,
+ );
+ }
+ }
+ }
+
+ return failures;
+}
+
+function main() {
+ const repoRoot = path.resolve(__dirname, "..", "..");
+ const sourceRoot = path.join(repoRoot, "src");
+ const assetRoots = [
+ path.join(repoRoot, "static"),
+ path.join(repoRoot, "public"),
+ ];
+ const failures = validateSeoImages({ repoRoot, sourceRoot, assetRoots });
+
+ if (failures.length > 0) {
+ console.error("Local SEO image validation failed:");
+ for (const failure of failures) {
+ console.error(`- ${failure}`);
+ }
+ process.exitCode = 1;
+ return;
+ }
+
+ console.log("All local SEO image references resolve.");
+}
+
+if (require.main === module) {
+ main();
+}
+
+module.exports = {
+ findLocalSeoImages,
+ findSeoOpeningTags,
+ validateSeoImages,
+};
diff --git a/.github/scripts/check-seo-images.test.cjs b/.github/scripts/check-seo-images.test.cjs
new file mode 100644
index 00000000000000..780a2bd963a600
--- /dev/null
+++ b/.github/scripts/check-seo-images.test.cjs
@@ -0,0 +1,88 @@
+const assert = require("node:assert/strict");
+const fs = require("node:fs");
+const os = require("node:os");
+const path = require("node:path");
+const test = require("node:test");
+
+const {
+ findLocalSeoImages,
+ validateSeoImages,
+} = require("./check-seo-images.cjs");
+
+function createFixture() {
+ const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), "seo-images-"));
+ const sourceRoot = path.join(repoRoot, "src");
+ const staticRoot = path.join(repoRoot, "static");
+ const publicRoot = path.join(repoRoot, "public");
+ fs.mkdirSync(sourceRoot, { recursive: true });
+ fs.mkdirSync(staticRoot, { recursive: true });
+ fs.mkdirSync(publicRoot, { recursive: true });
+
+ return {
+ repoRoot,
+ sourceRoot,
+ assetRoots: [staticRoot, publicRoot],
+ };
+}
+
+function writeFile(root, relativePath, contents = "") {
+ const filePath = path.join(root, relativePath);
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
+ fs.writeFileSync(filePath, contents);
+}
+
+test("extracts literal image props from multiline SEO tags", () => {
+ const source = `
+export const Head = () => (
+
+);`;
+
+ assert.deepEqual(findLocalSeoImages(source), [
+ { imagePath: "/images/platform.webp", line: 3 },
+ ]);
+});
+
+test("ignores external and dynamic SEO image props", () => {
+ const source = `
+
+`;
+
+ assert.deepEqual(findLocalSeoImages(source), []);
+});
+
+test("accepts an existing root-relative asset", (context) => {
+ const fixture = createFixture();
+ context.after(() => fs.rmSync(fixture.repoRoot, { recursive: true, force: true }));
+ writeFile(fixture.sourceRoot, "page.js", '');
+ writeFile(fixture.assetRoots[0], "images/card.webp", "image");
+
+ assert.deepEqual(validateSeoImages(fixture), []);
+});
+
+test("reports a missing local asset with its source line", (context) => {
+ const fixture = createFixture();
+ context.after(() => fs.rmSync(fixture.repoRoot, { recursive: true, force: true }));
+ writeFile(
+ fixture.sourceRoot,
+ "pages/missing.js",
+ 'const Head = () => (\n \n);',
+ );
+
+ const failures = validateSeoImages(fixture);
+ assert.equal(failures.length, 1);
+ assert.match(failures[0], /src[\\/]pages[\\/]missing\.js:2/);
+ assert.match(failures[0], /missing local SEO image \/images\/missing\.webp/);
+});
+
+test("rejects a relative local image path", (context) => {
+ const fixture = createFixture();
+ context.after(() => fs.rmSync(fixture.repoRoot, { recursive: true, force: true }));
+ writeFile(fixture.sourceRoot, "page.jsx", '');
+
+ const failures = validateSeoImages(fixture);
+ assert.equal(failures.length, 1);
+ assert.match(failures[0], /invalid local SEO image images\/card\.webp/);
+});
diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml
index 3fd7e8199af1e4..94b2eac1e9d6d1 100644
--- a/.github/workflows/checks.yml
+++ b/.github/workflows/checks.yml
@@ -21,6 +21,12 @@ jobs:
- name: Check CONTRIBUTING versions
run: npm run check:contributing-versions
+ - name: Check local SEO image references
+ run: npm run check:seo-images
+
+ - name: Test local SEO image validation
+ run: npm run test:seo-images
+
- name: Build
run: npm run build
diff --git a/package.json b/package.json
index f865572768e3a9..35f14595e8ad26 100644
--- a/package.json
+++ b/package.json
@@ -28,6 +28,8 @@
"lint": "eslint --fix .",
"checklint": "eslint .",
"check:contributing-versions": "node .github/scripts/check-contributing-versions.cjs",
+ "check:seo-images": "node .github/scripts/check-seo-images.cjs",
+ "test:seo-images": "node --test .github/scripts/check-seo-images.test.cjs",
"pretest": "eslint --ignore-path .gitignore .",
"preload-fonts": "gatsby-preload-fonts",
"deploy": "gatsby build && gh-pages -d public -b master",
diff --git a/src/pages/community/adventures-of-five-and-friends/index.js b/src/pages/community/adventures-of-five-and-friends/index.js
index d08f9a0615fc75..22cf6d9d345d11 100644
--- a/src/pages/community/adventures-of-five-and-friends/index.js
+++ b/src/pages/community/adventures-of-five-and-friends/index.js
@@ -13,5 +13,5 @@ const Adventures = () => {
};
export default Adventures;
export const Head = () => {
- return ;
+ return ;
};
\ No newline at end of file
diff --git a/src/pages/solutions/platform-engineering/index.js b/src/pages/solutions/platform-engineering/index.js
index b07f92f25d0322..ad03882b07b3a8 100644
--- a/src/pages/solutions/platform-engineering/index.js
+++ b/src/pages/solutions/platform-engineering/index.js
@@ -15,6 +15,6 @@ export default PlatformEngineeringSolutions;
export const Head = () => {
return ;
};
\ No newline at end of file
diff --git a/static/images/mascot-five-adventure-github-stars.webp b/static/images/mascot-five-adventure-github-stars.webp
new file mode 100644
index 00000000000000..0f1912fa7a646c
Binary files /dev/null and b/static/images/mascot-five-adventure-github-stars.webp differ
diff --git a/static/images/meshery-operator.webp b/static/images/meshery-operator.webp
new file mode 100644
index 00000000000000..7e888313f3e55c
Binary files /dev/null and b/static/images/meshery-operator.webp differ
diff --git a/static/images/solutions-collaborate.webp b/static/images/solutions-collaborate.webp
new file mode 100644
index 00000000000000..41a5f36f7a9d65
Binary files /dev/null and b/static/images/solutions-collaborate.webp differ
diff --git a/static/images/solutions-devrel.webp b/static/images/solutions-devrel.webp
new file mode 100644
index 00000000000000..0bc9841f6f1185
Binary files /dev/null and b/static/images/solutions-devrel.webp differ
diff --git a/static/images/solutions-diagram.webp b/static/images/solutions-diagram.webp
new file mode 100644
index 00000000000000..defa434b4dbf8d
Binary files /dev/null and b/static/images/solutions-diagram.webp differ
diff --git a/static/images/solutions-gitops.webp b/static/images/solutions-gitops.webp
new file mode 100644
index 00000000000000..5461c5d09fa77c
Binary files /dev/null and b/static/images/solutions-gitops.webp differ
diff --git a/static/images/solutions-infrastructure.webp b/static/images/solutions-infrastructure.webp
new file mode 100644
index 00000000000000..cfe9808334cce9
Binary files /dev/null and b/static/images/solutions-infrastructure.webp differ
diff --git a/static/images/solutions-operation.webp b/static/images/solutions-operation.webp
new file mode 100644
index 00000000000000..6d7983c8d2af94
Binary files /dev/null and b/static/images/solutions-operation.webp differ
diff --git a/static/images/solutions-platform-engineering.webp b/static/images/solutions-platform-engineering.webp
new file mode 100644
index 00000000000000..1bf272883300b2
Binary files /dev/null and b/static/images/solutions-platform-engineering.webp differ