Skip to content

Hide draft blog posts from listings, feeds, and search#3065

Open
atharvadeosthale wants to merge 1 commit into
stardustfrom
stardust-blog-drafts
Open

Hide draft blog posts from listings, feeds, and search#3065
atharvadeosthale wants to merge 1 commit into
stardustfrom
stardust-blog-drafts

Conversation

@atharvadeosthale

Copy link
Copy Markdown
Member

What

Makes draft: true in a blog post's frontmatter hide the post everywhere except its own direct URL.

A draft post:

  • stays reachable at /blog/post/<slug> (renders normally)
  • is excluded from: the blog listing (plus the featured hero and pagination), author pages, category pages and chips, the "read next" list, RSS (/blog/rss.xml), the JSON feed (/blog/feed.json), and llms.txt / llms-full.txt
  • emits <meta name="robots" content="noindex"> so search engines drop it while the URL stays live

How

A single source of truth: a new publishedPosts (non-draft) export in blog/content.ts, reused by:

  • the blog layout context (covers author pages, category pages, and "read next")
  • getBlogEntries() for the listing and category chips
  • both feeds (rss.xml, feed.json)
  • both llms generators (matched by slug)

Post.svelte gains a draft prop and the conditional noindex meta. The post route itself is untouched, so the URL keeps working. unlisted behavior is unchanged.

Testing

  • svelte-check: 0 errors, 0 warnings
  • Verified with a browser (Playwright) and curl: the draft is absent from every listing/feed surface, present only at its direct URL with noindex; published posts are unaffected

Call-outs (not included here)

  • The sitemap (server/sitemap.js) builds from the route manifest, not frontmatter, so a draft URL can still appear in sitemap.xml. noindex still prevents indexing; excluding it from the sitemap would be a small follow-up.
  • On-site search is handled by an external Meilisearch crawler, which is not guaranteed to skip drafts from this change alone.

A blog post with `draft: true` in frontmatter stays reachable at its direct
URL but is excluded from the blog listing (plus featured hero and pagination),
author pages, category pages and chips, the "read next" list, RSS, the JSON
feed, and the llms.txt / llms-full.txt aggregators. It also emits a noindex
robots meta so search engines drop it while the URL stays live.

Implemented via a single publishedPosts (non-draft) source of truth in
blog/content.ts, reused across the blog layout context, feeds, and llms
generators. unlisted behavior is unchanged.
@appwrite

appwrite Bot commented Jun 26, 2026

Copy link
Copy Markdown

Appwrite Website

Project ID: 69d7efb00023389e8d27

Sites (1)
Site Status Logs Preview QR
 website
69d7f2670014e24571ca
Failed Failed View Logs Preview URL QR Code

Website (appwrite/website)

Project ID: 684969cb000a2f6c0a02

Sites (1)
Site Status Logs Preview QR
 website
68496a17000f03d62013
Processing Processing View Logs Preview URL QR Code


Tip

MCP server integration brings LLM superpowers to Claude Desktop and Cursor IDE

@greptile-apps

greptile-apps Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces a draft frontmatter flag that hides blog posts from all public surfaces (listing, RSS, JSON feed, llms.txt/llms-full.txt, "read next") while keeping the post reachable at its direct URL with a noindex meta tag. A single publishedPosts export in content.ts acts as the sole source of truth and is consistently consumed by the layout context, both feeds, and both llms generators.

  • publishedPosts is derived from the existing posts array by filtering !post.draft; getBlogEntries(), +layout.ts, and both feed/llms routes are updated to consume it.
  • Post.svelte gains a draft prop (default false) and injects <meta name="robots" content="noindex"> in <svelte:head> when the prop is true.
  • llms.txt / llms-full.txt build a Set of published slugs at module load and skip any /blog/post/… path not present in that set, correctly matching the flat slug structure used in content.ts.

Confidence Score: 4/5

Safe to merge; draft filtering is consistent across all public surfaces and the direct URL keeps working as intended.

All seven changed files apply the draft filter in a consistent, single-source-of-truth way. The one minor observation is the redundant !(post.draft ?? false) check inside Post.svelte's read next filter — the context already contains only published posts, so the extra guard never fires, but it could mislead a future reader. No data loss, no broken URLs, and no feed regressions were found.

No files require special attention; all changes are straightforward and self-consistent.

Important Files Changed

Filename Overview
src/routes/blog/content.ts Adds publishedPosts export filtering out draft posts; updates getBlogEntries() to use publishedPosts for both category chips and the listing. Clean single-source-of-truth approach.
src/markdoc/layouts/Post.svelte Adds draft prop and noindex meta tag; adds a now-redundant draft filter in the "read next" context slice (context is already draft-free), but this is harmless.
src/routes/blog/+layout.ts Switches posts in layout context from all posts to publishedPosts; propagates the draft filter to author pages, category pages, and "read next".
src/routes/blog/feed.json/+server.ts Switches to publishedPosts; also fixes total from Object.keys(posts).length to publishedPosts.length, which is cleaner and now correctly reflects published-only count.
src/routes/blog/rss.xml/+server.ts Straightforward swap to publishedPosts; draft posts are now excluded from the RSS feed.
src/routes/llms-full.txt/+server.ts Builds a Set of published slugs and skips any blog post path not present in that set; slug extraction logic is consistent with the slug construction in content.ts for the current flat post directory structure.
src/routes/llms.txt/+server.ts Same PUBLISHED_BLOG_SLUGS pattern as llms-full.txt; correctly skips draft blog post paths.

Comments Outside Diff (1)

  1. src/markdoc/layouts/Post.svelte, line 49-51 (link)

    P2 Redundant draft guard in "read next" filter

    The context is now populated from publishedPosts, which already excludes drafts, so !(post.draft ?? false) here will never evaluate to false and remove any post. It's a harmless defensive check, but it could silently mislead a future reader into thinking the context might still contain drafts.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Reviews (1): Last reviewed commit: "Hide draft blog posts from listings, fee..." | Re-trigger Greptile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant