Skip to content

Development notes

Why the homepage, note lists, article pages, and sitemap share a publication query

How the actual consumers of `getPublishedNotes` share publication eligibility rules across the homepage, note lists, article pages, pagination, and sitemap while handling presentation separately.

林予晴
Published
  • Content engineering
  • Astro
  • Content Collections
  • Publication queries

The homepage, note lists, article pages, and sitemap should not each decide independently whether an article is public. If one includes an extra draft or leaves out a published article, readers encounter conflicting versions of the same site.

The content system described here centralizes the question of which articles qualify for publication in getPublishedNotes(). Each page then decides how to present them. Publication eligibility should have one source of truth; differences between pages belong after the query, rather than in separate filtering rules at every entry point.

Define publication eligibility in one place

The current data flow can be simplified to:

Markdown
  → getAllNotes()
  → validateNoteIdentities()
  → selectPublishedNotes()
  → getPublishedNotes(locale?)
  → Homepage / note lists / article pages / sitemap / further reading

getAllNotes() first checks article identities, directory locales, and the heading hierarchy in each article body. getPublishedNotes() then obtains today’s date in the Taipei time zone and passes it to selectPublishedNotes() to determine publication eligibility.

An article must meet all of these conditions:

  • draft is false.
  • It has a publishedAt value.
  • publishedAt is on or before today’s date.
  • If the query specifies a language, locale matches it.

The eligible collection is sorted by publication time, newest first. Only when publication timestamps match to the second does stableId determine the order. These rules live in src/utils/notes-model.ts, not in conditionals scattered across Astro pages.

Centralizing this decision also has a practical benefit: every page uses the same Taipei date, draft condition, and sorting rule when it encounters the same article. A change to the publication contract can start with one content model, followed by another run of all output checks.

Different pages consume the same public collection

Sharing a query does not mean every page has to look the same. The query answers which articles may be visible; each consumer then uses the collection according to its own responsibilities.

The /notes paths below are Traditional Chinese route examples. The English and Japanese article routes are listed separately.

Consumer How it uses the collection The page’s own responsibility
Homepage getPublishedNotes(lang).slice(0, 3) Show up to three of the latest published articles in that language and link to the full list
/notes getPublishedNotes(lang) Present the full list, its empty state, and grouping or pagination within the list
/notes/page/[page] getPublishedNotes() with paginateNotes() Generate only the subsequent list pages that are actually needed
/notes/[slug] getStaticPaths() using getPublishedNotes() Generate only published Traditional Chinese article pages
/en/notes/[slug], /ja/notes/[slug] Filter by language after the same query Generate only translated article pages that actually exist and qualify for publication
Sitemap getPublishedNotes() Include only published article URLs and list pages that actually exist
Further reading in an article getPublishedNotes() for the same language Exclude the current article from the public collection, then sort by metadata

The homepage can therefore show only three articles, the list can show more, article pages can generate individual routes, and the sitemap can include every published article. These are different presentation responsibilities, not different publication rules.

Independent page filters can hide problems in the output

Suppose the homepage checks only draft and ignores a future publishedAt. A scheduled article could appear on the homepage early. If the sitemap simply scans every Markdown file, a draft could acquire a sitemap URL even though it has no card in the list. If article pages do not use the same collection, readers following a link could also reach an article that does not appear in the list.

These errors do not necessarily surface in TypeScript checks or a build of an individual page, because each page’s conditional logic may be valid code. The problem is that different entry points disagree about what counts as a published article.

Once the public query is centralized, each page only needs to handle its own output: article counts on the homepage, arrangement in the list, body content on article pages, and URLs in the sitemap. They do not need to duplicate the checks for draft, dates, and language.

Test both the model and the generated output

The notes tests first cover the content model itself, including these cases:

  • A draft may omit publishedAt.
  • A published article without publishedAt fails validation.
  • Invalid dates and an updatedAt earlier than the publication time are rejected.
  • Drafts and articles whose publication date has not arrived stay out of the public query.
  • Articles published in the same second retain a stable order through stableId.
  • Language filtering and article URLs use the same identity rules.
  • Preview routes are registered only in Astro dev.

verify:notes then reads back the actual build output to check that the counts and links agree across the source Markdown, dist/, sitemap, article pages, and homepages in all three languages. Neither layer replaces the other. Model tests confirm the conditions; output verification confirms that those conditions actually affect pages and the sitemap.

A shared publication query does not check content quality

getPublishedNotes() can determine whether an article meets the current publication conditions. It cannot tell whether the article is well written or establish that its date, title, or description is suitable for public release. It also cannot prove anything about SEO rankings, reader comprehension, traffic, or collaboration outcomes.

There is a static-site limitation as well: an article with a future publication date will not go live by itself at midnight. The site must be rebuilt once the date condition is satisfied before the new public collection is written into the homepage, lists, article pages, and sitemap.

That is why the publication query should stay simple. It answers one narrow question: which content may enter the production output. Content accuracy, the scope of public disclosure, visual reading quality, and authorization to publish remain separate checks.

Trace the consumers before changing the rules

When changing the notes publication workflow as the article collection grows, start by following the consumers of getPublishedNotes. Check that the homepage, lists, pagination, article pages, further reading, and sitemap still use the same public collection. Then check that each page only handles its own presentation responsibilities.

This is more reliable than adding a conditional to one page. Publication-rule problems usually involve the same article receiving different statuses at different outputs, rather than a single screen being wrong. One definition of eligibility keeps pages from giving conflicting answers.

Scope of this record

This article is based on the implementations of getAllNotes, getPublishedNotes, and selectPublishedNotes, along with the homepage, lists, pagination, article routes, sitemap, article layout, and notes verification scripts. It describes the content query contract recorded here; it does not treat a shared query as a guarantee of content quality, SEO, or reader outcomes.