diff --git a/app/agb/page.tsx b/app/agb/page.tsx index 25cfb7a..8e18924 100644 --- a/app/agb/page.tsx +++ b/app/agb/page.tsx @@ -17,9 +17,9 @@ export const metadata: Metadata = { }; export default async function AgbPage() { - const page = await getLegalPage("agb"); - const headings = page ? extractHeadings(page.content) : []; const { isEnabled: isPreview } = await draftMode(); + const page = await getLegalPage("agb", { draft: isPreview }); + const headings = page ? extractHeadings(page.content) : []; return ( <> diff --git a/app/blog/[slug]/page.tsx b/app/blog/[slug]/page.tsx index b4f02af..b02567f 100644 --- a/app/blog/[slug]/page.tsx +++ b/app/blog/[slug]/page.tsx @@ -39,7 +39,8 @@ export default async function BlogDetailPage({ params: Promise<{ slug: string }>; }) { const { slug } = await params; - const post = await getPostBySlug(slug); + const { isEnabled: isPreview } = await draftMode(); + const post = await getPostBySlug(slug, { draft: isPreview }); if (!post) notFound(); // "Weiterlesen" — any other post, most recent first. Not the current @@ -47,7 +48,6 @@ export default async function BlogDetailPage({ // showing a fake/duplicate card when this is the only post. const otherPosts = await getBlogPosts(4); const nextPost = otherPosts.find((p) => p.slug !== post.slug) ?? null; - const { isEnabled: isPreview } = await draftMode(); return ( <> diff --git a/app/challenge/page.tsx b/app/challenge/page.tsx index 01546a8..1b9b5df 100644 --- a/app/challenge/page.tsx +++ b/app/challenge/page.tsx @@ -169,8 +169,8 @@ function EmailCapture({ buttonLabel = "Challenge starten" }: { buttonLabel?: str } export default async function ChallengePage() { - const testimonials = await getTestimonials("challenge"); const { isEnabled: isPreview } = await draftMode(); + const testimonials = await getTestimonials("challenge", { draft: isPreview }); return ( <> diff --git a/app/datenschutz/page.tsx b/app/datenschutz/page.tsx index ea27ef1..ce15ab6 100644 --- a/app/datenschutz/page.tsx +++ b/app/datenschutz/page.tsx @@ -16,9 +16,9 @@ export const metadata: Metadata = { }; export default async function DatenschutzPage() { - const page = await getLegalPage("datenschutz"); - const headings = page ? extractHeadings(page.content) : []; const { isEnabled: isPreview } = await draftMode(); + const page = await getLegalPage("datenschutz", { draft: isPreview }); + const headings = page ? extractHeadings(page.content) : []; return ( <> diff --git a/app/impressum/page.tsx b/app/impressum/page.tsx index cb26c47..7d1c533 100644 --- a/app/impressum/page.tsx +++ b/app/impressum/page.tsx @@ -16,9 +16,9 @@ export const metadata: Metadata = { }; export default async function ImpressumPage() { - const page = await getLegalPage("impressum"); - const headings = page ? extractHeadings(page.content) : []; const { isEnabled: isPreview } = await draftMode(); + const page = await getLegalPage("impressum", { draft: isPreview }); + const headings = page ? extractHeadings(page.content) : []; return ( <> diff --git a/app/lib/payload.ts b/app/lib/payload.ts index 4af7e99..71e7dab 100644 --- a/app/lib/payload.ts +++ b/app/lib/payload.ts @@ -1,16 +1,20 @@ -import { draftMode } from "next/headers"; import { formatPrice } from "./format"; const PAYLOAD_URL = process.env.PAYLOAD_URL || "https://payload.mk360.de"; const TENANT_SLUG = "einfach-produktiv"; // Used only by the fetchers backing Live Preview (posts, legal pages, -// testimonials) — Draft Mode is enabled exclusively while a document is -// open in the Payload admin's Live Preview iframe, so bypassing the normal -// 60s ISR cache there doesn't affect ordinary site visitors at all. -async function livePreviewCacheOption(): Promise<{ cache: "no-store" } | { next: { revalidate: 60 } }> { - const { isEnabled } = await draftMode(); - return isEnabled ? { cache: "no-store" } : { next: { revalidate: 60 } }; +// testimonials) — callers pass `draft: true` only while Draft Mode is +// enabled (a document open in the Payload admin's Live Preview iframe), +// bypassing the normal 60s ISR cache there without affecting ordinary +// visitors. Deliberately does NOT call next/headers' draftMode() itself +// here — this module's mapping functions/types (mapPayloadTestimonial, +// mapPayloadPost, etc.) are also imported by "use client" components +// (LiveTestimonialsGrid.tsx, LivePostContent.tsx), and next/headers is a +// server-only import that breaks the client bundle the moment anything +// in this file touches it, even indirectly. +function livePreviewCacheOption(draft: boolean): { cache: "no-store" } | { next: { revalidate: 60 } } { + return draft ? { cache: "no-store" } : { next: { revalidate: 60 } }; } export type BlogPost = { @@ -119,7 +123,7 @@ export function mapPayloadPost(doc: PayloadPostDetail): PostDetail { }; } -export async function getPostBySlug(slug: string): Promise { +export async function getPostBySlug(slug: string, options?: { draft?: boolean }): Promise { const params = new URLSearchParams({ "where[tenant.slug][equals]": TENANT_SLUG, "where[slug][equals]": slug, @@ -127,7 +131,7 @@ export async function getPostBySlug(slug: string): Promise { limit: "1", }); - const res = await fetch(`${PAYLOAD_URL}/api/posts?${params}`, await livePreviewCacheOption()); + const res = await fetch(`${PAYLOAD_URL}/api/posts?${params}`, livePreviewCacheOption(Boolean(options?.draft))); if (!res.ok) { console.error(`getPostBySlug: Payload returned ${res.status} ${res.statusText}`); return null; @@ -538,7 +542,7 @@ export function mapPayloadTestimonial(doc: PayloadTestimonial): Testimonial { // hardcoded arrays kept manually in sync. The single-quote "photo band" // testimonials on /not-found and /bestellbestaetigung are a different // shape (no avatar/role) and are not part of this collection. -export async function getTestimonials(page: TestimonialsPage): Promise { +export async function getTestimonials(page: TestimonialsPage, options?: { draft?: boolean }): Promise { const params = new URLSearchParams({ "where[tenant.slug][equals]": TENANT_SLUG, "where[page][equals]": page, @@ -547,7 +551,7 @@ export async function getTestimonials(page: TestimonialsPage): Promise { +export async function getLegalPage(type: LegalPageType, options?: { draft?: boolean }): Promise { const params = new URLSearchParams({ "where[tenant.slug][equals]": TENANT_SLUG, "where[type][equals]": type, @@ -582,7 +586,7 @@ export async function getLegalPage(type: LegalPageType): Promise diff --git a/app/todo-cards/page.tsx b/app/todo-cards/page.tsx index 5c7255d..a46bc4c 100644 --- a/app/todo-cards/page.tsx +++ b/app/todo-cards/page.tsx @@ -33,8 +33,8 @@ export const metadata: Metadata = { }; export default async function TodoCardsPage() { - const testimonials = await getTestimonials("todo-cards"); const { isEnabled: isPreview } = await draftMode(); + const testimonials = await getTestimonials("todo-cards", { draft: isPreview }); return ( <> diff --git a/app/widerruf/page.tsx b/app/widerruf/page.tsx index 4c907a7..10b8eb6 100644 --- a/app/widerruf/page.tsx +++ b/app/widerruf/page.tsx @@ -17,9 +17,9 @@ export const metadata: Metadata = { }; export default async function WiderrufPage() { - const page = await getLegalPage("widerruf"); - const headings = page ? extractHeadings(page.content) : []; const { isEnabled: isPreview } = await draftMode(); + const page = await getLegalPage("widerruf", { draft: isPreview }); + const headings = page ? extractHeadings(page.content) : []; return ( <>