Files
klz-cables.com/middleware.ts
Marc Mintel 9de3931e33
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 1m12s
Build & Deploy / 🧪 QA (push) Successful in 1m56s
Build & Deploy / 🚀 Deploy (push) Blocked by required conditions
Build & Deploy / 🧪 Smoke Test (push) Blocked by required conditions
Build & Deploy / ⚡ Lighthouse (push) Blocked by required conditions
Build & Deploy / ♿ WCAG (push) Blocked by required conditions
Build & Deploy / 🛡️ Quality Gates (push) Blocked by required conditions
Build & Deploy / 🔔 Notify (push) Blocked by required conditions
Build & Deploy / 🏗️ Build (push) Has been cancelled
feat: Implement imgproxy health check with fallback redirection for image requests when the service is down.
2026-02-23 02:35:49 +01:00

133 lines
4.3 KiB
TypeScript

import createMiddleware from 'next-intl/middleware';
import { NextRequest, NextResponse } from 'next/server';
// Create the internationalization middleware
const intlMiddleware = createMiddleware({
// A list of all locales that are supported
locales: ['en', 'de'],
// Used when no locale matches
defaultLocale: 'en',
});
const imgproxyStatus = { isDown: false, lastCheck: 0 };
async function isImgproxyDown() {
const now = Date.now();
if (now - imgproxyStatus.lastCheck > 60000) {
try {
const imgproxyUrl = process.env.IMGPROXY_URL || 'https://img.infra.mintel.me';
const checkUrl = imgproxyUrl.startsWith('http') ? imgproxyUrl : `https://${imgproxyUrl}`;
const res = await fetch(checkUrl, { signal: AbortSignal.timeout(2000) });
imgproxyStatus.isDown = res.status >= 500;
} catch (e) {
imgproxyStatus.isDown = true;
}
imgproxyStatus.lastCheck = now;
}
return imgproxyStatus.isDown;
}
export default async function middleware(request: NextRequest) {
const { method, url, headers } = request;
const { pathname } = request.nextUrl;
if (pathname.startsWith('/_img/')) {
if (await isImgproxyDown()) {
const originalUrl = request.nextUrl.searchParams.get('url');
if (originalUrl) {
return NextResponse.redirect(originalUrl);
}
}
return NextResponse.next();
}
// Explicit bypass for infrastructure routes to avoid locale redirects/interception
if (
pathname.startsWith('/stats') ||
pathname.startsWith('/errors') ||
pathname.startsWith('/health') ||
pathname.includes('/api/og') ||
pathname.includes('opengraph-image') ||
pathname.endsWith('sitemap.xml') ||
pathname.endsWith('manifest.webmanifest')
) {
return NextResponse.next();
}
// Build header object for logging
const headerObj: Record<string, string> = {};
headers.forEach((value, key) => {
headerObj[key] = value;
});
// Defensive URL correction for internal container leakage (0.0.0.0, klz-app, localhost)
// This prevents hydration mismatches and host poisoning in generated links/metadata.
const urlObj = new URL(url);
const internalHosts = ['0.0.0.0', 'klz-app', 'localhost', '127.0.0.1'];
let effectiveRequest = request;
if (internalHosts.includes(urlObj.hostname)) {
const proto = headers.get('x-forwarded-proto') || 'https';
// Prioritize x-forwarded-host (passed by Traefik) over the local Host header
const hostHeader =
headers.get('x-forwarded-host') || headers.get('host') || 'testing.klz-cables.com';
urlObj.protocol = proto;
effectiveRequest = new NextRequest(urlObj, {
headers: request.headers,
method: request.method,
body: request.body,
});
if (process.env.NODE_ENV !== 'production' || !process.env.CI) {
console.log(
`🛡️ Proxy: Fixed internal URL leak: ${url} -> ${urlObj.toString()} | Proto: ${proto} | Host: ${hostHeader}`,
);
}
}
try {
// Apply internationalization middleware
const response = intlMiddleware(effectiveRequest);
// Upgrade 307 (Temporary Redirect) to 308 (Permanent Redirect)
// This improves compatibility with scanners (Website Carbon, PageSpeed) and SEO.
if (response.status === 307) {
const location = response.headers.get('Location');
if (location) {
const url = new URL(location, request.url);
return Response.redirect(url, 308);
}
}
// Allow iframe embedding from recorder domains
const referer = headers.get('referer') || '';
const recorderDomains = ['recorder.localhost', 'recorder.mintel.me'];
const isRecorderRequest = recorderDomains.some((domain) => referer.includes(domain));
if (isRecorderRequest) {
response.headers.delete('x-frame-options');
response.headers.delete('content-security-policy');
response.headers.set('Access-Control-Allow-Origin', '*');
}
return response;
} catch (error) {
console.error(
`Request failed: method=${method} url=${url} headers=${JSON.stringify(headerObj)}`,
error,
);
throw error;
}
}
export const config = {
matcher: [
'/((?!api|_next/static|_next/image|favicon.ico|manifest.webmanifest|.*\\.(?:svg|png|jpg|jpeg|gif|webp|pdf|txt|vcf|xml|webm|mp4|map)$).*)',
'/(de|en)/:path*',
'/(de|en)/:path*',
],
};