Fix RSC build break: keep next/headers out of lib/payload.ts
payload.ts's mapping functions/types are also imported by "use client" components (LiveTestimonialsGrid, LivePostContent) — importing next/headers anywhere in that module made it unbundlable for the client, breaking the production build. draftMode() is now only ever called in the Server Component pages themselves; they pass the resulting boolean into getPostBySlug/getLegalPage/getTestimonials as a plain `draft` option.
This commit is contained in:
+2
-2
@@ -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 (
|
||||
<>
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
+17
-13
@@ -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<PostDetail | null> {
|
||||
export async function getPostBySlug(slug: string, options?: { draft?: boolean }): Promise<PostDetail | null> {
|
||||
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<PostDetail | null> {
|
||||
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<Testimonial[]> {
|
||||
export async function getTestimonials(page: TestimonialsPage, options?: { draft?: boolean }): Promise<Testimonial[]> {
|
||||
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<Testimoni
|
||||
limit: "20",
|
||||
});
|
||||
|
||||
const res = await fetch(`${PAYLOAD_URL}/api/testimonials?${params}`, await livePreviewCacheOption());
|
||||
const res = await fetch(`${PAYLOAD_URL}/api/testimonials?${params}`, livePreviewCacheOption(Boolean(options?.draft)));
|
||||
if (!res.ok) {
|
||||
console.error(`getTestimonials: Payload returned ${res.status} ${res.statusText}`);
|
||||
return [];
|
||||
@@ -574,7 +578,7 @@ type PayloadLegalPage = {
|
||||
attachment: { url: string; title: string } | number | null;
|
||||
};
|
||||
|
||||
export async function getLegalPage(type: LegalPageType): Promise<LegalPage | null> {
|
||||
export async function getLegalPage(type: LegalPageType, options?: { draft?: boolean }): Promise<LegalPage | null> {
|
||||
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<LegalPage | nul
|
||||
limit: "1",
|
||||
});
|
||||
|
||||
const res = await fetch(`${PAYLOAD_URL}/api/legal-pages?${params}`, await livePreviewCacheOption());
|
||||
const res = await fetch(`${PAYLOAD_URL}/api/legal-pages?${params}`, livePreviewCacheOption(Boolean(options?.draft)));
|
||||
if (!res.ok) {
|
||||
console.error(`getLegalPage: Payload returned ${res.status} ${res.statusText}`);
|
||||
return null;
|
||||
|
||||
@@ -39,8 +39,8 @@ export const metadata: Metadata = {
|
||||
// weekly newsletter), so this page resolves both at once instead of
|
||||
// picking a new URL and leaving one of them still dangling.
|
||||
export default async function NewsletterPage() {
|
||||
const testimonials = await getTestimonials("newsletter");
|
||||
const { isEnabled: isPreview } = await draftMode();
|
||||
const testimonials = await getTestimonials("newsletter", { draft: isPreview });
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user