Compare commits

...

4 Commits

Author SHA1 Message Date
8a87318b12 fix(imgproxy): fallback to smart gravity (sm) instead of face detection (fv)
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 12s
Build & Deploy / 🧪 QA (push) Successful in 1m27s
Build & Deploy / 🏗️ Build (push) Successful in 2m56s
Build & Deploy / 🚀 Deploy (push) Successful in 29s
Build & Deploy / 🧪 Smoke Test (push) Successful in 51s
Build & Deploy / ⚡ Lighthouse (push) Successful in 4m33s
Build & Deploy / 🔔 Notify (push) Successful in 3s
- 'fv' requires ML modules not present in standard imgproxy image
- 'sm' is robust and supported everywhere
- Fixes broken images on staging using Next.js Image loader
2026-02-19 18:05:29 +01:00
93cb12d7d9 fix(imgproxy): URL-encode plain source URLs
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 13s
Build & Deploy / 🧪 QA (push) Successful in 1m49s
Build & Deploy / 🏗️ Build (push) Successful in 2m57s
Build & Deploy / 🚀 Deploy (push) Successful in 26s
Build & Deploy / 🧪 Smoke Test (push) Successful in 49s
Build & Deploy / ⚡ Lighthouse (push) Successful in 4m23s
Build & Deploy / 🔔 Notify (push) Successful in 1s
- Use encodeURIComponent for source URLs in plain/ format
- Prevents 308 redirect loops caused by double-slash normalization
- Prevents invalid URL structures for imgproxy
2026-02-19 17:15:58 +01:00
44f0c430a9 fix(infra): whitelist video files and source maps
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 7s
Build & Deploy / 🧪 QA (push) Successful in 1m28s
Build & Deploy / 🏗️ Build (push) Successful in 7m31s
Build & Deploy / 🚀 Deploy (push) Successful in 26s
Build & Deploy / 🧪 Smoke Test (push) Successful in 1m4s
Build & Deploy / ⚡ Lighthouse (push) Successful in 3m17s
Build & Deploy / 🔔 Notify (push) Successful in 2s
- Added webm, mp4, map to Traefik whitelist to bypass Gatekeeper
- Added webm, mp4, map to middleware exclusion to prevent locale redirects
- This fixes 404 errors for background videos and source maps on protected environments
2026-02-19 16:04:58 +01:00
1478909a73 fix(imgproxy): switch from base64 to plain URL format
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 13s
Build & Deploy / 🧪 QA (push) Successful in 1m27s
Build & Deploy / 🏗️ Build (push) Successful in 7m41s
Build & Deploy / 🚀 Deploy (push) Successful in 26s
Build & Deploy / 🧪 Smoke Test (push) Successful in 49s
Build & Deploy / ⚡ Lighthouse (push) Successful in 4m6s
Build & Deploy / 🔔 Notify (push) Successful in 1s
Use plain/ source URL format instead of base64 encoding.
Base64 was causing 404 errors from imgproxy.
Plain format verified working via direct curl tests.
2026-02-19 15:07:20 +01:00
4 changed files with 7 additions and 24 deletions

View File

