From c83bbfbc35ab98d5c5bf154b701af498eef11b76 Mon Sep 17 00:00:00 2001 From: Marco Date: Wed, 29 Jul 2026 23:10:07 +0000 Subject: [PATCH] Filter draft/scheduled posts out of public blog fetches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit getBlogPosts()/getPostBySlug() now only return status='published' posts for normal requests — draftMode's live preview passes {draft:true} to bypass it, same as before. Backend-side scheduling in the payload repo (status/scheduledPublishAt + a per-minute autopublish job). Co-Authored-By: Claude Sonnet 5 --- app/lib/payload.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/lib/payload.ts b/app/lib/payload.ts index 5f4e301..1af5c36 100644 --- a/app/lib/payload.ts +++ b/app/lib/payload.ts @@ -49,6 +49,11 @@ type PayloadPost = { export async function getBlogPosts(limit = 3): Promise { const params = new URLSearchParams({ "where[tenant.slug][equals]": TENANT_SLUG, + // Draft/scheduled posts never appear publicly — same "filter, not + // access-control" pattern as Products.active. Live preview + // (LivePostContent.tsx) bypasses this entirely since it fetches the + // one specific document by id directly, not through this list. + "where[status][equals]": "published", sort: "-featured,-publishedAt", depth: "2", limit: String(limit), @@ -144,6 +149,10 @@ export async function getPostBySlug(slug: string, options?: { draft?: boolean }) depth: "2", limit: "1", }); + // Draft/scheduled posts 404 for a normal visitor — draftMode's preview + // (options.draft, wired from the page's own draftMode() call) is the + // one legitimate way to view one before its scheduledPublishAt fires. + if (!options?.draft) params.set("where[status][equals]", "published"); const res = await fetch(`${PAYLOAD_URL}/api/posts?${params}`, livePreviewCacheOption(Boolean(options?.draft))); if (!res.ok) {