I write tutorials. Most of them start as Claude Code sessions where I am solving a real problem for a real project. The version of the tutorial that ends up on my personal blog is not the version in the session: the session has client names, internal tools, and project codenames that should never leave a private context.

The manual workflow used to be: copy the conversation, rewrite for a public audience, find a slug, generate frontmatter, build the Astro site, commit, push, deploy. Forty minutes for a 1500-word post. Half of that was the white-label rewrite. The other half was the mechanical steps that I could not skip but did not enjoy.

nf-blog is the skill I wrote so the same pipeline takes five minutes and the white-label audit is mandatory, not best-effort.

The problem the skill solves

Publishing from a session has three failure modes:

  • Leaking identifiers. The tutorial mentions a client by name, or references an internal tool, or shows a URL with a private subdomain. By the time you notice, the post is on the public internet.
  • Slug and frontmatter inconsistency. Vietnamese titles with diacritics that should become ASCII slugs. Tags that should be ASCII even if the post is Vietnamese. Series fields that have to match across multiple posts.
  • Build, commit, deploy as separate steps. Astro build, git commit, push, Cloudflare Pages deploy. Each step is one command, but skipping the build check is how broken posts ship.

The skill turns these three into one pipeline with a hard checkpoint between draft and publish: the white-label audit. Without that audit signing off, the post does not get written.

What it does

  • Gathers content from the current session, a referenced file, or asks Claude to write fresh.
  • Asks the post type up front: single, part of an existing series, or new series. This determines what frontmatter fields are needed.
  • Auto-detects the post language by scanning for Vietnamese diacritics. Affects slug generation and lang frontmatter field.
  • Asks for title, description, tags, pubDate in one batch (max four AskUserQuestion per turn) with sensible defaults.
  • Runs a mandatory white-label audit: scans the draft for proper nouns, flags any that look client- or company-specific, proposes generic rewrites. Shows the user a table to approve, edit, or keep per item.
  • Generates the post file at src/content/blog/<slug>.md with full frontmatter.
  • Runs npm run build to verify the post compiles before any commit.
  • Commits and pushes with the project’s git convention, then deploys via the blog’s deploy script.

How to invoke

/nf-blog

No arguments. The skill is disable-model-invocation: true so it does not auto-trigger; you type the slash command when you want to publish.

A walkthrough

You just finished a Claude session debugging a hook in a client project. You realize the debugging steps are general enough to be a public post.

$ /nf-blog

Source:
  Detected: tutorial section in this session (lines 142-310)
  Use this? Yes / No / Point me to a different file
> Yes

Post type:
  1. Single post
  2. Part of an existing series
  3. New series
> 1

Language detection:
  Diacritics found: 0
  Vietnamese words: 0
  -> lang: en

Metadata batch (defaults shown):
  Title:       "Debugging Claude Code Hooks That Fire Silently"
  Slug:        debugging-claude-code-hooks-that-fire-silently
  Description: "How to debug a Claude Code Stop hook that runs but produces no output, by..."
  Tags:        ["claude-code", "hooks", "debugging"]
  pubDate:     2026-05-17

Confirm or edit? Confirm / Edit
> Confirm

White-label audit:
  Scanning for proper nouns, internal references, private URLs...

  Found 3 potential client-specific items:
    | # | Original                                          | Proposed rewrite                       |
    |---|---------------------------------------------------|-----------------------------------------|
    | 1 | "at <a-client>"                                   | "at a previous client"                  |
    | 2 | "<an-internal-tool>" (mentioned 4 times)          | "an internal tool"                      |
    | 3 | "hook fired against portal.<a-client>.com"        | [remove URL, keep architecture]         |

  Approve / Edit / Keep-as-is per item?
> Approve all

Writing src/content/blog/debugging-claude-code-hooks-that-fire-silently.md...

Build:
  $ cd <blog-repo> && npm run build
  ... 38 pages generated. Build OK.

Commit + push:
  $ git add src/content/blog/debugging-claude-code-hooks-that-fire-silently.md
  $ git commit -m "content(main): add post debugging-claude-code-hooks-that-fire-silently"
  $ git push origin main

Deploy:
  $ npm run deploy
  ... wrangler deploying...
  Deployment URL: https://<hash>.<blog-pages-domain>/

Final URLs:
  Preview:    https://<hash>.<blog-pages-domain>/blog/debugging-claude-code-hooks-that-fire-silently/
  Production: https://<your-blog>/blog/debugging-claude-code-hooks-that-fire-silently/
  Tags:       https://<your-blog>/tags/claude-code/
              https://<your-blog>/tags/hooks/
              https://<your-blog>/tags/debugging/