@@ -32,7 +32,7 @@ services:
- "traefik.http.routers.${PROJECT_NAME:-klz}.middlewares=${AUTH_MIDDLEWARE:-klz-ratelimit,klz-forward,klz-compress}"
# Public Router (Whitelist for OG Images, Sitemaps, Health)
- "traefik.http.routers.${PROJECT_NAME:-klz}-public.rule=(${TRAEFIK_HOST_RULE:-Host(`${TRAEFIK_HOST:-klz-cables.com}`)}) && (PathPrefix(`/health`) || PathPrefix(`/sitemap.xml`) || PathPrefix(`/robots.txt`) || PathPrefix(`/manifest.webmanifest`) || PathPrefix(`/_img`) || PathPrefix(`/api/og`) || PathPrefix(`/de/api/og`) || PathPrefix(`/en/api/og`) || PathPrefix(`/opengraph-image`) || PathPrefix(`/de/opengraph-image`) || PathPrefix(`/en/opengraph-image`) || PathPrefix(`/blog/opengraph-image`) || PathPrefix(`/de/blog/opengraph-image`) || PathPrefix(`/en/blog/opengraph-image`) || PathRegexp(`^/sitemap(-[0-9]+)?\\.xml$`) || PathRegexp(`^/.*\\.(svg|png|jpg|jpeg|gif|webp|ico)$`))"
- "traefik.http.routers.${PROJECT_NAME:-klz}-public.rule=(${TRAEFIK_HOST_RULE:-Host(`${TRAEFIK_HOST:-klz-cables.com}`)}) && (PathPrefix(`/health`) || PathPrefix(`/sitemap.xml`) || PathPrefix(`/robots.txt`) || PathPrefix(`/manifest.webmanifest`) || PathPrefix(`/_img`) || PathPrefix(`/api/og`) || PathPrefix(`/de/api/og`) || PathPrefix(`/en/api/og`) || PathPrefix(`/logo-white.svg`) || PathPrefix(`/icon-white.svg`) || PathPrefix(`/opengraph-image`) || PathPrefix(`/de/opengraph-image`) || PathPrefix(`/en/opengraph-image`) || PathPrefix(`/blog/opengraph-image`) || PathPrefix(`/de/blog/opengraph-image`) || PathPrefix(`/en/blog/opengraph-image`) || PathRegexp(`^/sitemap(-[0-9]+)?\\.xml$`) || PathRegexp(`.*\\.(svg|png|jpg|jpeg|gif|webp|ico|webm|mp4|map)$`))"
- "traefik.http.routers.${PROJECT_NAME:-klz}-public.entrypoints=${TRAEFIK_ENTRYPOINT:-web}"
- "traefik.http.routers.${PROJECT_NAME:-klz}-public.tls.certresolver=${TRAEFIK_CERT_RESOLVER:-}"
- "traefik.http.routers.${PROJECT_NAME:-klz}-public.tls=${TRAEFIK_TLS:-false}"
@@ -168,7 +168,6 @@ services:
IMGPROXY_URL_MAPPING: "${IMGPROXY_URL_MAPPING:-http://klz.localhost/:http://klz-app:3000/,http://cms.klz.localhost/:http://klz-cms:8055/}"
IMGPROXY_USE_ETAG: "true"
IMGPROXY_MAX_SRC_RESOLUTION: 20
IMGPROXY_ALLOWED_NETWORKS: "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
IMGPROXY_IGNORE_SSL_ERRORS: "true"
IMGPROXY_DEBUG: "true"
labels:

View File

@@ -28,6 +28,6 @@ export default function imgproxyLoader({
return getImgproxyUrl(src, {
width,
resizing_type: 'fit',
gravity: 'fv', // Use face-aware focusing (face detection)
gravity: 'sm', // Use smart gravity (content-aware) instead of face detection (requires ML)
});
}

View File

@@ -13,22 +13,6 @@ interface ImgproxyOptions {
extension?: string;
}
/**
* Encodes a string to Base64 (URL-safe)
*/
function encodeBase64(str: string): string {
if (typeof Buffer !== 'undefined') {
return Buffer.from(str)
.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
} else {
// Fallback for browser environment if Buffer is not available
return window.btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
}
export function getImgproxyUrl(src: string, options: ImgproxyOptions = {}): string {
// Use local proxy path which is rewritten in next.config.mjs
const baseUrl = '/_img';
@@ -71,10 +55,10 @@ export function getImgproxyUrl(src: string, options: ImgproxyOptions = {}): stri
`g:${gravity}`,
].join('/');
// Using /unsafe/ for now as we don't handle signatures yet
// Format: <base_url>/unsafe/<options>/<base64_url>
// Using /unsafe/ with plain/ source URL format
// plain/ format works reliably with imgproxy URL mapping
// Format: <base_url>/unsafe/<options>/plain/<source_url>[@<extension>]
const suffix = extension ? `@${extension}` : '';
const encodedSrc = encodeBase64(absoluteSrc + suffix);
return `${baseUrl}/unsafe/${processingOptions}/${encodedSrc}`;
return `${baseUrl}/unsafe/${processingOptions}/plain/${encodeURIComponent(absoluteSrc)}${suffix}`;
}

View File

@@ -95,7 +95,7 @@ export default function middleware(request: NextRequest) {
export const config = {
matcher: [
'/((?!api|_next/static|_next/image|_img|favicon.ico|manifest.webmanifest|.*\\.(?:svg|png|jpg|jpeg|gif|webp|pdf|txt|vcf|xml)$).*)',
'/((?!api|_next/static|_next/image|_img|favicon.ico|manifest.webmanifest|.*\\.(?:svg|png|jpg|jpeg|gif|webp|pdf|txt|vcf|xml|webm|mp4|map)$).*)',
'/(de|en)/:path*',
'/(de|en)/:path*',
],