Publishing a Blog Post
Publishing a Blog Post
Section titled “Publishing a Blog Post”Use this page when you need to add a post to the Hyper blog or take an existing draft live. It covers where post files live, which frontmatter fields are required, how drafts behave in each environment, and what to check before you merge.
Blog posts are Markdown files in the website repository. There is no CMS and no publishing UI — a post goes live when a commit containing it deploys to production.
When to use this page
Section titled “When to use this page”- You are writing a new post and need the file location, frontmatter, and review steps.
- You have a draft in the repository and want to publish it.
- You need to correct a live post, or add an updated date to one.
Before you start
Section titled “Before you start”- Clone the website repository and install dependencies with
bun install. - Decide the byline. The
authorfield prints verbatim on the post and the index card. - Decide whether the post ships as a draft first. Drafts are visible locally and excluded from production, so a draft can be merged safely before it is ready to announce.
- Have the description written. It is used as the meta description, the social share text, and the card summary, so it needs to work as a standalone sentence.
Where posts live
Section titled “Where posts live”| Path | Purpose |
|---|---|
src/content/blog/<slug>.md |
The post itself. The filename becomes the URL slug. |
src/content.config.ts |
The blog collection schema. Frontmatter is validated against it at build time. |
src/lib/blog.ts |
Shared helpers: post ordering, draft filtering, reading time, tag lookups. |
src/lib/tag-slug.ts |
Tag normalization and aggregation, shared with the collection schema. |
src/pages/blog/index.astro |
Post index, topic list, and empty state. |
src/components/blog/PostListItem.astro |
A single row in the post list, used by the index and tag pages. |
src/pages/blog/[slug].astro |
Individual post route, Open Graph metadata, and structured data. |
src/pages/blog/tags/[tag].astro |
One page per tag, generated from the tags in use. |
src/pages/blog/rss.xml.ts |
The RSS feed at /blog/rss.xml. |
The filename is the URL. src/content/blog/on-device-inference.md publishes to /blog/on-device-inference. Use lowercase words separated by hyphens, and do not rename a file after it has been live — that breaks inbound links and the feed.
Frontmatter reference
Section titled “Frontmatter reference”| Field | Required | Notes |
|---|---|---|
title |
Yes | 70 characters or fewer. Sentence-style capitalization, no trailing period. |
description |
Yes | 160 characters or fewer. One complete sentence. |
pubDate |
Yes | YYYY-MM-DD. Controls sort order on the index and in the feed. |
updatedDate |
No | YYYY-MM-DD. Adds an “Updated” line to the post header. |
draft |
No | Defaults to false. Set true to keep a post out of production. |
tags |
No | Array of strings. Each tag used by a published post generates a tag page, so a tag must contain at least one letter or number. Spellings that normalize to the same slug share one page. |
author |
No | Defaults to Hyper Team. |
authorRole |
No | Shown under the byline in the post list, e.g. Co-founder. Omitted if unset. |
authorImage |
No | Path to a headshot in src/assets, relative to the post file. Falls back to an initials monogram. |
ogImage |
No | Absolute path or URL for the social share image. Falls back to the site default. |
The build fails on a schema violation rather than shipping a broken post, so a title over 70 characters or a missing pubDate is caught by bun run build.
---title: "Introducing Hyper"description: "Why we're building a meeting platform where the AI runs on your device."pubDate: 2026-08-12draft: truetags: ["announcement", "product"]author: "Zach Sherman"---Write the post
Section titled “Write the post”- Create the file at
src/content/blog/<slug>.md. - Add the frontmatter block. Start with
draft: trueunless the post is already reviewed and ready. - Write the body in Markdown, starting with a paragraph rather than a heading. The
titlefield renders the page’s onlyh1, so a#heading in the body would produce a second one. - Use
##for sections and###for subsections. Headings at those two levels build the “On this page” sidebar, which appears once a post has more than one section. - Keep the first paragraph strong. It renders at a larger size as the post’s lead, and it is the text most readers see before deciding to continue.
- Link internally with absolute paths such as
/docs/getting-started/overviewor/pricing. Link to the waitlist with/#waitlist.
Reading time is calculated from the body at build time. Do not write it into the post.
Adding images
Section titled “Adding images”Author headshots go in src/assets and are referenced from authorImage as a path relative to the post file, such as ../../assets/authors/zach.jpg. The collection schema validates the file exists and Astro optimizes it, so a missing or misspelled path fails the build instead of rendering a broken image. Posts without a headshot show an initials monogram, which is the intended default rather than a placeholder to fill in.
Put other post images in src/assets and reference them through Astro’s asset pipeline rather than dropping files in public/. Give every image explicit dimensions so the page does not shift while it loads, and follow the image workflow in the repository’s AGENTS.md. Use public/ only when an asset needs a fixed, permanent URL — for example an ogImage you intend to share directly.
Preview locally
Section titled “Preview locally”Start the dev server and open /blog:
bun run devBoth servers run in the background under coding agents, each with its own subcommands: astro dev status, astro dev logs, and astro dev stop for the dev server, and astro preview status, astro preview logs, and astro preview stop for the preview server.
Check the post at three sizes — mobile, tablet, and desktop — and check both themes with the toggle in the header. The blog inherits the site’s dark and light palettes, so a post that looks correct in one theme can still fail in the other, most often in code blocks and tables.
Draft behavior
Section titled “Draft behavior”| Environment | Drafts visible | Notes |
|---|---|---|
bun run dev |
Yes | Drafts render with a Draft badge on the card and in the post header. |
bun run build and production |
No | Drafts are excluded from the index, tag pages, the feed, and routing entirely. |
Because drafts are excluded from routing, a production build generates no page for a draft post. This applies to every environment built with astro build — including bun run preview and the staging deployment — so a draft URL returns a 404 there too, not only in production. Review drafts locally with bun run dev, which is the only place they render.
Publish
Section titled “Publish”- Remove
draft: true, or set it tofalse. - Set
pubDateto the intended publish date. If the post sat in review, update it — this is the date readers see. - Run the verification commands below.
- Open a pull request with a Conventional Commits title, such as
docs(blog): add on-device inference post. - Merge and deploy. The post, the tag pages, the RSS feed, and the sitemap all regenerate from the same build.
Verify before merge
Section titled “Verify before merge”bun run checkbun run buildastro check catches type and template errors. bun run build validates frontmatter against the collection schema and confirms the post, its tag pages, and the feed all generate.
After the build, confirm:
- The post appears at
/blog/<slug>with the correct date, byline, and reading time. - The post is listed on
/blog, newest first. - Each tag links to a tag page that lists the post.
/blog/rss.xmlincludes the post.- Tables and code blocks are readable in both themes and scroll rather than overflow on mobile.
Review checklist
Section titled “Review checklist”- Title is 70 characters or fewer and reads as a specific claim, not a category.
- Description stands alone as one sentence and is 160 characters or fewer.
- First paragraph says what the post is about without requiring the title as setup.
- Product claims match what Hyper currently does. Availability language matches the docs.
- No invented metrics, customer names, or dates.
- Internal links resolve, and external links use
https. - Tags reuse existing tags where one fits, rather than creating a near-duplicate.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause | Fix |
|---|---|---|
| Build fails with a Zod or collection error naming the post | Frontmatter violates the schema in src/content.config.ts |
Read the field name in the error. Most often a title over 70 characters, a description over 160, or a malformed pubDate. |
Post is missing from /blog after deploy |
draft is still true |
Set draft: false or remove the field, then rebuild. |
| Post is at the wrong position on the index | pubDate is older than expected |
The index sorts by pubDate descending. Correct the date. |
Two h1 headings on the page |
The body starts with a # heading |
Remove it. title already renders the h1. |
| “On this page” sidebar is missing | The post has one section or none | Expected. The sidebar appears only when a post has more than one ## or ### heading. |
| Tag page 404s | The tag was changed or removed from every post | Tag pages are generated from tags in use. Restore the tag or update the inbound link. |
| Share preview shows the default site image | No ogImage set |
Expected fallback. Add ogImage to use a post-specific image. |
Related tasks
Section titled “Related tasks”- Markdown Guide — writing and structure rules for docs pages
- Page Template — the canonical docs page shape
- Contributing — core rules for the docs set