diff --git a/apps/site/src/lib/structured-data.ts b/apps/site/src/lib/structured-data.ts index 28214c43..c6eaf1f0 100644 --- a/apps/site/src/lib/structured-data.ts +++ b/apps/site/src/lib/structured-data.ts @@ -1,4 +1,17 @@ import { absoluteUrl, SITE_NAME } from "./site"; +import { youtubeId } from "./youtube"; + +function cleanDescription( + desc?: string | null, + fallback?: string | null, +): string | undefined { + const trimmed = desc?.trim(); + if (trimmed && trimmed.toLowerCase() !== "fix me") { + return trimmed; + } + const fb = fallback?.trim(); + return fb && fb.toLowerCase() !== "fix me" ? fb : undefined; +} /** * JSON-LD builders, ported from the Next app's lib/structured-data.ts. @@ -97,7 +110,7 @@ export function articleSchema( "@type": content.articleType ?? "TechArticle", "@id": `${url}#article`, headline: content.title ?? undefined, - description: content.excerpt ?? undefined, + description: cleanDescription(content.excerpt, content.title), ...(content.imageUrl ? { image: content.imageUrl } : {}), ...(content.date ? { datePublished: content.date } : {}), ...(content._updatedAt ? { dateModified: content._updatedAt } : {}), @@ -204,19 +217,27 @@ export function videoObjectSchema( durationIso = d; } + const ytId = youtubeId(video.youtubeUrl); + const resolvedThumb = + video.thumbnailUrl || + (ytId ? `https://i.ytimg.com/vi/${ytId}/hqdefault.jpg` : undefined); + const embedUrl = ytId + ? `https://www.youtube.com/embed/${ytId}` + : video.youtubeUrl + .replace("watch?v=", "embed/") + .replace("youtu.be/", "www.youtube.com/embed/"); + return { "@type": "VideoObject", "@id": `${url}#video`, name: video.title ?? undefined, - description: video.description ?? undefined, - ...(video.thumbnailUrl ? { thumbnailUrl: [video.thumbnailUrl] } : {}), + description: cleanDescription(video.description, video.title), + ...(resolvedThumb ? { thumbnailUrl: [resolvedThumb] } : {}), ...(video.uploadDate ? { uploadDate: video.uploadDate } : {}), ...(durationIso ? { duration: durationIso } : {}), ...(video.transcript ? { transcript: video.transcript } : {}), contentUrl: video.youtubeUrl, - embedUrl: video.youtubeUrl - .replace("watch?v=", "embed/") - .replace("youtu.be/", "www.youtube.com/embed/"), + embedUrl, }; } @@ -326,7 +347,7 @@ export function podcastEpisodeSchema( "@id": `${url}#episode`, url, name: content.title ?? undefined, - description: content.excerpt ?? undefined, + description: cleanDescription(content.excerpt, content.title), ...(content.date ? { datePublished: content.date } : {}), ...(content.imageUrl ? { image: content.imageUrl } : {}), ...(content.season ? { seasonNumber: content.season } : {}), diff --git a/apps/site/src/middleware.ts b/apps/site/src/middleware.ts index 53ab0414..a684f5a7 100644 --- a/apps/site/src/middleware.ts +++ b/apps/site/src/middleware.ts @@ -77,10 +77,15 @@ export const onRequest = defineMiddleware(async (context, next) => { }); } - if (context.locals.sanity.preview.enabled) { - // Not cosmetic: without this a response containing unpublished drafts can - // be written to the edge cache and served to the public. - response.headers.set("Cache-Control", "no-store, private"); + if ( + context.locals.sanity.preview.enabled || + origin !== "https://codingcat.dev" + ) { + if (context.locals.sanity.preview.enabled) { + // Not cosmetic: without this a response containing unpublished drafts can + // be written to the edge cache and served to the public. + response.headers.set("Cache-Control", "no-store, private"); + } response.headers.set("X-Robots-Tag", "noindex, nofollow"); } @@ -97,7 +102,7 @@ export const onRequest = defineMiddleware(async (context, next) => { ); // AIPREF / AI Preference Headers - response.headers.set("Content-Usage", "train-ai=n, search=y"); + response.headers.set("Content-Usage", "train-ai=y, search=y"); // Always emit Link headers (RFC 8288) for AI agent discoverability response.headers.set(