# Markdown blog (Agent Skill)

> A multilingual blog served from markdown files: localized slugs tied by one translation key, tag pages, related posts, RSS, sitemap and hreflang. No CMS.

Page: https://timerise.ai/skills/blog-markdown

Agent Skill `blog-markdown`: https://github.com/timerise-ai/blog-markdown. Category: Content. Runs on Next.js; backend: Repository files, no CMS.

Latest release: v0.1.10 (2026-10-07).

Install: npx skills add timerise-ai/blog-markdown. Timerise builds it on a brief; the price comes from the brief.

A company blog has two jobs: read well and be found. The second job fails once there is more than one language. Translated posts get separate URLs with nothing tying them together. Tag pages split on a spelling variant. The feed goes stale. Search engines see duplicates instead of translations.

This skill builds the blog from markdown files, with one field that ties an article's language versions together: the translation key. That key drives hreflang, the language switcher, related posts and cross-language redirects. Slugs stay localized for search.

Tag pages, RSS, sitemap and cover art come with it. A validator in CI refuses a build with a broken post, so a bad file never publishes as an empty page.

Build a file-based, multilingual blog from markdown content: locale-partitioned posts, per-locale slugs joined by a shared translation key, tag pages, related posts, cover art, RSS, sitemap and hreflang. Use when: (1) adding a blog, changelog, news or article section backed by markdown files rather than a CMS, (2) making an existing markdown blog multilingual, or adding tag pages, hreflang, feeds or related posts, (3) the content lives in a repo folder and must be statically generated, (4) the user mentions: markdown blog, MDX blog, frontmatter, gray-matter, content/blog, translationKey, tag pages, hreflang, related posts, generateStaticParams for posts, blog RSS feed. Carries the cross-locale slug model, the tag-slug collision rule, the frontmatter parser that survives smart quotes, and the loader memoization that removes a measured 71x build-time read amplification. Next.js App Router oriented; the content source is a seam, so a CMS can replace the filesystem. Not a CMS integration skill and not a docs/help-center skill.

The whole design turns on one idea: **a post's identity is its `translationKey`, not its slug.** Slugs are localized for SEO and differ per language; the key is what makes three files one article, and it is what powers hreflang, the language switcher, related posts and cross-locale redirects. This skill was written by the engineer who has shipped this module; the earlier implementation it was audited against was a multi-locale, statically generated marketing blog. The templates hold the properties such a blog has to hold: every translation resolves to its siblings through the key, every tag page lists every post that carries the tag under any spelling, a file that `gray-matter` cannot parse still loads, and each content file is read once per build. The content-layer suite and the build verify each one; [references/provenance.md](references/provenance.md) has the record.

What it builds:
- Localized slugs
- Tag pages
- Related posts
- RSS
- Sitemap, hreflang and a CI content validator

When to use:
- Content authored as markdown in the repo, deployed with the app
- More than one language, with different slugs per language
- Tag pages, related posts, feeds, or hreflang are in scope
- You want the whole thing statically generated at build time

Non-negotiables:
- `gray-matter` caches a failed parse as an empty result. It writes its cache entry before parsing, so the second and every later parse of a file whose YAML throws returns `{}`, with no error and no frontmatter. Always call `matter(contents, {})`; any options object opts out of the cache. The re-parse test in the suite holds it.
- Relations are authored once, in the default locale. A translated file that omits `related` resolves through `translationKey` to the default locale's relations, so every translation renders the same related section. The fallback test holds it.
- A tag's identity is its slug, not its label. `"AI Agents"` and `"AI agents"` slug to the same URL; group and match by slug, so every spelling variant's posts appear on the one tag page. Two tests hold it.
- Loading is O(posts x locales) per page unless you memoize. Memoize per locale so each content file is read once per build; resolving alternates in `generateMetadata` is otherwise a 71x file-read amplification, measured on the earlier implementation.

Not this:
- A hosted CMS as the source of truth: That CMS's SDK; keep only `references/content-model.md`
- A docs site or help center with categories, sidebar and search: The sibling [`help-center-markdown`](https://github.com/timerise-ai/help-center-markdown) skill, a different navigation model
- A single-locale blog of under ten posts: Plain `fs` plus `gray-matter` inline; this skill is overhead there
- Rendering user-submitted markdown: A sanitizing renderer; see the XSS note in `references/rendering.md`

What to tell your agent:
- Add a blog to this Next.js app from markdown files in content/blog: post pages, tag pages, related posts, an RSS feed and sitemap entries, all statically generated.
- Make our markdown blog bilingual, English and Polish, with a different slug per language joined by one translation key, and hreflang between the two.
- Our blog build got slow as the posts grew. Find where the loader rereads the markdown files and fix it.

Agent eval results (newest per agent and prompt):
- Codex CLI (gpt-6.1-sol) on v0.1.10, 2026-10-07: pass (Repository files)
- Gemini CLI (gemini-3.8-flash) on v0.1.10, 2026-10-07: pass (Repository files)
- Claude Code (claude-opus-5-5) on v0.1.10, 2026-10-07: pass (Repository files)

Recent releases:
- v0.1.10 (2026-10-07): Fix release, from scoring the prompt-1 agent eval runs against 0.1.9.
- v0.1.9 (2026-10-07): Fix release, from scoring the prompt-1 agent eval runs against 0.1.8.
- v0.1.8 (2026-10-07): Fix release, from scoring the prompt-1 agent eval runs against 0.1.7.
