diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml
index b2fd93f7..ae25f5c9 100644
--- a/.gitea/workflows/deploy.yml
+++ b/.gitea/workflows/deploy.yml
@@ -363,11 +363,43 @@ jobs:
run: docker builder prune -f --filter "until=1h"
# ──────────────────────────────────────────────────────────────────────────────
- # JOB 5: Notifications
+ # JOB 5: Smoke Test (OG Images)
+ # ──────────────────────────────────────────────────────────────────────────────
+ smoke_test:
+ name: 🧪 Smoke Test
+ needs: [prepare, deploy]
+ if: needs.deploy.result == 'success'
+ runs-on: docker
+ container:
+ image: catthehacker/ubuntu:act-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ - name: Setup pnpm
+ uses: pnpm/action-setup@v3
+ with:
+ version: 10
+ - name: 🔐 Registry Auth
+ run: |
+ echo "@mintel:registry=https://${{ vars.REGISTRY_HOST || 'npm.infra.mintel.me' }}" > .npmrc
+ echo "//${{ vars.REGISTRY_HOST || 'npm.infra.mintel.me' }}/:_authToken=${{ secrets.REGISTRY_PASS }}" >> .npmrc
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+ - name: 🚀 Run OG Image Check
+ env:
+ TEST_URL: ${{ needs.prepare.outputs.next_public_url }}
+ run: pnpm run check:og
+
+ # ──────────────────────────────────────────────────────────────────────────────
+ # JOB 6: Notifications
# ──────────────────────────────────────────────────────────────────────────────
notifications:
name: 🔔 Notify
- needs: [prepare, deploy]
+ needs: [prepare, deploy, smoke_test]
if: always()
runs-on: docker
container:
diff --git a/app/[locale]/[slug]/opengraph-image.tsx b/app/[locale]/[slug]/opengraph-image.tsx
index da398edb..9bacaef4 100644
--- a/app/[locale]/[slug]/opengraph-image.tsx
+++ b/app/[locale]/[slug]/opengraph-image.tsx
@@ -5,7 +5,12 @@ import { getOgFonts, OG_IMAGE_SIZE } from '@/lib/og-helper';
export const runtime = 'nodejs';
-export default async function Image({ params: { locale, slug } }: { params: { locale: string, slug: string } }) {
+export default async function Image({
+ params,
+}: {
+ params: Promise<{ locale: string; slug: string }>;
+}) {
+ const { locale, slug } = await params;
const pageData = await getPageBySlug(slug, locale);
if (!pageData) {
@@ -15,17 +20,14 @@ export default async function Image({ params: { locale, slug } }: { params: { lo
const fonts = await getOgFonts();
return new ImageResponse(
- (
-
- ),
+ ,
{
...OG_IMAGE_SIZE,
fonts,
- }
+ },
);
}
-
diff --git a/app/[locale]/api/og/product/route.tsx b/app/[locale]/api/og/product/route.tsx
index 290d241f..a7027ea8 100644
--- a/app/[locale]/api/og/product/route.tsx
+++ b/app/[locale]/api/og/product/route.tsx
@@ -5,13 +5,15 @@ import { OGImageTemplate } from '@/components/OGImageTemplate';
import { NextRequest } from 'next/server';
import { getOgFonts, OG_IMAGE_SIZE } from '@/lib/og-helper';
+import { SITE_URL } from '@/lib/schema';
+
export const runtime = 'nodejs';
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ locale: string }> },
) {
- const { searchParams, origin } = new URL(request.url);
+ const { searchParams } = new URL(request.url);
const slug = searchParams.get('slug');
const { locale } = await params;
@@ -58,7 +60,7 @@ export async function GET(
const featuredImage = product.frontmatter.images?.[0]
? product.frontmatter.images[0].startsWith('http')
? product.frontmatter.images[0]
- : `${origin}${product.frontmatter.images[0]}`
+ : `${SITE_URL}${product.frontmatter.images[0]}`
: undefined;
return new ImageResponse(
diff --git a/app/[locale]/blog/[slug]/opengraph-image.tsx b/app/[locale]/blog/[slug]/opengraph-image.tsx
index 7c3138bd..21265c28 100644
--- a/app/[locale]/blog/[slug]/opengraph-image.tsx
+++ b/app/[locale]/blog/[slug]/opengraph-image.tsx
@@ -7,10 +7,11 @@ import { SITE_URL } from '@/lib/schema';
export const runtime = 'nodejs';
export default async function Image({
- params: { locale, slug },
+ params,
}: {
- params: { locale: string; slug: string };
+ params: Promise<{ locale: string; slug: string }>;
}) {
+ const { locale, slug } = await params;
const post = await getPostBySlug(slug, locale);
if (!post) {
diff --git a/app/[locale]/blog/opengraph-image.tsx b/app/[locale]/blog/opengraph-image.tsx
index 4b0a8b47..d697076b 100644
--- a/app/[locale]/blog/opengraph-image.tsx
+++ b/app/[locale]/blog/opengraph-image.tsx
@@ -5,21 +5,16 @@ import { getOgFonts, OG_IMAGE_SIZE } from '@/lib/og-helper';
export const runtime = 'nodejs';
-export default async function Image({ params: { locale } }: { params: { locale: string } }) {
+export default async function Image({ params }: { params: Promise<{ locale: string }> }) {
+ const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'Blog.meta' });
const fonts = await getOgFonts();
return new ImageResponse(
- (
-
- ),
+ ,
{
...OG_IMAGE_SIZE,
fonts,
- }
+ },
);
}
diff --git a/app/[locale]/contact/opengraph-image.tsx b/app/[locale]/contact/opengraph-image.tsx
index cf664684..e770e6ba 100644
--- a/app/[locale]/contact/opengraph-image.tsx
+++ b/app/[locale]/contact/opengraph-image.tsx
@@ -5,7 +5,8 @@ import { getOgFonts, OG_IMAGE_SIZE } from '@/lib/og-helper';
export const runtime = 'nodejs';
-export default async function Image({ params: { locale } }: { params: { locale: string } }) {
+export default async function Image({ params }: { params: Promise<{ locale: string }> }) {
+ const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'Contact' });
const fonts = await getOgFonts();
@@ -13,16 +14,10 @@ export default async function Image({ params: { locale } }: { params: { locale:
const description = t('meta.description') || t('subtitle');
return new ImageResponse(
- (
-
- ),
+ ,
{
...OG_IMAGE_SIZE,
fonts,
- }
+ },
);
}
diff --git a/app/[locale]/opengraph-image.tsx b/app/[locale]/opengraph-image.tsx
index f1fdab6e..2d074099 100644
--- a/app/[locale]/opengraph-image.tsx
+++ b/app/[locale]/opengraph-image.tsx
@@ -5,22 +5,20 @@ import { getOgFonts, OG_IMAGE_SIZE } from '@/lib/og-helper';
export const runtime = 'nodejs';
-export default async function Image({ params: { locale } }: { params: { locale: string } }) {
+export default async function Image({ params }: { params: Promise<{ locale: string }> }) {
+ const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'Index.meta' });
const fonts = await getOgFonts();
return new ImageResponse(
- (
-
- ),
+ ,
{
...OG_IMAGE_SIZE,
fonts,
- }
+ },
);
}
-
diff --git a/app/[locale]/products/opengraph-image.tsx b/app/[locale]/products/opengraph-image.tsx
index eb15740f..9a94386f 100644
--- a/app/[locale]/products/opengraph-image.tsx
+++ b/app/[locale]/products/opengraph-image.tsx
@@ -5,7 +5,8 @@ import { getOgFonts, OG_IMAGE_SIZE } from '@/lib/og-helper';
export const runtime = 'nodejs';
-export default async function Image({ params: { locale } }: { params: { locale: string } }) {
+export default async function Image({ params }: { params: Promise<{ locale: string }> }) {
+ const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'Products' });
const fonts = await getOgFonts();
@@ -13,17 +14,10 @@ export default async function Image({ params: { locale } }: { params: { locale:
const description = t('meta.description') || t('subtitle');
return new ImageResponse(
- (
-
- ),
+ ,
{
...OG_IMAGE_SIZE,
fonts,
- }
+ },
);
}
-
diff --git a/app/[locale]/team/opengraph-image.tsx b/app/[locale]/team/opengraph-image.tsx
index 8c4707f9..e5faef5f 100644
--- a/app/[locale]/team/opengraph-image.tsx
+++ b/app/[locale]/team/opengraph-image.tsx
@@ -5,7 +5,8 @@ import { getOgFonts, OG_IMAGE_SIZE } from '@/lib/og-helper';
export const runtime = 'nodejs';
-export default async function Image({ params: { locale } }: { params: { locale: string } }) {
+export default async function Image({ params }: { params: Promise<{ locale: string }> }) {
+ const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'Team' });
const fonts = await getOgFonts();
@@ -13,17 +14,10 @@ export default async function Image({ params: { locale } }: { params: { locale:
const description = t('meta.description') || t('hero.title');
return new ImageResponse(
- (
-
- ),
+ ,
{
...OG_IMAGE_SIZE,
fonts,
- }
+ },
);
}
-
diff --git a/lib/og-helper.tsx b/lib/og-helper.tsx
index 088bce81..02bb7e23 100644
--- a/lib/og-helper.tsx
+++ b/lib/og-helper.tsx
@@ -6,37 +6,37 @@ import { join } from 'path';
* Since we are using runtime = 'nodejs', we can read them from the filesystem.
*/
export async function getOgFonts() {
- const boldFontPath = join(process.cwd(), 'public/fonts/Inter-Bold.ttf');
- const regularFontPath = join(process.cwd(), 'public/fonts/Inter-Regular.ttf');
+ const boldFontPath = join(process.cwd(), 'public/fonts/Inter-Bold.woff2');
+ const regularFontPath = join(process.cwd(), 'public/fonts/Inter-Regular.woff2');
- try {
- const boldFont = readFileSync(boldFontPath);
- const regularFont = readFileSync(regularFontPath);
+ try {
+ const boldFont = readFileSync(boldFontPath);
+ const regularFont = readFileSync(regularFontPath);
- return [
- {
- name: 'Inter',
- data: boldFont,
- weight: 700 as const,
- style: 'normal' as const,
- },
- {
- name: 'Inter',
- data: regularFont,
- weight: 400 as const,
- style: 'normal' as const,
- },
- ];
- } catch (error) {
- console.error('Failed to load OG fonts from filesystem, falling back to system fonts:', error);
- return [];
- }
+ return [
+ {
+ name: 'Inter',
+ data: boldFont,
+ weight: 700 as const,
+ style: 'normal' as const,
+ },
+ {
+ name: 'Inter',
+ data: regularFont,
+ weight: 400 as const,
+ style: 'normal' as const,
+ },
+ ];
+ } catch (error) {
+ console.error('Failed to load OG fonts from filesystem, falling back to system fonts:', error);
+ return [];
+ }
}
/**
* Common configuration for OG images
*/
export const OG_IMAGE_SIZE = {
- width: 1200,
- height: 630,
+ width: 1200,
+ height: 630,
};
diff --git a/package.json b/package.json
index 1a8936d4..1e3d8f4e 100644
--- a/package.json
+++ b/package.json
@@ -80,6 +80,7 @@
"typecheck": "tsc --noEmit",
"test": "vitest run --passWithNoTests",
"test:og": "vitest run tests/og-image.test.ts",
+ "check:og": "tsx scripts/check-og-images.ts",
"cms:branding:local": "DIRECTUS_URL=http://localhost:8055 npx tsx --env-file=.env scripts/setup-directus-branding.ts",
"cms:branding:testing": "DIRECTUS_URL=https://cms.testing.klz-cables.com npx tsx --env-file=.env scripts/setup-directus-branding.ts",
"cms:branding:staging": "DIRECTUS_URL=https://cms.staging.klz-cables.com npx tsx --env-file=.env scripts/setup-directus-branding.ts",
diff --git a/public/fonts/Inter-Bold.ttf b/public/fonts/Inter-Bold.ttf
deleted file mode 100644
index fc27522d..00000000
--- a/public/fonts/Inter-Bold.ttf
+++ /dev/null
@@ -1,1447 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Page not found · GitHub · GitHub
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- You can’t perform that action at this time.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/public/fonts/Inter-Bold.woff2 b/public/fonts/Inter-Bold.woff2
new file mode 100644
index 00000000..b9e3cb3b
Binary files /dev/null and b/public/fonts/Inter-Bold.woff2 differ
diff --git a/public/fonts/Inter-Regular.ttf b/public/fonts/Inter-Regular.ttf
deleted file mode 100644
index bb370eb9..00000000
--- a/public/fonts/Inter-Regular.ttf
+++ /dev/null
@@ -1,1447 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Page not found · GitHub · GitHub
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- You can’t perform that action at this time.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/public/fonts/Inter-Regular.woff2 b/public/fonts/Inter-Regular.woff2
new file mode 100644
index 00000000..2bcd222e
Binary files /dev/null and b/public/fonts/Inter-Regular.woff2 differ
diff --git a/scripts/check-og-images.ts b/scripts/check-og-images.ts
new file mode 100644
index 00000000..9daf3af2
--- /dev/null
+++ b/scripts/check-og-images.ts
@@ -0,0 +1,72 @@
+import fetch from 'node-fetch';
+import { SITE_URL } from '../lib/schema.js';
+
+const BASE_URL = process.env.TEST_URL || SITE_URL;
+
+console.log(`\n🚀 Starting OG Image Verification for ${BASE_URL}\n`);
+
+const routes = [
+ '/de/opengraph-image',
+ '/en/opengraph-image',
+ '/de/blog/opengraph-image',
+ '/de/api/og/product?slug=nay2y',
+ '/en/api/og/product?slug=medium-voltage-cables',
+];
+
+async function verifyImage(path: string): Promise {
+ const url = `${BASE_URL}${path}`;
+ const start = Date.now();
+
+ try {
+ const response = await fetch(url);
+ const duration = Date.now() - start;
+
+ console.log(`Checking ${url}...`);
+
+ if (response.status !== 200) {
+ throw new Error(`Status: ${response.status}`);
+ }
+
+ const contentType = response.headers.get('content-type');
+ if (!contentType?.includes('image/png')) {
+ throw new Error(`Content-Type: ${contentType}`);
+ }
+
+ const buffer = await response.arrayBuffer();
+ const bytes = new Uint8Array(buffer);
+
+ // PNG Signature: 89 50 4E 47 0D 0A 1A 0A
+ if (bytes[0] !== 0x89 || bytes[1] !== 0x50 || bytes[2] !== 0x4e || bytes[3] !== 0x47) {
+ throw new Error('Invalid PNG signature');
+ }
+
+ if (bytes.length < 10000) {
+ throw new Error(`Image too small (${bytes.length} bytes), likely blank`);
+ }
+
+ console.log(` ✅ OK (${bytes.length} bytes, ${duration}ms)`);
+ return true;
+ } catch (error: unknown) {
+ const message = error instanceof Error ? error.message : String(error);
+ console.error(` ❌ FAILED: ${message}`);
+ return false;
+ }
+}
+
+async function run() {
+ let allOk = true;
+ for (const route of routes) {
+ const ok = await verifyImage(route);
+ if (!ok) allOk = false;
+ }
+
+ if (allOk) {
+ console.log('\n✨ All OG images verified successfully!\n');
+ process.exit(0);
+ } else {
+ console.error('\n⚠️ Some OG images failed verification.\n');
+ process.exit(1);
+ }
+}
+
+run();