From 5cf4fc48a63026a1d437f3ff52b7d7e00aaf8dd3 Mon Sep 17 00:00:00 2001 From: Florian Wilde Date: Tue, 1 Sep 2026 19:39:34 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20server-only-Kette=20aus=20dem=20Browser-?= =?UTF-8?q?Bundle=20l=C3=B6sen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die Posterkachel zog über lib/images.ts die serverseitige Cache-Logik in den Browser-Bundle, sobald sie aus einer Client-Komponente heraus gerendert wurde — der Build brach ab, sobald eine solche Seite dazukam. Die reine URL-Bildung liegt jetzt in lib/image-url.ts und darf überall verwendet werden; Abruf und Dateicache bleiben hinter server-only. --- src/components/Poster.tsx | 2 +- src/components/ProviderBadges.tsx | 2 +- src/lib/image-url.ts | 42 +++++++++++++++++++++++++++++++ src/lib/images.ts | 36 +++++--------------------- 4 files changed, 50 insertions(+), 32 deletions(-) create mode 100644 src/lib/image-url.ts diff --git a/src/components/Poster.tsx b/src/components/Poster.tsx index d4abad9..50f7083 100644 --- a/src/components/Poster.tsx +++ b/src/components/Poster.tsx @@ -1,5 +1,5 @@ import { clsx } from "clsx"; -import { posterUrl, type ImageSize } from "@/lib/images"; +import { posterUrl, type ImageSize } from "@/lib/image-url"; import { FlimmerMark } from "@/components/brand/FlimmerLogo"; /** diff --git a/src/components/ProviderBadges.tsx b/src/components/ProviderBadges.tsx index 6b7a991..44f108f 100644 --- a/src/components/ProviderBadges.tsx +++ b/src/components/ProviderBadges.tsx @@ -1,4 +1,4 @@ -import { posterUrl } from "@/lib/images"; +import { posterUrl } from "@/lib/image-url"; /** * Streamingdienste als Badges. diff --git a/src/lib/image-url.ts b/src/lib/image-url.ts new file mode 100644 index 0000000..e604783 --- /dev/null +++ b/src/lib/image-url.ts @@ -0,0 +1,42 @@ +/** + * Bild-URLs und ihre Prüfung. + * + * Bewusst **ohne** `server-only`: Posterkacheln werden auch aus + * Client-Komponenten heraus gerendert (etwa in der sortierbaren Liste eines + * Universums). Alles, was tatsächlich den Server braucht — Abruf bei TMDB und + * der Dateicache — liegt in `images.ts`. + */ + +/** Zugelassene Größen — verhindert, dass über den Parameter beliebige URLs entstehen. */ +export const IMAGE_SIZES = [ + "w92", + "w154", + "w185", + "w300", + "w342", + "w500", + "w780", + "w1280", + "original", +] as const; + +export type ImageSize = (typeof IMAGE_SIZES)[number]; + +const PATH_PATTERN = /^\/[A-Za-z0-9]{8,64}\.(jpg|jpeg|png|svg|webp)$/; + +export function isValidImagePath(path: string): boolean { + return PATH_PATTERN.test(path); +} + +export function isValidSize(size: string): size is ImageSize { + return (IMAGE_SIZES as readonly string[]).includes(size); +} + +/** URL des eigenen Bild-Endpunkts — überall im UI statt der TMDB-Adresse benutzen. */ +export function posterUrl( + path: string | null | undefined, + size: ImageSize = "w342", +): string | null { + if (!path || !isValidImagePath(path)) return null; + return `/api/image?size=${size}&path=${encodeURIComponent(path)}`; +} diff --git a/src/lib/images.ts b/src/lib/images.ts index d6561ed..36ff997 100644 --- a/src/lib/images.ts +++ b/src/lib/images.ts @@ -4,6 +4,7 @@ import { mkdir, readFile, rename, stat, writeFile } from "node:fs/promises"; import { join } from "node:path"; import { env } from "./env"; import { IMAGE_BASE } from "./tmdb"; +import type { ImageSize } from "./image-url"; /** * Lokaler Bild-Zwischenspeicher. @@ -12,32 +13,13 @@ import { IMAGE_BASE } from "./tmdb"; * eigenen Server ausgeliefert. Das spart Anfragen, macht die Oberfläche auch * ohne Internetverbindung nutzbar und verhindert, dass jeder Seitenaufruf * einen Abruf bei einem Dritten auslöst. + * + * Die URL-Bildung selbst steht in `image-url.ts` — die brauchen auch + * Client-Komponenten, und sie darf deshalb nicht hinter `server-only` liegen. */ -/** Zugelassene Größen — verhindert, dass jemand über den Parameter beliebige URLs baut. */ -export const IMAGE_SIZES = [ - "w92", - "w154", - "w185", - "w300", - "w342", - "w500", - "w780", - "w1280", - "original", -] as const; - -export type ImageSize = (typeof IMAGE_SIZES)[number]; - -const PATH_PATTERN = /^\/[A-Za-z0-9]{8,64}\.(jpg|jpeg|png|svg|webp)$/; - -export function isValidImagePath(path: string): boolean { - return PATH_PATTERN.test(path); -} - -export function isValidSize(size: string): size is ImageSize { - return (IMAGE_SIZES as readonly string[]).includes(size); -} +export { IMAGE_SIZES, isValidImagePath, isValidSize, posterUrl } from "./image-url"; +export type { ImageSize } from "./image-url"; function cacheDir() { return join(env.dataDir, "images"); @@ -99,9 +81,3 @@ export async function getCachedImage(size: ImageSize, path: string): Promise