| name | blog-writing |
| description | Writes and edits BabyPickr blog posts with a human parent voice, homepage-aligned layout/theme, and without common AI writing tells (no em dashes or double hyphens). Use when creating or revising blog articles, guides, roundup posts, SEO content under app/blog/, or when the user mentions blog posts, editorial voice, or sounding like AI. |
BabyPickr blog writing
Read this skill before drafting or editing any blog content for this repo.
Canonical reference implementation: app/blog/best-strollers-city-apartments/page.tsx
Shared components: components/blog/ (BlogCatalogPrice, BlogArticleDisclosure, BlogArticleShell, …) · Blog CSS utilities: app/globals.css (search Blog article typography)
Testing (required)
Run tests yourself after catalog picks, pickMeta, TOC anchors, or lib/blog/catalogPicks.ts changes. Do not finish with failing tests or ask the user to run them unless shell is unavailable.
| When | Command |
|---|
| Blog picks, anchors, or catalog sync | npm test (includes lib/blog/blogPages.test.ts, lib/blog/catalogPicks.test.ts) |
| New or heavily edited post | npm test then npm run build |
lib/blog/blogPages.test.ts scans every app/blog/*/page.tsx guide: catalog ids, TOC ↔ picks, internal href="#…" anchors, and buildCatalogPicks / buildMemorialDayPick smoke tests. Fix failures before marking complete.
Voice goal
Write like a tired-but-sharp parent who has actually hauled a stroller up a walk-up and compared folds in a hallway. Not like a content mill. BabyPickr blogs are practical guides tied to real catalog data, with opinions you can defend.
How to avoid sounding like AI
To prevent your writing from triggering AI detectors or feeling robotic, watch out for these distinct AI "tells":
Cliche Openings: Avoid starting your paragraphs with phrases like "In today's fast-paced digital world..." or "In conclusion..."
The "Rule of Three": AI has a weird obsession with listing exactly three things or breaking contrasts into triplets (e.g., "Not just X, and not just Y, but also Z.")
Bland, Neutral Tone: AI writing tools are built to offend no one and please everyone. Make sure to inject your own personal opinions, humor, and unique voice into your writing.
Impersonal Stories: Real human writing includes real-life anecdotes and "here is what happened when I tried this" moments.
Vague Citations: Remove vague references like "a recent study shows" or "experts say" without providing direct, authoritative links to your sources.
Using double hyphens: Seems all AI generated content always uses double hyphens.
Decision-tree arrows: Do not prefix answers with →, ->, or indented Yes: / No: branches in the Which One Is Right for You? section. That pattern reads like AI output. Write a plain sentence under each question instead ("Grab the…", "The…", "If yes, the…"). Drop "Keep reading" lines entirely; the next question handles the flow. CTA buttons (Find your … →, See it in BabyPickr →) may keep →; that is standard UI, not decision-tree prose.
Endless Em Dashes: AI uses em dashes to tack on extra thoughts at the end of sentences. Break those thoughts into two separate sentences instead.
Staccato fact chains: AI stacks three or more short declarative sentences that share the same rhythm ("We live in the city. Our car is compact. It was our first baby."). Humans usually weave those facts into one or two sentences with commas, "and", or a "so/because" link. Save a standalone short sentence for a punch line after a longer setup, not for every beat in a row.
Corporate comparison closers: Phrases like "more considered than a registry sprint" or "built with intentionality" sound polished but hollow. Replace with a specific image or memory (panic-adding at midnight, the shower deadline, the pile of gear in the hallway).
Do not use -- (Markdown/em dash habit) or — / – in published blog copy. Prefer a period, comma, colon, or parentheses. If you catch yourself tacking on a qualifier after a dash, split it into its own sentence.
Extra tells to cut on pass two
- Hedging stacks: "It's worth noting that…", "Additionally…", "Furthermore…", "In essence…"
- Empty intensifiers: "crucial", "game-changer", "landscape", "navigate", "delve", "robust", "seamless"
- Symmetrical section headers that all start the same way
- Every paragraph the same length; vary rhythm (one short punch, then a longer explanation)
- Fake balance: "Pros and cons" lists where every item sounds equally weightless
- Staccato fact chains: four or more back-to-back sentences under ~8 words each, all ending in periods
Replace with
| Weak pattern | Stronger pattern |
|---|
| "Experts recommend…" | Name the source + link (AAP, CPSC, manufacturer manual, reputable review with URL) |
| "Many parents find…" | "If your building has a 32-inch elevator, measure the folded width first. We did, and …" |
| Three parallel bullets | Two specifics, or four if the fourth is genuinely different. Not padding. |
| Sentence — extra thought tacked on | Two sentences. Or use parentheses for a short aside. |
Double hyphen -- in prose | Period or comma between clauses |
| "In conclusion…" | One direct sentence: what you'd buy, or who should skip this pick |
| Staccato facts: "We live in X. Our Y is Z. It was our first…" | One flowing sentence: "We live in a city apartment with a compact car, and it was our first baby, so…" |
| Corporate closer: "more considered than a registry sprint" | Specific memory: "panic-adding things at midnight the weekend before our shower" |
Decision tree: → Yes: Product / → No: Keep reading | Question as bold line, answer as one prose sentence with linked product name |
Indented ml-4 answer branches under every question | Answer flush under question (mt-1, no extra indent) |
"Which One Is Right for You?" markup (required)
Use the rounded card (rounded-2xl border … p-6 space-y-5). Each branch is a <div> with a bold question and a plain answer paragraph. No arrows, no "Keep reading", no symmetrical Yes/No pairs unless both paths recommend a specific product.
<div>
<p className="font-semibold text-[var(--bp-ink)]">Flying to Disney?</p>
<p className="mt-1 text-[var(--bp-ink-2)]">
The{" "}
<a href="#stokke-yoyo3" className="text-[var(--bp-brand)] underline underline-offset-2">
Stokke YOYO3
</a>{" "}
or{" "}
<a href="#cybex-libelle-2" className="text-[var(--bp-brand)] underline underline-offset-2">
Cybex Libelle 2
</a>
. Both fold with one hand and fit carry-on bins.
</p>
</div>
Bad:
<p className="mt-1 ml-4">→ <strong>Yes:</strong> Stokke YOYO3</p>
<p className="mt-0.5 ml-4">→ <strong>No:</strong> Keep reading</p>
Branding and navigation (required)
BabyPickr is a multi-category gear site, not a stroller-only tool.
| Use | Do not use |
|---|
Gear Finder (links to /) | Stroller Finder, Open Stroller Finder |
Guides (breadcrumb + nav label for /blog) | "Blog" in breadcrumbs or top nav |
| Try the Gear Finder (CTA section title) | Try our Stroller Finder / Car Seat Finder |
| gear finder (lowercase in prose links) | stroller finder, car seat finder (as product name) |
| ← Back to Guides | ← Back to Blog |
- Top nav on all blog pages:
CategoryTopNav with finderPill={{ label: "Gear Finder", href: "/" }} (handled by layouts).
- CTA button text can stay category-specific (e.g. "Find your apartment stroller →") as long as the section title says Gear Finder.
- Homepage browse pages keep Restart journey on
CategoryTopNav; only blog/guides pages use the Gear Finder pill.
Layout and design system (required)
Blogs must match the main site (CategoryPageLayout / homepage): warm background, hero gradient header, Geist body, Source Serif headings, --bp-* tokens.
Do not build custom blog chrome
Never hand-roll a sticky white header, DM Serif wordmark, bg-[#F2F7F2], or a one-off footer. Use the shared shells.
| Page | Wrapper | Notes |
|---|
/blog index | BlogIndexLayout | Hero: "Baby Gear Guides" via .bp-hero-title |
/blog/[slug] article | BlogArticleShell | Pass breadcrumbCurrent (short label, e.g. "City Apartment Strollers") |
| Article body + TOC | BlogArticleLayout | Pass articleToc; children are sections only |
Footer on all blog pages: BlogSiteFooter (included in both layouts).
Typography
- Body: Geist via shell (
--font-geist-sans), 15–16px.
- Headings (h1–h3):
font-bp-serif (Source Serif 4). Never font-dm-serif-display on new blog work.
- Wrap article content in
<article className="blog-article pt-2"> so global .blog-article rules apply.
Colors and surfaces
Use CSS variables from app/globals.css, not legacy hex palette:
| Token | Typical use |
|---|
--bp-bg-warm | Page background (shell sets this) |
--bp-ink, --bp-ink-2, --bp-ink-3 | Headings, body, meta |
--bp-brand, --bp-brand-50, --bp-brand-700 | Links, tags, buttons |
--bp-line, --bp-surface | Borders, cards |
--bp-primary-dark, --bp-primary-text | CTA strip text |
--bp-terracotta, --bp-ochre + -50 variants | Buying-guide / seasonal tags |
Avoid: #F2F7F2, #1A1A2E, #388E3C, #6B7280, border-gray-200, bg-white on new blog UI.
Reusable CSS classes (app/globals.css)
| Class | Use for |
|---|
blog-article | Outer <article> wrapper |
blog-cta + blog-cta-label / blog-cta-title / blog-cta-body | Green gradient Gear Finder CTA block |
blog-product-card | Per-product pick cards (scroll-mt-28 on same element) |
BlogCatalogPrice | Pick-card catalog price with superscript * (pairs with footnote below) |
BlogArticleDisclosure | * price footnote + back link; full affiliate text lives in BlogSiteFooter only |
CTA section must include id="gear-finder" and a TOC entry { id: "gear-finder", label: "Gear Finder" }.
Primary button pattern:
<Link
href="/"
className="mt-5 inline-block rounded-full bg-[var(--bp-brand)] px-6 py-2.5 text-sm font-semibold text-white shadow-md shadow-[var(--bp-brand)]/30 transition hover:bg-[var(--bp-brand-700)]"
>
Find your … →
</Link>
Guides index bottom CTA uses bp-topnav-pill and copy like "Open Gear Finder →" / "Find baby gear that fits your life".
File layout and wiring
Guides index
- File:
app/blog/page.tsx
- Add each new post to the
posts array: slug, title, description, date, readMinutes, tag, tagColor.
tagColor should use --bp-* tokens where possible (see existing posts).
New article
- Path:
app/blog/[slug]/page.tsx (one folder per slug).
- Imports:
BlogArticleShell, BlogArticleLayout, BlogCatalogPrice, BlogArticleDisclosure, BlogTocItem, buildAmazonLink, productImageSrc as needed.
- Metadata:
export const metadata with title, description, keywords, openGraph (type: "article"), alternates.canonical → https://babypickr.com/blog/{slug}.
- JSON-LD:
Article schema in a <script type="application/ld+json"> before the shell.
- Also update:
app/sitemap.ts (and config/seo-metadata.ts if the project uses shared blog metadata helpers).
Article page skeleton
export default function MyGuidePage() {
const publishDate = "YYYY-MM-DD";
const jsonLd = { };
return (
<>
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
<BlogArticleShell breadcrumbCurrent="Short Nav Label">
<article className="blog-article pt-2">
<BlogArticleLayout toc={articleToc}>
<header className="pb-10 border-b border-[var(--bp-line)]">
{/* tag pill, date, read time, h1, lede paragraphs */}
</header>
{/* sections: id + scroll-mt-28 on each */}
<section id="gear-finder" className="blog-cta scroll-mt-28 mt-14">…</section>
<BlogArticleDisclosure />
</BlogArticleLayout>
</article>
</BlogArticleShell>
</>
);
}
Table of contents
- Define
articleToc: BlogTocItem[] at top of file.
- Every major
<section> needs id matching a TOC entry and className including scroll-mt-28.
- Nested product picks:
children array under the picks section (see reference page).
- Include Gear Finder as a top-level TOC item before Quick Answers (if present).
Standard section order (pick guides)
- What to check / reader situation
- Best picks (product cards)
- What to avoid (optional)
- Which one is right for you (decision tree)
- Gear Finder CTA (
blog-cta)
- Quick Answers (FAQ)
<BlogPostFooter slug="…" /> (category browse CTA + scored related guides)
<BlogArticleDisclosure /> (use variant="deals" for deal roundups)
Roundup/deal posts may vary; still use shell, tokens, Gear Finder naming, and BlogArticleDisclosure.
Blog registry and internal linking (required)
Single source of truth: lib/blogRegistry.ts — every published guide is one entry in BLOG_POSTS.
| Field | Purpose |
|---|
slug | URL path and BlogPostFooter prop |
title, description | SEO + index cards + related link copy |
category | strollers | car-seats | cribs | monitors | high-chair | buying-guide |
situations | Real SituationId values from lib/journeyStorage.ts (e.g. city-apartment, compact-car) — used to score related posts |
publishedAt | ISO YYYY-MM-DD — index sort, sitemap lastModified, recency scoring |
seasonal | Optional; seasonal posts skip RelatedArticles but still get CategoryPageCTA |
categoryBrowse | Green callout: href, title, body, buttonLabel for the matching browse filters |
Components (do not hand-write “More from BabyPickr” link lists):
import { BlogPostFooter } from "@/components/blog/BlogPostFooter";
<BlogPostFooter slug="your-post-slug" />
CategoryPageCTA — soft green browse callout from categoryBrowse
RelatedArticles — top 3 peers by category (+3), shared situations (+2 each), recency (+1); never includes seasonal posts; hidden when fewer than two matches (small-catalog fallback fills from recent guides)
- Memorial Day / other
seasonal: true → footer shows category CTA only
Publish a new post — update in this order:
- Add entry to
BLOG_POSTS in lib/blogRegistry.ts (index + sitemap read from here automatically)
- Create
app/blog/{slug}/page.tsx with BlogPostFooter
- Run
npm test — lib/blog/blogRegistry.test.ts and lib/blog/blogPages.test.ts enforce registry ↔ disk parity
- Run
npm run build
Do not duplicate post metadata in app/blog/page.tsx or app/sitemap.ts.
Product picks
- Price, rating, review count, ASIN, and image must come from catalog data (
data/*.ts), never duplicated in the blog. Use @/lib/blog/catalogPicks.ts: pickMeta (editorial only) + buildCatalogPicks() for roundup guides; buildMemorialDayPick() for deal-style posts. Reference: app/blog/best-high-chairs-small-kitchens/page.tsx, app/blog/best-strollers-city-apartments/page.tsx.
- When copy mentions price or review count, use a summary callback
(product) => \…$${product.price}…`` so catalog edits stay in sync.
- After editing picks or anchors, run tests (see Testing above).
- Pull real products from catalog data: real ASINs, weights, prices, ratings.
- Pick-card price: always
<BlogCatalogPrice price={p.price} /> (or product.price on deal cards). Never hand-roll $ + {p.price} in the specs row. The superscript * matches the footnote in BlogArticleDisclosure.
- Prose mentions of price (
At about $299… in summary callbacks) stay plain text; no asterisk in body copy.
- Use
buildAmazonLink from @/lib/affiliate for Amazon links (rel="noopener noreferrer sponsored").
- Use
productImageSrc when images exist; do not invent specs.
- Product cards:
blog-product-card, scroll-mt-28, stable id={product.id} matching the catalog id (TOC anchors use the same id).
- Include honest watch-outs; link to BabyPickr category or filtered views when it helps.
- Inline links to the finder: anchor text gear finder, href
/ or filtered category URL.
Multi-retailer picks (ANB Baby and beyond)
BabyPickr is no longer Amazon-only. Some catalog products carry non-Amazon affiliate links via retailerLinks (types/product.ts), and some premium products are ANB Baby exclusives with asin: null.
- Ranked
pickMeta picks still require a real ASIN. lib/blog/blogPages.test.ts asserts every pickMeta id resolves to a catalog product with an ASIN and a valid Amazon link. Do not add an asin: null product to pickMeta; the build/test will fail.
- For premium / ANB-only products (Nuna, some UPPAbaby/Bugaboo SKUs), add a separate "Premium Picks: Also Worth Considering" section at the end of the product list (a top-level TOC entry with no
children:, so TOC↔pickMeta parity holds). Reference: app/blog/best-strollers-city-apartments/page.tsx and app/blog/best-car-seats-compact-cars/page.tsx.
- Build retailer hrefs with
getProductAnbBabyHref(product.retailerLinks) (Amazon stays buildAmazonLink(asin)). All affiliate <a> tags use rel="noopener noreferrer sponsored".
- Buttons by availability: Amazon-only → orange "Check price on Amazon"; Amazon + ANB → orange Amazon button plus a green "Buy at ANB Baby" or a "✓ Also at ANB Baby (Authorized Retailer) →" link; ANB-only → green "Buy at ANB Baby". Add the note:
Available at ANB Baby, an authorized retailer. Prices may vary.
- On any article that links to ANB Baby, pass
<BlogArticleDisclosure anbBaby /> (renders the Amazon + ANB Baby affiliate line). Keep the intro paragraph that explains premium brands come via ANB Baby.
- No em dashes / double hyphens in this copy either: write "Available at ANB Baby, an authorized retailer." not "ANB Baby — authorized retailer".
"See it in BabyPickr" deep links
Every product card needs a second CTA beside Amazon: See it in BabyPickr →. Link to the category browse page with highlight={ASIN} so the product scrolls into view with a green ring (cold visitors skip the journey gate when URL params are present).
Use the category path plus any filters that match the article topic:
| Category | href pattern | Example |
|---|
| Strollers | `/?${optionalFilters}&highlight=${asin}` | City apartment guide: `/?space=apartment&sortBy=best-match&highlight=${asin}` |
| Car seats | `/car-seats?${optionalFilters}&highlight=${asin}` | Compact cars: `/car-seats?vehicleFit=compact&highlight=${asin}` |
| Baby monitors | `/monitors?${optionalFilters}&highlight=${asin}` | No WiFi guide: `/monitors?connectivity=non-wifi&highlight=${asin}` |
| Cribs | `/cribs?highlight=${asin}` | Memorial Day crib picks: `/cribs?highlight=${asin}` |
Rules:
- Always pass the real catalog ASIN, not product
id.
- Add query params only when the post applies a meaningful filter (connectivity, vehicle fit, space, etc.).
- Label the button exactly See it in BabyPickr → (not "Compare on BabyPickr").
- If a category page does not yet support
highlight, add the same cold-visitor + scroll behavior as app/car-seats/page.tsx before shipping the post.
Tone for this brand
- Second person ("you") is fine; first person plural ("we") for editorial picks is fine.
- Specific numbers beat adjectives: lbs, folded dimensions, elevator width, price.
- Light humor is welcome if it's situational, not forced puns.
- End articles with
<BlogArticleDisclosure /> (asterisk footnote for pick-card prices). Amazon Associates disclosure is in BlogSiteFooter via BlogArticleShell; do not repeat it in the article body.
Price asterisk pattern (required)
- Each product pick card shows catalog price via
BlogCatalogPrice (renders e.g. $449*).
- Article ends with
<BlogArticleDisclosure /> (* Prices shown are approximate and may vary on Amazon.)
- Deal/roundup posts:
<BlogArticleDisclosure variant="deals" /> (list-price wording).
Example specs-row price block:
<BlogCatalogPrice price={p.price} />
Drafting workflow
- Topic research (new guides): Run blog-seo-topic-research first. Fill topic-brief-template.md and get user approval before drafting.
- Define the reader situation in one sentence (e.g., walk-up, no car, newborn + toddler).
- Outline with outcome-first sections, not "Introduction / Overview / Conclusion".
- Draft the lede: start mid-scene or with a concrete constraint, never a cliché opener.
- Insert at least one anecdote or "what we'd do in the hallway" moment per major section.
- Verify every factual claim: spec, safety rule, or study → source link or remove.
- Anti-AI pass: read aloud; cut triplets, staccato fact chains, corporate comparison closers, hedges, symmetrical fluff, and every
-- / em dash.
- Wire up page with
BlogArticleShell, BlogPostFooter, and blogRegistry entry (index + sitemap follow the registry).
- Run
npm test then npm run build (see Testing above).
Gear checklist blog (baby-gear-checklist-new-parents)
This post embeds the interactive checklist via BudgetGearChecklist (RegistryChecklistShell alias). Canonical tool page: /checklist — link readers there for a full-width experience; embed uses returnTo back to #budget-checklists.
- Do not hand-build category browse URLs in checklist slot data. Use
lib/registry/browseLinks.ts once implemented (see registry-checklist skill).
- Tier tabs: minimum / comfortable / premium. Editorial slot lists live in
lib/blog/budgetChecklists.ts (moving to lib/registry/slots.ts).
- Consumables (diapers, wipes) use external Amazon slots in
data/registryExternals.ts when user supplies affiliate URLs.
- Internal links to category pages in prose still use
highlight={ASIN} for ranked picks; checklist browse links use registry URL contract (registryTier, registrySlot, returnTo).
Pre-publish checklist
Voice and content
- [ ] Opening paragraph has zero AI clichés
- [ ] No gratuitous groups of exactly three
- [ ] At least one specific parent-life anecdote or tested scenario
- [ ] Opinions stated plainly ("we'd skip this if…" / "worth it only when…")
- [ ] No "studies show" / "experts say" without named source + URL
- [ ] Zero `--`, em dashes (`—`), or en dashes (`–`) in body copy
- [ ] No staccato chains of 3+ short declarative sentences where facts could be woven together
- [ ] Closers use a specific image or memory, not corporate comparison phrasing ("more considered than…")
Layout and branding
- [ ] Uses BlogArticleShell (article) or BlogIndexLayout (index), not custom header/footer
- [ ] Article wrapped in className="blog-article"
- [ ] Headings use font-bp-serif, not font-dm-serif-display
- [ ] Colors use --bp-* variables, not legacy #F2F7F2 / #1A1A2E hex set
- [ ] CTA section: id="gear-finder", blog-cta classes, title "Try the Gear Finder"
- [ ] Pick cards use `BlogCatalogPrice`; `BlogArticleDisclosure` footnote starts with `* Prices shown are approximate…`
- [ ] Breadcrumb says Guides; `BlogArticleDisclosure` ends with "← Back to Guides"
- [ ] Decision tree has no `→` prefixes, no "Keep reading", no indented Yes/No branches
- [ ] No "Stroller Finder" / "Open Stroller Finder" branding anywhere
Tests and build
- [ ] `npm test` passes (blog ↔ catalog integration + full suite)
- [ ] `npm run build` passes for new or heavily edited posts
Registry and linking
- [ ] New entry in `lib/blogRegistry.ts` (`situations` use real `SituationId` values)
- [ ] `<BlogPostFooter slug="…" />` after FAQ; no hand-written related link list
- [ ] `seasonal: true` only for time-boxed deal posts (skips related guides)
Data and SEO
- [ ] Product data matches catalog (ASIN, weight, price, caveats)
- [ ] Each product card has **See it in BabyPickr →** with `highlight={ASIN}` on the right category URL
- [ ] metadata title/description are specific, not generic filler
- [ ] Canonical URL https://babypickr.com/blog/{slug}
Examples
Bad lede:
In today's fast-paced world, city parents face unique challenges. Choosing the right stroller is crucial, seamless, and can make all the difference.
Good lede:
Our building's elevator is 30 inches wide. The first stroller we bought folded to 31. We learned that in the lobby, with a sleeping newborn and an annoyed super.
Bad em dash / double hyphen:
The Butterfly folds fast -- perfect for small apartments -- and the basket is tiny.
Good (split sentences):
The Butterfly folds fast. That's perfect for small apartments. The basket is still tiny.
Bad triplet:
It's not just lightweight, and not just compact, but also affordable.
Good triplet:
At 13.7 lbs it beats everything else here on weight. You'll pay for that with a short handlebar if you're over 6 ft.
Bad staccato chain:
We live in the city. Our car is compact. It was our first baby. Every list felt wrong.
Good (woven facts):
We live in a city apartment with a compact car, and it was our first baby, so every "best stroller" list felt written for someone with a garage and an afternoon to test folds in a parking lot.
Bad corporate closer:
BabyPickr is what finally came out of that. It is more considered than a registry sprint built in one weekend.
Good (specific memory):
BabyPickr is what finally came out of that pile, and it is the registry I wish I had instead of panic-adding things at midnight the weekend before our shower.
Bad citation:
Experts say rear-facing is safer for longer.
Good citation:
AAP recommends rear-facing until at least age 2. Check your car seat manual for the height/weight limit on your model.
Bad CTA branding:
Try our Stroller Finder. Open Stroller Finder →
Good CTA branding:
Try the Gear Finder. Button: "Find your apartment stroller →" (specific action, generic product name in the section title).
For longer before/after samples, see examples.md.