feat: update site URLs and enhance image handling
Deploy / verify (push) Successful in 50s
Deploy / deploy (push) Failing after 1m3s

- 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:
Peter Meier
2026-04-18 12:25:45 +02:00
parent a2f2d5bfb0
commit da294010c8
16 changed files with 322 additions and 29 deletions
+92 -4
View File
@@ -12,7 +12,8 @@ import {
getPostImageField,
formatPostDate,
} from '$lib/blog-utils';
import { POST_RESOLVE } from '$lib/constants';
import { POST_RESOLVE, POST_SOCIAL_IMAGE_TRANSFORM, SITE_NAME } from '$lib/constants';
import { env } from '$env/dynamic/public';
import { error } from '@sveltejs/kit';
import { marked } from 'marked';
@@ -47,6 +48,21 @@ export const load: PageServerLoad = async ({ params, locals, url }) => {
})
: null;
const siteBaseUrl = (env.PUBLIC_SITE_URL || '').replace(/\/$/, '');
const canonical = `${siteBaseUrl}/post/${slug}/`;
const postSocialImageRel = postImageField
? await ensureTransformedImage(postImageField.url, {
...POST_SOCIAL_IMAGE_TRANSFORM,
focalX: postImageField.focal?.x,
focalY: postImageField.focal?.y,
})
: null;
const postSocialImageAbs = postSocialImageRel
? postSocialImageRel.startsWith('/')
? `${siteBaseUrl}${postSocialImageRel}`
: postSocialImageRel
: null;
const postDate = formatPostDate(post.created);
const postContent = (post as { content?: string }).content;
@@ -63,6 +79,14 @@ export const load: PageServerLoad = async ({ params, locals, url }) => {
((post as { row2Content?: unknown[] }).row2Content?.length ?? 0) > 0 ||
((post as { row3Content?: unknown[] }).row3Content?.length ?? 0) > 0;
const siteName =
(import.meta.env.PUBLIC_SITE_NAME as string | undefined) || SITE_NAME;
const postCreated = (post as { created?: string }).created ?? null;
const postUpdated = (post as { updated?: string }).updated ?? postCreated;
const postAuthor = (post as { author?: string }).author ?? null;
const postHeadline = post.headline ?? post.linkName ?? slug;
const postDescription = post.seoDescription ?? post.subheadline ?? post.excerpt ?? '';
const isEvent = !!(post as { isEvent?: boolean }).isEvent;
const eventDateRaw = (post as { eventDate?: string }).eventDate ?? null;
const eventDateLabel =
@@ -104,6 +128,68 @@ export const load: PageServerLoad = async ({ params, locals, url }) => {
.replace(/\{\{PAGE_TITLE\}\}/g, post.headline ?? post.linkName ?? slug);
const showCommentSection = (post as { showCommentSection?: boolean }).showCommentSection !== false;
const jsonLd: Record<string, unknown>[] = [];
const articleLd: Record<string, unknown> = {
'@context': 'https://schema.org',
'@type': isEvent ? 'Article' : 'BlogPosting',
headline: postHeadline,
mainEntityOfPage: { '@type': 'WebPage', '@id': canonical },
url: canonical,
inLanguage: 'de-DE',
...(postDescription ? { description: postDescription } : {}),
...(postCreated ? { datePublished: postCreated } : {}),
...(postUpdated ? { dateModified: postUpdated } : {}),
...(postSocialImageAbs ? { image: postSocialImageAbs } : {}),
author: {
'@type': postAuthor ? 'Person' : 'Organization',
name: postAuthor ?? siteName,
},
publisher: {
'@type': 'Organization',
name: siteName,
...(siteBaseUrl ? { url: siteBaseUrl } : {}),
},
};
jsonLd.push(articleLd);
if (isEvent && eventDateRaw) {
const eventLd: Record<string, unknown> = {
'@context': 'https://schema.org',
'@type': 'Event',
name: postHeadline,
startDate: eventDateRaw,
eventAttendanceMode: 'https://schema.org/OfflineEventAttendanceMode',
eventStatus: 'https://schema.org/EventScheduled',
url: canonical,
inLanguage: 'de-DE',
...(postDescription ? { description: postDescription } : {}),
...(postSocialImageAbs ? { image: postSocialImageAbs } : {}),
organizer: {
'@type': 'Organization',
name: siteName,
...(siteBaseUrl ? { url: siteBaseUrl } : {}),
},
...(eventLocation?.text
? {
location: {
'@type': 'Place',
name: eventLocation.text,
...(hasCoords
? {
geo: {
'@type': 'GeoCoordinates',
latitude: eventLocation.lat,
longitude: eventLocation.lng,
},
}
: {}),
},
}
: {}),
};
jsonLd.push(eventLd);
}
return {
post,
postImageUrl,
@@ -120,9 +206,11 @@ export const load: PageServerLoad = async ({ params, locals, url }) => {
mapLinkUrl,
commentSlot: showCommentSection ? commentSlot : '',
translations,
seoTitle: post.seoTitle ?? post.headline ?? post.linkName ?? slug,
seoDescription: post.seoDescription ?? post.subheadline ?? post.excerpt ?? '',
socialImage: postImageUrl ?? undefined,
seoTitle: post.seoTitle ?? postHeadline,
seoDescription: postDescription,
socialImage: postSocialImageAbs ?? postImageUrl ?? undefined,
robots: (post as { seoMetaRobots?: string }).seoMetaRobots ?? undefined,
jsonLd,
breadcrumbItems: [
{ href: '/', label: 'Start' },
{ href: '/posts/', label: 'Beiträge' },