Audit summary: 3 rewrites applied.

The audit step is the one that took the longest to get right. Showing the user a per-item table with explicit Approve / Edit / Keep options is the difference between “skill catches my leaks” and “skill silently overwrites my prose with generic terms.”

How it works under the hood

The skill is one SKILL.md. The four mechanisms worth knowing about:

Language detection from diacritics. Vietnamese has a distinctive set of combining characters (à á ả ã ạ ă ắ ằ ẳ ẵ ặ ...). The skill scans the body for those characters plus the letter đ/Đ. If any are present, lang: vi; otherwise lang: en. This drives slug generation (Vietnamese titles need diacritic stripping) and the <html lang> attribute on the rendered page.

Vietnamese-aware slug generation. The algorithm is:

const toSlug = (s) => s
  .normalize('NFD')                       // decompose accented chars
  .replace(/[̀-ͯ]/g, '')        // strip combining diacritics
  .replace(/đ/g, 'd').replace(/Đ/g, 'D')  // not handled by NFD
  .toLowerCase()
  .replace(/[^a-z0-9]+/g, '-')            // non-alphanumeric -> dashes
  .replace(/^-+|-+$/g, '');               // trim leading/trailing dashes

A title like "Hướng dẫn Docker căn bản" becomes huong-dan-docker-can-ban. Tags stay ASCII even for Vietnamese posts because they become URLs at /tags/<tag>/.

White-label audit is the hard checkpoint. The skill reads the draft end-to-end, maintains a list of proper nouns it encounters, and for each one asks itself: “is this public knowledge a random reader would recognize?” If yes (Docker, AWS, React), it stays. If no (a client name, an internal tool), it gets flagged.

Context phrases the skill looks for: at <name>, our <name>, we used <name>, client <name>, project <name>, plus any URL pattern matching what looks like a private domain.

The output is a table presented to the user, where each row is one decision. The default is “apply all proposed rewrites,” but you can opt to keep an item (rare) or edit the proposed rewrite (more common, for getting the tone right).

Build verification before commit. npm run build runs Astro’s full build before any commit. If the build fails (usually frontmatter parse errors or missing image references), the skill stops and surfaces the error. Nothing gets committed, nothing gets pushed. This is the cheapest possible test, and it catches the class of mistake (broken frontmatter) that would otherwise ship to production.

Gotchas

  • The audit is not bulletproof. It catches the obvious cases: explicit “at Client X” mentions, internal tool names that appear in proper-noun form, URLs with private subdomains. It will miss things like a screenshot file path that contains the client name, or an embedded diagram with internal labels. Skim the draft yourself after the audit; the skill is a checklist, not a guarantee.
  • Tags are ASCII even in Vietnamese posts. The /tags/<tag>/ URLs need to be ASCII for routing. The skill enforces this. If you want a Vietnamese-displayed tag, render it in the title or description but use the ASCII form in the tags array.
  • No em-dashes or en-dashes. The skill enforces a project rule that no long-dash characters appear in the post body, tables, or frontmatter. These are strong AI-detection signals. The skill replaces them with ., ,, :, or () depending on context. Final grep check before build.
  • Series frontmatter has three required fields. series (kebab-case slug), seriesTitle (display title, can have diacritics), and seriesOrder (integer). The skill enforces all three when you pick “part of a series” or “new series”. A series landing post (no seriesOrder, just series and seriesTitle) is optional and created separately.
  • Deploy is a wrangler command with one tricky workaround. The blog deploys to Cloudflare Pages via npm run deploy, which unsets CLOUDFLARE_API_KEY and CLOUDFLARE_EMAIL to prevent wrangler from mis-parsing a project-scoped API token. If you adapt this skill to a different deploy target, change the deploy command and skip the workaround.

Why I share this

The skill is opinionated: it forces a white-label audit, it requires npm run build to pass, it enforces a punctuation style, it asks for the post type up front. None of those constraints are arbitrary. Each one came from a publish that went wrong and a rule I added so the same mistake could not repeat.

If you maintain a personal blog with Astro (or any static site generator with markdown content), the structure is portable: source -> ask type/lang -> ask metadata batch -> white-label audit -> generate file -> build -> commit -> deploy. Copy the skill, swap the blog repo path and deploy command, and tune the audit rules to your own ecosystem (different client names, different internal tools).

For the related skills that prepare content before publishing, claude-authoring covers the rules/skills/agents that influence Claude’s drafting style. For the broader claude-code skill model, claude-code-skills walks through how skills load and execute.

The audit table is the part I encourage you to copy even if you do not use the rest of the skill. Mandatory per-item user confirmation on flagged terms is the only audit mechanism that has consistently caught my leaks before publication.