From ef1f2e97883b0f9dcc3c2a56e35adb7977892775 Mon Sep 17 00:00:00 2001 From: Yann Date: Wed, 22 Jul 2026 11:26:52 -0700 Subject: [PATCH] Add draft flag for blog posts Astro has no native draft support (markdown.drafts was removed in v4), so gate posts behind a schema flag instead. Drafts render in dev and are filtered out of production builds: listing, tag pages, routes, RSS, sitemap. All blog reads go through getBlogPosts() in scripts/content.js so the filter cannot be forgotten on a new call site. It lives outside utilities.js because it imports astro:content, which must stay out of client bundles. Marks the container queries post as a draft. Co-Authored-By: Claude Opus 4.8 --- src/content.config.ts | 2 ++ .../blog/css-container-queries-examples.mdx | 1 + src/pages/blog/[...slug].astro | 5 +++-- src/pages/blog/index.astro | 4 ++-- src/pages/blog/tags/[tag].astro | 5 +++-- src/pages/blog/tags/index.astro | 4 ++-- src/pages/rss.xml.js | 4 ++-- src/scripts/content.js | 18 ++++++++++++++++++ 8 files changed, 33 insertions(+), 10 deletions(-) create mode 100644 src/scripts/content.js diff --git a/src/content.config.ts b/src/content.config.ts index 1dba693..b5de7f1 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -18,6 +18,8 @@ const blogCollection = defineCollection({ }) .optional(), tags: z.array(z.string()), + // Hidden from production builds, still visible in dev. See scripts/content.js. + draft: z.boolean().default(false), }), }); diff --git a/src/content/blog/css-container-queries-examples.mdx b/src/content/blog/css-container-queries-examples.mdx index 6ed505e..5d13abd 100644 --- a/src/content/blog/css-container-queries-examples.mdx +++ b/src/content/blog/css-container-queries-examples.mdx @@ -4,6 +4,7 @@ pubDate: 2026-03-02 author: "Yann" tags: ["css", "queries"] description: "Reading the specs and the nitty griddy details on MDN is great to get a solid understanding of container queries. But it's a lot faster to do if you got the gist of it through a few carefully selected examples that **show you** the basics." +draft: true --- import CanIUse from "../../components/_CanIUse.astro"; diff --git a/src/pages/blog/[...slug].astro b/src/pages/blog/[...slug].astro index 8b21e36..c804b5d 100644 --- a/src/pages/blog/[...slug].astro +++ b/src/pages/blog/[...slug].astro @@ -1,9 +1,10 @@ --- -import { getCollection, render } from 'astro:content'; +import { render } from 'astro:content'; import MarkdownPostLayout from '../../layouts/BlogPostLayout.astro'; +import { getBlogPosts } from '../../scripts/content.js'; export async function getStaticPaths() { - const blogEntries = await getCollection('blog'); + const blogEntries = await getBlogPosts(); return blogEntries.map((entry) => ({ params: { slug: entry.id }, props: { entry }, diff --git a/src/pages/blog/index.astro b/src/pages/blog/index.astro index 4aa60de..97e217c 100644 --- a/src/pages/blog/index.astro +++ b/src/pages/blog/index.astro @@ -1,12 +1,12 @@ --- -import { getCollection } from 'astro:content'; import { createMarkdownProcessor } from '@astrojs/markdown-remark'; +import { getBlogPosts } from '../../scripts/content.js'; import BaseLayout from '../../layouts/BaseLayout.astro'; import CardList from '../../components/CardList.astro'; import Card from '../../components/Card.astro'; const title = 'mCSS Blog'; -const allPosts = await getCollection('blog'); +const allPosts = await getBlogPosts(); allPosts.sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime()); const md = await createMarkdownProcessor(); diff --git a/src/pages/blog/tags/[tag].astro b/src/pages/blog/tags/[tag].astro index dccaff1..a9c3f4a 100644 --- a/src/pages/blog/tags/[tag].astro +++ b/src/pages/blog/tags/[tag].astro @@ -1,11 +1,12 @@ --- -import { getCollection, type CollectionEntry } from 'astro:content'; +import { type CollectionEntry } from 'astro:content'; import { createMarkdownProcessor } from '@astrojs/markdown-remark'; import BaseLayout from '../../../layouts/BaseLayout.astro'; import { slugify } from '../../../scripts/utilities'; +import { getBlogPosts } from '../../../scripts/content.js'; export async function getStaticPaths() { - const allPosts = await getCollection('blog'); + const allPosts = await getBlogPosts(); // Group by slug so tags that differ only in case or spacing share one page. // The first spelling encountered becomes the display name. diff --git a/src/pages/blog/tags/index.astro b/src/pages/blog/tags/index.astro index 1baad21..67ab358 100644 --- a/src/pages/blog/tags/index.astro +++ b/src/pages/blog/tags/index.astro @@ -1,10 +1,10 @@ --- -import { getCollection } from 'astro:content'; import BaseLayout from '../../../layouts/BaseLayout.astro'; import Tags from '../../../components/Tags.astro'; +import { getBlogPosts } from '../../../scripts/content.js'; -const allPosts = await getCollection('blog'); +const allPosts = await getBlogPosts(); const tags = [...new Set(allPosts.map((post) => post.data.tags).flat())]; --- diff --git a/src/pages/rss.xml.js b/src/pages/rss.xml.js index 4ef404c..19ad465 100644 --- a/src/pages/rss.xml.js +++ b/src/pages/rss.xml.js @@ -1,8 +1,8 @@ import rss from "@astrojs/rss"; -import { getCollection } from "astro:content"; +import { getBlogPosts } from "../scripts/content.js"; export async function GET(context) { - const blog = await getCollection("blog"); + const blog = await getBlogPosts(); return rss({ title: "mCSS News", description: "Stay up to date with mCSS updates", diff --git a/src/scripts/content.js b/src/scripts/content.js new file mode 100644 index 0000000..54a6d4c --- /dev/null +++ b/src/scripts/content.js @@ -0,0 +1,18 @@ +// CONTENT HELPERS +// Kept separate from utilities.js: this module imports `astro:content`, so it +// must never be pulled into a client-side bundle. + +import { getCollection } from "astro:content"; + +/** + * Blog posts, with drafts hidden in production builds and visible in dev. + * Always use this instead of getCollection("blog") so a draft cannot leak + * into a listing, a tag page, the RSS feed, or the sitemap. + * + * @returns {Promise[]>} + */ +export async function getBlogPosts() { + return getCollection("blog", ({ data }) => + import.meta.env.PROD ? !data.draft : true, + ); +}