Skip to content

Development notes

Previewing drafts without leaking them into the production build

How an Astro notes system makes Markdown drafts available through dev-only previews while keeping public routes, the sitemap, and static output limited to publishable content.

林予晴
Published
  • Content engineering
  • Astro
  • Draft previews
  • Static sites

Drafts need to be visible for review, but the public site must not treat them as published articles. Relying on a single if makes it easy to overlook one of the places where content can escape: the homepage, article pages, or the sitemap.

The implementation recorded in the original article on October 2, 2026 separates content into two paths. Local previews can read all valid Markdown; production builds can read only publishable articles. My main judgment is that draft safety requires several boundaries to hold together. Hiding draft cards from a list is not enough.

Separate the preview collection from the public collection

Local previews use getAllNotes(). It reads the entire Content Collection, including drafts and articles whose publication dates have not arrived. Authors can check titles, body text, the table of contents, and metadata before making an article public.

Public pages use getPublishedNotes() instead. It excludes drafts, entries without publishedAt, and articles with a publication date later than today in Taipei. The homepage, lists, public article pages, and sitemap are all built from this publishable collection. A Markdown file does not become a public page simply because it exists.

The distinction between the two functions is deliberate:

Use case Query Available content
Local editorial review getAllNotes() All articles that pass the schema, including drafts and entries with future publication dates
Public homepage and lists getPublishedNotes() Published articles whose publication dates have arrived
Public article pages getPublishedNotes() Routes are generated only for publishable articles
Public sitemap getPublishedNotes() Only publishable articles and the necessary list pages

The point is not to duplicate the content system for previews. It is to separate the two paths at the data entry point.

Inject preview routes only in Astro dev

The notes-preview integration checks Astro’s command first. It injects these two routes only when command === 'dev':

  • /preview/notes
  • /preview/notes/[locale]/[slug]

In build, preview, or sync mode, the integration injects no preview routes. Production output does not generate preview pages and then try to hide them with robots tags. Those routes have no entry point in the production output at all.

That distinction matters. noindex is an indexing instruction for crawlers. It cannot replace a routing boundary or turn a publicly reachable preview page into access control. Preview routes are for local editorial review, not an authentication system or a private file service.

noindex is a second layer of protection, not the only one

Both the preview list and individual previews set X-Robots-Tag: noindex, nofollow. The shared layout also emits noindex metadata. Individual previews additionally display a notice that the article is a draft and has not been published, so authors and readers do not mistake it for a public article.

These layers address different problems:

  • The integration ensures preview routes exist only in dev.
  • The response header tells search engines not to index the preview response.
  • HTML metadata preserves the noindex instruction in the page itself.
  • The preview notice tells the person reviewing the page its status directly.

If draft cards disappear from the homepage but public article routes still let getStaticPaths read all Markdown, drafts can retain guessable URLs. In the recorded implementation, public article routes use getPublishedNotes(), so this exit is subject to the same publication conditions.

Check the output again after the production build

Production builds do not register the preview integration’s routes. Article pages also use getStaticPaths to generate paths only from the publishable collection. The sitemap likewise includes only article URLs returned by getPublishedNotes().

The verify:notes check goes beyond whether a command succeeds. It reads the build artifacts and checks each of the following:

  • dist/ does not contain /preview/notes.
  • Slugs, titles, and body excerpts from drafts or articles with future publication dates have not leaked.
  • Public articles contain a title, a single h1, static body content, a canonical URL, and a sitemap URL.
  • Note lists and homepage card counts in all three languages match the actual publishable articles.
  • Languages with no public articles show a genuine empty state rather than invented articles.

These checks address three distinct risks: routes that should not exist, content that should not leak, and public articles that should actually be generated. Running the build alone cannot prove all of these conditions hold.

The review sequence for a draft

  1. Create valid Markdown frontmatter, keep draft: true, and leave publishedAt empty rather than inventing a publication date.
  2. Start Astro dev and inspect the content through /preview/notes or the corresponding individual preview URL.
  3. Read the title, summary, body, table of contents, and links. Check that internal handoff information has not found its way into public prose.
  4. Run the production build and confirm that the draft produces neither a public article page nor a sitemap URL.
  5. Run verify:notes to check the source Markdown, dist/, and sitemap together.
  6. After the engineering checks, the site owner still confirms the content, public scope, and visual reading experience. Without publication authorization, the article remains a draft.

The preview can establish that the author can see the article. The build and output checks can establish that production artifacts do not expose it. Both are necessary, and neither substitutes for the other.

What these boundaries do not solve

The preview routes described here have no accounts, permissions, or content encryption. They rely on local use of Astro dev and the rule that production builds do not register preview routes. Do not expose the development server to the public internet or treat a preview URL as a public article URL.

Likewise, noindex does not mean a search engine can never see the URL. It is only an indexing instruction supplied through the response and HTML. The first boundary is still keeping preview routes out of production output; noindex and status notices come second.

Draft previews make content available for checking before publication. They are not a way to quietly put unconfirmed content into production. With separate entry points for preview and public queries, authors can see an article first, while publication conditions determine when readers can see it.

Scope of this record

This article draws on the Astro integration, preview pages, Content Collection queries, public article routes, sitemap, and notes-output verification script. It describes the repository’s local review and production-build boundaries as recorded on October 2, 2026. It does not establish that any production host’s permission settings or search engine behavior have been verified.