feat: update site URLs and enhance image handling
- Changed site URLs in docker-compose and deployment configurations to remove "www" prefix for consistency. - Improved image handling by introducing warmPostCardImages function for pre-fetching images in posts and tags. - Enhanced PostCard component with responsive image sizes for better performance. - Updated layout to include robots meta tag and JSON-LD structured data for improved SEO. - Added loading state for images in TopBanner component to enhance user experience.
This commit is contained in:
@@ -47,6 +47,7 @@
|
||||
width={400}
|
||||
aspect="3/2"
|
||||
alt={post.headline ?? ""}
|
||||
sizes="(min-width: 1024px) 33vw, (min-width: 640px) 50vw, 100vw"
|
||||
class="w-full h-full object-cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
@@ -80,6 +80,19 @@
|
||||
const hasPageTitle = $derived((headline != null && headline !== "") || (subheadline != null && subheadline !== ""));
|
||||
const showBanner = $derived(hasImage || hasText || hasPageTitle);
|
||||
const altText = $derived(banner?.headline ?? headline ?? "");
|
||||
|
||||
let imgEl = $state<HTMLImageElement | null>(null);
|
||||
let imgLoaded = $state(false);
|
||||
|
||||
const currentSrc = $derived(responsive?.fallback ?? resolvedImages[0] ?? "");
|
||||
$effect(() => {
|
||||
// reset on src change, then probe cache
|
||||
void currentSrc;
|
||||
imgLoaded = false;
|
||||
queueMicrotask(() => {
|
||||
if (imgEl?.complete && imgEl.naturalWidth > 0) imgLoaded = true;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if showBanner}
|
||||
@@ -98,13 +111,17 @@
|
||||
<img
|
||||
src={responsive.fallback}
|
||||
alt={altText}
|
||||
class="w-full object-cover max-h-[400px] lg:max-h-[600px] max-w-[1920px] aspect-square sm:aspect-video lg:aspect-24/9 text-transparent"
|
||||
class="w-full object-cover max-h-[400px] lg:max-h-[600px] max-w-[1920px] aspect-square sm:aspect-video lg:aspect-24/9 text-transparent transition-opacity duration-500"
|
||||
class:opacity-0={!imgLoaded}
|
||||
class:opacity-100={imgLoaded}
|
||||
width={responsive.width}
|
||||
height={responsive.height}
|
||||
style={imgStyle}
|
||||
loading="eager"
|
||||
fetchpriority="high"
|
||||
decoding="async"
|
||||
bind:this={imgEl}
|
||||
onload={() => (imgLoaded = true)}
|
||||
/>
|
||||
</picture>
|
||||
{/key}
|
||||
@@ -113,12 +130,15 @@
|
||||
<img
|
||||
src={resolvedImages[0]}
|
||||
alt={altText}
|
||||
class="w-full object-cover max-h-[400px] lg:max-h-[600px] max-w-[1920px] aspect-square sm:aspect-video lg:aspect-24/9 text-transparent"
|
||||
class="w-full object-cover max-h-[400px] lg:max-h-[600px] max-w-[1920px] aspect-square sm:aspect-video lg:aspect-24/9 text-transparent transition-opacity duration-500"
|
||||
class:opacity-0={!imgLoaded}
|
||||
class:opacity-100={imgLoaded}
|
||||
width={1920}
|
||||
height={600}
|
||||
style={imgStyle}
|
||||
loading="eager"
|
||||
fetchpriority="high"
|
||||
onload={() => (imgLoaded = true)}
|
||||
/>
|
||||
{/key}
|
||||
{/if}
|
||||
|
||||
+15
-3
@@ -7,10 +7,22 @@
|
||||
export const DEFAULT_SOCIAL_IMAGE_URL =
|
||||
"https://images.ctfassets.net/xjxq6v7l1pfe/43yZHpF9OCnrBlEWHJSZMK/1ef20b265ea1e4e55317812ee5ae6b4c/simple-green-tree-illustrated-watercolor-with-gentle-shading-great-rural-scenery-farm-backdrops-naturethemed-designs-landsca.jpg";
|
||||
|
||||
/** Parameter für og:image / twitter:image (über /cms-images + Disk-Cache). */
|
||||
/** og:image-Dimensionen (Facebook/Twitter empfehlen 1200×630, 1.91:1). */
|
||||
export const SOCIAL_IMAGE_DIMENSIONS = {
|
||||
width: 1200,
|
||||
height: 630,
|
||||
} as const;
|
||||
|
||||
/** Logo-Fallback für og:image: letterboxed (contain), damit Logo komplett sichtbar bleibt. */
|
||||
export const SOCIAL_IMAGE_TRANSFORM = {
|
||||
width: 200,
|
||||
height: 200,
|
||||
...SOCIAL_IMAGE_DIMENSIONS,
|
||||
fit: "contain" as const,
|
||||
format: "webp" as const,
|
||||
};
|
||||
|
||||
/** Post-Bild für og:image: cover (Banner-Look), Focal wird separat übergeben. */
|
||||
export const POST_SOCIAL_IMAGE_TRANSFORM = {
|
||||
...SOCIAL_IMAGE_DIMENSIONS,
|
||||
fit: "cover" as const,
|
||||
format: "webp" as const,
|
||||
};
|
||||
|
||||
@@ -255,3 +255,35 @@ export function warmResponsiveImage(
|
||||
void fetchFn(u).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire-and-forget Warmup für PostCard-Bilder (Grid: 400px base, 2x density, webp+jpeg).
|
||||
* Spiegelt die URLs, die <RustyImage width={400} aspect="3/2"/> zur Laufzeit anfragt.
|
||||
*/
|
||||
export function warmPostCardImages(
|
||||
posts: Array<{ _rawImageUrl?: string; _imageFocal?: { x: number; y: number } | null }>,
|
||||
fetchFn: typeof fetch,
|
||||
limit = 6,
|
||||
): void {
|
||||
const widths = [400, 800];
|
||||
const formats: Array<'webp' | 'jpeg'> = ['webp', 'jpeg'];
|
||||
for (const post of posts.slice(0, limit)) {
|
||||
const raw = post._rawImageUrl;
|
||||
if (!raw) continue;
|
||||
const focal = post._imageFocal ?? undefined;
|
||||
for (const w of widths) {
|
||||
for (const format of formats) {
|
||||
const url = getCmsImageUrl(raw, {
|
||||
width: w,
|
||||
height: Math.round((w / 3) * 2),
|
||||
fit: 'cover',
|
||||
format,
|
||||
quality: 75,
|
||||
ar: '3/2',
|
||||
...(focal ? { focalX: focal.x, focalY: focal.y } : {}),
|
||||
});
|
||||
void fetchFn(url).catch(() => {});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user