| name | blog-post-template |
| description | Use when writing or auditing any blog post on your product's site (new post or existing). Enforces the BlogPostLayout container contract AND a Tier-1/Tier-2 anti-AI-slop checklist derived from 15-source Exa research (2026-05-14). Both checks are mandatory before any blog post merges. |
blog-post-template
Canonical contract for every blog post on your product's site. Two enforcement layers:
- Container contract — every post must use
BlogPostLayout (or render an equivalent surface). Locks the brutal-audit fixes from /blog/compression-benchmark-methodology so we never re-ship the "prototype-grade container, enterprise-grade content" mismatch.
- Anti-AI-slop rubric — Tier-1 + Tier-2 lexical bans, structural pattern bans, regex enforcement. Derived from a 15-source Exa research synthesis on AI writing tells (slop-forensics, EQ-Bench, Colin Gorrie's rhetorical analysis, fly.io / patio11 / stripe / supabase voice references).
If you are about to write, edit, or audit a blog post: load this skill first. Both checks fail closed — a missing TOC OR a Tier-1 word will block the merge.
Where the new post lives — pick the route first
Two routes exist. Default to the dynamic-markdown route unless the post needs bespoke React.
Route A — dynamic markdown (default, ~95% of posts)
Use for any prose-heavy post: news, research summaries, cost-analysis explainers, companion posts, tutorials with code samples. Content is a markdown template-literal string the existing pipeline renders.
Files to touch:
-
<web-app>/src/app/[locale]/(unauth)/blog/page-posts.ts — add a POSTS entry with this shape:
{
slug: 'kebab-case-slug',
href: '/blog/kebab-case-slug',
title: 'Concrete, specific headline',
excerpt: 'One or two sentences. Becomes the auto-generated TL;DR.',
date: 'YYYY-MM-DD',
readTime: 'N min',
category: 'Engineering' | 'Research' | 'Cost' | 'Technical' | 'Guide',
authorSlug: 'the operator',
internal: true,
}
Byline rendering rules (anonymous-byline directive, 2026-05-20).
The authorSlug field controls attribution, but the rendered byline must
observe the anonymous-byline directive — no personal identity:
- Permitted:
By the operator · 6 min read · 2026-05-20 (contributor name only)
- Permitted:
By the <your product> team · 6 min read · 2026-05-20
- Prohibited: any ownership / role title attached to the name —
Founder, operator, Owner, Creator, Built by, Maker of, Founder of <your product>
- Prohibited: personal photos, personal profile URLs, and any
employer/day-job references in the author block
- Prose voice: "we" throughout the body, even on personally-bylined posts
- E-E-A-T substitute: link to named data + commits + CHANGELOG + /trust page; never personal credentials
- Rule: use an anonymous/brand byline — no personal identity. An
anonymous or product-team byline reads as institutional and lets the
product's own results carry the credibility instead of a named individual.
-
<web-app>/src/app/[locale]/(unauth)/blog/[slug]/page.tsx — add a BLOG_CONTENT[slug] entry:
'kebab-case-slug': {
title: '…same as POSTS…',
date: '…',
readTime: '…',
category: '…',
content: `
## First H2 becomes a TOC entry (auto-generated)
Body markdown. Use \\\`backticked code\\\` for inline. Triple-backtick fences for blocks.
Tables, lists, links, headings — all supported by the existing sanitized HTML pipeline.
`,
},
What you get for free (BlogPostLayout populates these from POSTS metadata — no per-post wiring):
- Auto-generated TOC from every
## H2 heading
- TL;DR block built from
excerpt
- Cite this block (BibTeX + APA) built from
title + authorSlug + date
- Related-links sidebar (4 standard links)
- Contribute CTA grid (3 cards: Suggest edit / Discuss / Email)
- Linked author + word count + scroll-spy nav + anchor-linked H2s
What you do NOT get on this route:
<CopyableCode> buttons on code fences (deferred — needs MDX or client post-processor)
- Inline React components / charts / interactive widgets
- Custom
statusCallout / revisions log / extraChips (optional props exist but the dynamic route doesn't wire them by default; add per-post if needed)
Route B — standalone TSX (bespoke posts only)
Use ONLY when the post needs one or more of:
- Interactive widgets or charts that mount in the body
- Per-post
statusCallout banner (e.g. v0 — methodology only)
- A dated
revisions log table
<CopyableCode> blocks with sourceHref pinned to a commit SHA
- Custom data viz, embedded form, or any other React-only behavior
Reference implementation: <web-app>/src/app/[locale]/(unauth)/blog/compression-benchmark-methodology/page.tsx — copy its scaffolding. Wraps BlogPostLayout directly and passes every prop explicitly. Still needs a corresponding POSTS entry so it appears in the index, RSS, and /llms.txt.
Decision flow
Does the post need interactive React, a status callout, a revisions log,
or commit-pinned <CopyableCode>?
└── no → Route A (dynamic markdown). Add POSTS entry + BLOG_CONTENT entry. Done.
└── yes → Route B (standalone TSX). Copy compression-benchmark-methodology
scaffolding. Add POSTS entry. Wire BlogPostLayout props explicitly.
Container contract — every post uses BlogPostLayout
Component: <web-app>/src/components/blog/BlogPostLayout.tsx. Used by:
<web-app>/src/app/[locale]/(unauth)/blog/[slug]/page.tsx (dynamic markdown route)
<web-app>/src/app/[locale]/(unauth)/blog/compression-benchmark-methodology/page.tsx (standalone TSX posts — pattern to copy for any future bespoke post)
Required props every post must populate:
| Prop | Required? | Source |
|---|
post | yes | POSTS entry from <web-app>/src/app/[locale]/(unauth)/blog/page-posts.ts |
author | yes | authorForPost(post) from blog-data.ts |
dek | yes | 2-line summary under the title |
tocEntries | yes if 3+ <h2> sections | auto-derived from markdown via extractTocFromMarkdown in the dynamic route; explicit array for TSX posts |
wordCount | yes | ~N words — wordCountFromMarkdown(content) or hand-counted for TSX |
version | yes for methodology / benchmark posts | e.g. "Methodology v0.3" |
statusCallout | yes if the post has a v0 / "deprecated" / "partial" / "draft" status | amber <aside> above the TL;DR; never bury status disclosures mid-body |
tldr | yes for posts > 800 words | 3-5 bullet block (What / How / Status / Reproduce or post-appropriate variant) |
relatedLinks | yes if 2+ related artifacts exist | links to leaderboard, changelog, repo, etc. |
revisions | yes if post is versioned | dated table — satisfies the post's own "methodology changes require a new dated entry" commitment |
cite | yes for research/methodology posts | BibTeX + APA |
contributeCards | yes for research/methodology posts | 3 cards — Submit result / Propose mitigation / Report bug — linked to prefilled GitHub issues |
For posts that include reproducibility commands, verbatim prompts, or any code:
- Use
<CopyableCode> from <web-app>/src/components/blog/CopyableCode.tsx
- Pass
caption, language, and (for source-of-truth content) sourceHref + sourceLabel
"verbatim" is a claim — every code block tagged "verbatim" MUST carry a sourceHref to a commit-pinned GitHub URL
For every <h2> in a standalone TSX post:
- Add
id derived via the same slugifyHeading logic the dynamic route uses (<web-app>/src/app/[locale]/(unauth)/blog/[slug]/page.tsx)
- Add
scroll-mt-24 so anchor jumps clear the sticky nav
- Add the
group class + the hover-only ¶ anchor link
Anti-AI-slop rubric (mandatory before merge)
Derived from a 15-source Exa research synthesis on AI writing tells. Failing any Tier-1 check blocks the merge. Tier-2 is a cluster check — 3+ in 200 words = rewrite.
Tier 1 — kill on sight (regex below)
Every match is a sentence-level rewrite. No exceptions.
Lexical bans:
| Word/phrase | Replace with |
|---|
delve (any form) | dig into, examine, look at |
utilize (any form) | use |
leverage (verb) | use, take advantage of, apply |
facilitate (any form) | help, enable, let |
elucidate | explain, clarify |
embark | start, begin |
endeavor / endeavour | effort, try, attempt |
multifaceted | complex, varied (or describe the parts) |
tapestry (metaphorical) | describe the actual thing |
a testament to | shows, proves, demonstrates |
paradigm | model, approach, framework |
synergy / synergize | rewrite the sentence |
holistic | whole, full, end-to-end |
catalyze / catalyst | trigger, spark, cause |
realm (metaphorical) | area, field, domain |
landscape (metaphorical) | field, space, situation |
myriad | many, lots of |
plethora | many, a lot |
game-changer / game-changing | describe what specifically changed |
ever-evolving | changing, growing, shifting |
Punctuation tells:
- Em-dash overuse: 2 max per page. Every instance must pass the four-alternative test (could this be a period / comma / colon / parentheses instead?). Code blocks exempt. The em-dash is not banned outright — it's a habit-marker. Habit overuse is what flags AI authorship.
- Tricolon compulsion: 1 max per 500 words. "Fast, reliable, and affordable" pattern. If you find one, decide whether it earns its place; break others into prose.
- "Not just X, but Y" construction: zero tolerance. The single most overrepresented AI trigram per slop-forensics analysis. Restructure entirely.
- "It's not about X, it's about Y": zero tolerance. Same family.
Filler phrases (delete every instance, no exceptions):
It's worth noting that… / It's important to note that…
Notably, / Interestingly, (as paragraph openers)
Let's dive into… / Let's explore…
In conclusion, / To summarize,
Furthermore, / Moreover, / Additionally, (as transitions — replace with and or a new sentence)
In today's fast-paced/digital/modern world…
At the end of the day…
It goes without saying…
When it comes to…
In the realm of…
Cannot be overstated
We are excited/thrilled/pleased to announce
Tier 2 — cluster check (3+ in 200 words = rewrite section)
Fine individually, fatal in density:
robust, comprehensive, seamless/seamlessly, cutting-edge, innovative, streamline, empower, foster, enhance, elevate, optimize (metaphorical), scalable (as buzzword), pivotal, intricate, profound, resonate, underscore, harness, navigate (metaphorical), cultivate, bolster, galvanize, cornerstone
Structural patterns to fix manually (no regex catches these)
-
Cadence uniformity — read the first three paragraphs aloud. If every sentence resolves with the same weight and timing, the rhythm is AI. Mix sentence lengths deliberately. Short sentences. Then a longer one that builds through several clauses before arriving at a point.
-
Topic-sentence machine — every paragraph in shape: topic → elaboration → example → wrap-up. Vary the shape. Sometimes the point comes last. Sometimes a paragraph is one sentence.
-
Symmetry addiction — three pros + three cons, exactly five steps, equal-weight bullet items. Real topics are lumpy. Some sections are long because the material demands it; others are two sentences.
-
Transition-word paragraph openers — read the first word of every paragraph. If more than 2 consecutive start with However / Furthermore / Additionally / Moreover, rewrite. Human writers start paragraphs with the subject of the paragraph.
-
Hedge parade — may potentially help in some cases is slop. State what you know. If you don't know, say "we haven't measured this yet."
-
List abuse — bullets where prose is cleaner; every item starting with the same grammatical structure (Ensures…, Provides…, Enables…); exactly 3 or 5 items; nesting 3+ levels deep.
-
Boilerplate closer — As AI continues to evolve, one thing is clear: Cut every closer of this shape. Real conclusions take a position and stop.
-
Opening paragraph contract — The first three sentences must answer two questions: (1) "Is this article for someone like me?" and (2) "How will I benefit from reading it?" If paragraph two begins and neither question is answered, rewrite the opener. In today's rapidly evolving digital landscape… and all variants auto-fail. The fly.io blog opens with the actual claim. So does every post that reaches HN front page. This is not a style preference — it is the reader's threshold for not closing the tab.
Title pattern: use a benefit statement (A Simple Way to Write X) or an intriguing hook (How We Lost $2K to a Caching Bug) — not a category label (A Guide to Compression APIs). "Ultimate Guide" titles auto-fail. Source: https://refactoringenglish.com/blog/2025-hn-top-5/
Humanizing techniques — DO this
- Vary sentence length. Short sentences hit hard. Then a longer one carries the reader through several turns of thought before landing.
- State opinions directly. Engineering writing has positions. "This is the wrong approach." "We got this wrong for two years." "Most ORMs make this harder, not easier."
- Use specific numbers, dates, named technologies.
23% p95 reduction not significant performance improvement. shipped in March not recently released.
- Include failure and messiness. What broke. What surprised you. What the wrong first approach was. What took longer than expected.
- Write in first person with a real persona.
I and we. Identifiable author with identifiable opinions.
- One surprising sentence per major section. If no section surprises, the draft is too smooth.
- Quotable line per post. Write one sentence per post that could be screenshotted and shared with zero context. Without this, the post can be read but cannot be quoted. The HN-front-page pattern (Simon Willison, Sean Goedecke 2025 analysis) confirms a single quotable, contrarian, or counter-intuitive sentence drives share rates.
Citation discipline — inline at claim level
AI Overview selection rate: 34.9% for paragraphs with specific verifiable inline links vs. 3.2% for vague or footnoted references (Wellows 2026 analysis of 1,000+ AI Overviews — https://wellows.com/blog/google-ai-overviews-ranking-factors/).
Rules:
- Place the hyperlink on the specific claim, not in a "Sources" section at the end.
"A September 2025 Seer Interactive study found CTR dropped 61% [link]", not a numbered footnote.
- Paraphrase + inline link beats blockquote. Direct quotation > 2 sentences requires explicit attribution and creates copyright surface area. Paraphrase + link is always safer for both E-E-A-T and DMCA posture.
- Each claim-bearing paragraph should be self-contained. AI extraction prefers passages that stand alone — no
as mentioned above, no see section X, no "this builds on the previous point." A passage that requires context above it is invisible to AI Overview extraction.
- 134–167 words is the AI-Overview-extraction sweet spot for self-contained paragraphs (Wellows 2026). Over 200 words and the passage exceeds the extraction window; under 80 and there's not enough context to stand alone.
Anchor reference blogs (read these for voice)
When in doubt about voice, read a recent post from one of these:
- https://fly.io/blog/ — named authors, specific technical details, first-person voice with opinions, admitted failures, irregular sentence rhythm. Zero tricolons or "not just X but Y" constructions.
- https://kalzumeus.com / https://bitsaboutmoney.com (patio11) — deep domain specificity, zero hedging, every sentence contains information. No filler. No symmetry.
- https://stripe.com/blog/engineering — "We migrated 3.7 million lines of code in a single pull request" — specific numbers, named technologies, actual decision reasoning.
- https://supabase.com/blog — technical depth, honest trade-off discussion, named engineers.
Regex enforcement (run as pre-publish linter)
Paste this into the post text and grep. Every Tier-1 match must be addressed before merge.
# Punctuation tells
/—/g flag every em-dash in body prose (code blocks exempt)
# Tier 1 — kill on sight
/\b(delve|delves|delved|delving)\b/i
/\b(utilize|utilizes|utilized|utilizing|utilization)\b/i
/\b(leverage|leverages|leveraged|leveraging)\b/i
/\b(facilitate|facilitates|facilitated|facilitating)\b/i
/\b(elucidate|elucidates|elucidated|elucidating)\b/i
/\b(embark|embarks|embarked|embarking)\b/i
/\b(endeavor|endeavour|endeavors|endeavours)\b/i
/\b(multifaceted)\b/i
/\btapestry\b/i
/\ba testament to\b/i
/\b(paradigm|paradigms)\b/i
/\b(synergy|synergize|synergistic)\b/i
/\b(holistic)\b/i
/\b(catalyze|catalyzes|catalyzed|catalyst)\b/i
/\b(realm|realms)\b/i
/\b(myriad)\b/i
/\b(plethora)\b/i
/\b(game.?changer|game.?changing)\b/i
/\b(ever-evolving|ever evolving)\b/i
# Structural — zero tolerance
/\bit'?s not just\b.*\bbut\b/i
/\bit'?s not about\b.*\bit'?s about\b/i
/\bthe question isn'?t\b.*\bthe (?:real )?question is\b/i
# Filler openers — delete
/\bit'?s worth noting that\b/i
/\bit'?s important to note that\b/i
/^notably[,\s]/im
/^interestingly[,\s]/im
/\blet'?s (?:dive|delve) into\b/i
/^in conclusion[,\s]/im
/^to summarize[,\s]/im
/^furthermore[,\s]/im
/^moreover[,\s]/im
/^additionally[,\s]/im
/\bin today'?s (?:fast.paced|digital|modern|rapidly evolving)\b/i
/\bat the end of the day\b/i
/\bit goes without saying\b/i
/\bwhen it comes to\b/i
/\bin the realm of\b/i
/\bcannot be overstated\b/i
/\bwe are (?:excited|thrilled|pleased) to (?:announce|share|introduce)\b/i
# Tier 2 — cluster check (flag section if 3+ in 200 words)
/\b(robust|comprehensive|seamless|seamlessly|cutting.edge|innovative|innovation|streamline|empower|foster|enhance|elevate|optimize|scalable|pivotal|intricate|profound|resonate|underscore|harness|navigate|cultivate|bolster|galvanize|cornerstone)\b/i
Practical workflow:
- First pass — write the post the way you'd write it for a coworker. Don't self-edit toward "professional".
- Pre-publish lint — run the regex sweep. Address every Tier-1 hit.
- Cluster check — grep Tier-2 patterns. If any section has 3+ in 200 words, rewrite it.
- Read aloud — open the draft and read the first three paragraphs aloud. If you can't hear rhythm variance, mix sentence lengths.
- Surprise audit — does at least one sentence per major section surprise? If not, add concrete specifics or admit a failure.
- First-word audit — read the first word of every paragraph. If you see transition words in sequence, rewrite the openings.
- Em-dash audit — count em-dashes. If more than 2 per page, each one must pass the four-alternative test.
- Specificity audit — find every vague claim. Replace with a number, a date, or a named technology.
What this skill does NOT do
- Markdown→MDX migration for the dynamic route (deferred; current sanitized HTML pipeline is shipping)
- Code-block copy buttons for the dynamic route (deferred; needs MDX or a client-side post-processor — standalone TSX posts get
CopyableCode today)
- Inline figure / chart generation on Route A (Route B only)
- "Print as PDF" view
Historical: bulk em-dash + Tier-1 sweep across all 26 dynamic posts shipped 2026-05-14 (commit c3cdf2b, 153 → 9 em-dashes, every post ≤2). Future posts must clear the rubric at draft time so we never need a bulk sweep again.
Track these as separate plan items; this skill is for the writing-and-editing pass, not architecture migration.
When to invoke
- Writing a new blog post: invoke before drafting
- Editing an existing post: invoke before merging changes
- Auditing the blog index after a content sprint: invoke as part of the audit
- Reviewing a co-author's draft: invoke to anchor the rubric
- Any time the phrase "let's dive into" enters a draft — invoke immediately