<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="atom.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://www.voix.md/blog/</id>
    <title>voix - documentation migration notes</title>
    <updated>2026-07-29T00:00:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://www.voix.md/blog/"/>
    <subtitle>Field notes on documentation migration, docs-as-code, and information architecture.</subtitle>
    <icon>https://www.voix.md/img/favicon.ico</icon>
    <rights>Copyright © 2026 voix</rights>
    <entry>
        <title type="html"><![CDATA[Docs as code without the cargo cult]]></title>
        <id>https://www.voix.md/blog/docs-as-code-without-the-cargo-cult/</id>
        <link href="https://www.voix.md/blog/docs-as-code-without-the-cargo-cult/"/>
        <updated>2026-07-29T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Which parts of the docs-as-code toolchain earn their keep, what has to be true before you adopt each one, and which practices are pure theatre.]]></summary>
        <content type="html"><![CDATA[<p>Docs as code is a shipping model, not a shopping list. Copying another company's
toolchain gives you their cost structure without their constraints, and most of
the cost lands on the person maintaining the pipeline rather than the person
writing the page. This is which parts pay for themselves, what has to be true
before each one does, and what is theatre.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="two-things-are-load-bearing-the-rest-is-optional">Two things are load-bearing, the rest is optional<a href="https://www.voix.md/blog/docs-as-code-without-the-cargo-cult/#two-things-are-load-bearing-the-rest-is-optional" class="hash-link" aria-label="Direct link to Two things are load-bearing, the rest is optional" title="Direct link to Two things are load-bearing, the rest is optional" translate="no">​</a></h2>
<p>The model rests on two properties: the content is plain text in version control,
and a change reaches production through a review that a machine can gate. Every
other practice in the stack exists to make one of those two cheaper or safer.</p>
<p>That framing is useful because it gives you a test. Before adopting a tool, ask
which of the two properties it strengthens. A prose linter makes review cheaper,
so it qualifies once review is a bottleneck. Screenshot automation strengthens
neither; it is a content-production tool that happens to live in the repo, and
it should be judged on its own terms.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-pays-for-itself-and-when">What pays for itself, and when<a href="https://www.voix.md/blog/docs-as-code-without-the-cargo-cult/#what-pays-for-itself-and-when" class="hash-link" aria-label="Direct link to What pays for itself, and when" title="Direct link to What pays for itself, and when" translate="no">​</a></h2>
<table><thead><tr><th>Practice</th><th>Adopt when</th><th>What it buys</th></tr></thead><tbody><tr><td>Markdown or MDX in Git</td><td>Day one</td><td>History, blame, branches, and an exit from any vendor</td></tr><tr><td>Build on every commit, publish on merge</td><td>Day one</td><td>Nobody publishes by hand at 6pm on a Friday</td></tr><tr><td>Internal link checking in the build</td><td>Day one</td><td>Reorganisations stop producing silent 404s</td></tr><tr><td>Pull request review with CODEOWNERS</td><td>As soon as two people can edit the same page</td><td>The right reviewer is assigned without anyone remembering to</td></tr><tr><td>Preview deployment per pull request</td><td>As soon as reviewers are not the writers</td><td>Subject-matter experts review rendered pages, not diffs</td></tr><tr><td>External link checking, scheduled</td><td>Once you link out to third parties</td><td>Rot in other people's URLs surfaces weekly instead of in a ticket</td></tr><tr><td>Prose linter with a small rule set</td><td>When the same correction appears in review for the third time</td><td>Style arguments move from people to config</td></tr><tr><td>Reusable snippets or partials</td><td>When one procedure appears verbatim in three or more places</td><td>One edit instead of three, and no drift between them</td></tr><tr><td>Generated API reference</td><td>When the spec is authoritative and validated in CI</td><td>Reference stops lagging the API</td></tr><tr><td>Docs versioning</td><td>When you support more than one released version at once</td><td>Users on the old release stop reading the new instructions</td></tr><tr><td>Custom MDX components</td><td>When a layout pattern repeats across many pages</td><td>Consistency without copy-pasted markup</td></tr><tr><td>Executable code samples</td><td>When users copy samples and they break silently</td><td>Samples fail in CI instead of in production</td></tr><tr><td>Localisation pipeline</td><td>When translation is funded and continuous</td><td>Translations track the source instead of forking from it</td></tr></tbody></table>
<p>Read the middle column as a precondition, not a recommendation. Docs versioning
without a second supported release multiplies your maintenance surface for
nothing: every fix now needs a decision about which versions it applies to.
Snippets before the third duplicate make the source harder to read than the
duplication did.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="do-you-need-docs-as-code-for-a-five-person-team">Do you need docs as code for a five-person team?<a href="https://www.voix.md/blog/docs-as-code-without-the-cargo-cult/#do-you-need-docs-as-code-for-a-five-person-team" class="hash-link" aria-label="Direct link to Do you need docs as code for a five-person team?" title="Direct link to Do you need docs as code for a five-person team?" translate="no">​</a></h2>
<p>You need version control and a build that fails on broken links. You almost
certainly do not need the rest yet. At five people, review happens in a shared
channel rather than through CODEOWNERS, style is settled by asking the person
sitting next to you, and there is only one supported version of the product, so
the parts of the stack that pay off through coordination have nothing to
coordinate.</p>
<p>What is worth doing early is choosing formats that do not block later adoption.
Plain Markdown in a repo with a real build keeps every option open. A
proprietary editor with an export button does not: you can add a linter to
Markdown next year, but you cannot add version control to a platform that only
stores rendered HTML.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-smallest-pipeline-that-is-worth-having">The smallest pipeline that is worth having<a href="https://www.voix.md/blog/docs-as-code-without-the-cargo-cult/#the-smallest-pipeline-that-is-worth-having" class="hash-link" aria-label="Direct link to The smallest pipeline that is worth having" title="Direct link to The smallest pipeline that is worth having" translate="no">​</a></h2>
<p>For a Docusaurus site, this is close to the whole thing:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#0550ae">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> docs</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#0550ae">on</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#0550ae">pull_request</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#0550ae">paths</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#c2185b">'docs/**'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#c2185b">'blog/**'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#c2185b">'src/**'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#c2185b">'docusaurus.config.js'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#0550ae">push</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#0550ae">branches</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#0550ae">jobs</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#0550ae">build</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#0550ae">runs-on</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> ubuntu</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">latest</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#0550ae">steps</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#0550ae">uses</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> actions/checkout@v4</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#0550ae">uses</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> actions/setup</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">node@v4</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#0550ae">with</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#0550ae">node-version</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#0a6d6b">20</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#0550ae">cache</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> npm</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#0550ae">run</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> npm ci</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#6a6558;font-style:italic"># onBrokenLinks: 'throw' makes this step the internal link checker.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#0550ae">run</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> npm run build</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#0550ae">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Check external links</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#0550ae">uses</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> lycheeverse/lychee</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">action@v2</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#0550ae">with</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#0550ae">args</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">-</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">no</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">progress </span><span class="token punctuation" style="color:#393A34">-</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">max</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">concurrency 8 build/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#0550ae">fail</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean important" style="color:#0a6d6b">true</span><br></span></code></pre></div></div>
<p>The comment on the build step is the important part. In Docusaurus,
<code>onBrokenLinks: 'throw'</code> and <code>onBrokenAnchors: 'throw'</code> turn the ordinary build
into a link checker for every internal link and every heading anchor on the
site. It is two lines of configuration and it removes an entire class of defect
that is otherwise chased with a crawler running after deployment, which is to
say after users have already found the 404.</p>
<p>Ownership is the other early win, and it is one file:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain"># CODEOWNERS — the last matching pattern wins, so general first, specific after.</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">/docs/            @acme/docs</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">/docs/api/        @acme/platform</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">/docs/billing/    @acme/billing</span><br></span></code></pre></div></div>
<p>That gets the API team onto API pull requests automatically. Without it, "the
docs team reviews everything" becomes the bottleneck the pipeline was supposed
to remove.</p>
<p>When style corrections start repeating, add a linter with a deliberately small
rule set. Three rules that block a merge beat forty that people learn to ignore:</p>
<div class="language-ini codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ini codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">StylesPath = styles</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">MinAlertLevel = error</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Packages = Google</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">[*.{md,mdx}]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">BasedOnStyles = Google</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Google.Headings = YES</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Google.WordList = YES</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Google.Passive = NO</span><br></span></code></pre></div></div>
<p><code>Google.Passive = NO</code> is not an oversight. Passive voice is a judgement call a
reviewer should make, and a linter that fails builds over it teaches writers to
route around the linter.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-usually-theatre">What is usually theatre<a href="https://www.voix.md/blog/docs-as-code-without-the-cargo-cult/#what-is-usually-theatre" class="hash-link" aria-label="Direct link to What is usually theatre" title="Direct link to What is usually theatre" translate="no">​</a></h2>
<ul>
<li class=""><strong>A style guide nobody enforces.</strong> A 40-page document that no build checks and
no reviewer cites. Either encode three rules in the linter or accept that
style is a review conversation.</li>
<li class=""><strong>Screenshot automation.</strong> The pipeline is itself a product: selectors,
seeded data, a browser runner, and a rendering environment that drifts. It
earns its keep when the screenshot count is large and the UI is stable. While
the UI is still moving, crop and commit.</li>
<li class=""><strong>Versioning switched on because the platform supports it.</strong> Each version is a
copy of the docs that someone now has to keep true.</li>
<li class=""><strong>A monorepo docs setup for one product.</strong> Path filters, build matrices and
workspace configuration to solve a coordination problem you do not have.</li>
<li class=""><strong>A custom static site generator.</strong> The maintenance never appears on the
roadmap and never stops.</li>
<li class=""><strong>"Everyone contributes" with no template, owner or review commitment.</strong> Open
contribution without a review service level turns the pull request queue into
a slower version of the backlog it replaced, and contributors stop trying
after the second unreviewed pull request.</li>
<li class=""><strong>A WYSIWYG layer bolted onto the Git workflow so non-writers can edit.</strong> You
now maintain two authoring models and reconcile them by hand. Pick one, and if
the answer is that some contributors cannot use Git, make the web editor in
the Git host the fallback rather than a second system.</li>
</ul>
<p>None of these are wrong in every context. They are wrong as a starting position,
and they are what a stack looks like when it was copied rather than chosen.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="adopt-in-the-order-that-removes-pain">Adopt in the order that removes pain<a href="https://www.voix.md/blog/docs-as-code-without-the-cargo-cult/#adopt-in-the-order-that-removes-pain" class="hash-link" aria-label="Direct link to Adopt in the order that removes pain" title="Direct link to Adopt in the order that removes pain" translate="no">​</a></h2>
<p>A workable sequence, where each step is only taken when the previous one is
boring:</p>
<ol>
<li class="">Content in Git, built in CI, published on merge to the default branch.</li>
<li class="">Broken internal links fail the build.</li>
<li class="">CODEOWNERS, so review routing is automatic.</li>
<li class="">Preview deployments, so subject-matter experts can review rendered pages.</li>
<li class="">Templates for the two or three page types you actually publish.</li>
<li class="">A linter, with the smallest rule set that ends the recurring argument.</li>
<li class="">Everything else, only when its precondition in the table above is met.</li>
</ol>
<p>Steps 1 to 4 are the ones that change how it feels to ship a page. Steps 5 and 6
are what keep a growing contributor pool from producing forty page shapes. Most
of the tooling anxiety in this space is about step 7, which is the part that
matters least.</p>
<p>A migration is the cheapest moment to make these choices, because the pipeline
is being built once anyway and nobody has habits in the new system yet. The
<a class="" href="https://www.voix.md/docs/">migration handbook</a> covers the operational side of that build, and
<a class="" href="https://www.voix.md/services/">contributor onboarding and CI/CD delivery</a> are two of the four
things a voix engagement covers. If you are earlier than that and still deciding
whether the move is worth it, the <a class="" href="https://www.voix.md/blog/documentation-content-audit/">content audit post</a>
is the honest first step.</p>]]></content>
        <author>
            <name>Maria Gilca</name>
            <uri>https://www.voix.md/about/</uri>
        </author>
        <category label="Docs as Code" term="Docs as Code"/>
        <category label="Developer Experience" term="Developer Experience"/>
        <category label="Governance" term="Governance"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Redirect mapping is the part of a docs migration that breaks]]></title>
        <id>https://www.voix.md/blog/redirect-mapping-docs-migration/</id>
        <link href="https://www.voix.md/blog/redirect-mapping-docs-migration/"/>
        <updated>2026-07-08T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Why URL continuity decides whether a docs migration hurts, how to build a redirect map from real traffic sources, and how to test it before cutover.]]></summary>
        <content type="html"><![CDATA[<p>Content conversion is the visible part of a migration and the part that rarely
fails. The part that fails is the URL layer: every bookmark, support macro,
in-product help button, partner page and search result that points at the old
docs. A redirect map keeps those working, and it is usually built last, in a
hurry, by whoever is still awake.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="every-old-url-has-an-owner-you-cannot-email">Every old URL has an owner you cannot email<a href="https://www.voix.md/blog/redirect-mapping-docs-migration/#every-old-url-has-an-owner-you-cannot-email" class="hash-link" aria-label="Direct link to Every old URL has an owner you cannot email" title="Direct link to Every old URL has an owner you cannot email" translate="no">​</a></h2>
<p>The reason redirects matter is not tidiness. It is that most links into your
documentation were created by people and systems you have no way to update:</p>
<ul>
<li class=""><strong>Released clients.</strong> A help button compiled into version 4.2 of a desktop or
mobile app points at the old URL for as long as anyone runs 4.2. You cannot
patch a shipped binary's help links, so the URL has to keep answering.</li>
<li class=""><strong>Support macros and canned replies.</strong> Every saved reply in the helpdesk
carries a URL. Nobody has an inventory of them.</li>
<li class=""><strong>Search engines.</strong> The index is built from URLs, and ranking signals attach
to URLs, not to content.</li>
<li class=""><strong>Third parties.</strong> Community answers, partner integration guides, conference
slides, blog posts, and the customer's own internal wiki.</li>
<li class=""><strong>Onboarding email sequences and PDFs</strong>, which are usually owned by a team
that does not know a migration is happening.</li>
</ul>
<p>You do not need to know who they all are. You need every path they might request
to resolve to the right page.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-happens-to-search-rankings-when-you-skip-redirects">What happens to search rankings when you skip redirects?<a href="https://www.voix.md/blog/redirect-mapping-docs-migration/#what-happens-to-search-rankings-when-you-skip-redirects" class="hash-link" aria-label="Direct link to What happens to search rankings when you skip redirects?" title="Direct link to What happens to search rankings when you skip redirects?" translate="no">​</a></h2>
<p>The old URLs start returning 404, crawlers drop them from the index over the
following crawls, and none of the authority those pages accumulated transfers to
the new ones — the new pages compete as if they were published yesterday. That
is the mechanism, and it is worth being precise about two variants that look
like fixes and are not.</p>
<p>Redirecting everything to the docs home page is almost as bad as a 404. A
redirect to a page that does not answer the original request is treated as a
soft 404: the target is not credited, and the old URL still leaves the index.
Using a 302 is the other common mistake — a temporary redirect tells crawlers to
keep the old URL and not to consolidate signals onto the new one. For a
migration, the answer is a 301 to the closest equivalent page, per URL.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="build-the-map-from-traffic-not-from-the-sitemap">Build the map from traffic, not from the sitemap<a href="https://www.voix.md/blog/redirect-mapping-docs-migration/#build-the-map-from-traffic-not-from-the-sitemap" class="hash-link" aria-label="Direct link to Build the map from traffic, not from the sitemap" title="Direct link to Build the map from traffic, not from the sitemap" translate="no">​</a></h2>
<p>The old platform's sitemap tells you what it thinks it publishes. It does not
tell you what people actually request. Build the source list as a union of six
exports, then deduplicate:</p>
<ol>
<li class=""><code>sitemap.xml</code> from the old platform — the structural baseline.</li>
<li class="">Analytics, every URL with at least one session in the last 24 months — what
humans use.</li>
<li class="">CDN or server access logs — what integrations and bots fetch, including
URLs analytics never recorded.</li>
<li class="">Search Console pages with impressions — what search already knows about.</li>
<li class="">A crawl of the old site — catches orphans the sitemap omits.</li>
<li class="">A grep of the product source and the helpdesk macros for the docs domain —
catches the links you are contractually stuck with.</li>
</ol>
<p>Then normalise before you map anything: decide on trailing slash, lowercase
paths if the old platform was case-insensitive, and strip tracking query
strings. Do this once, in a script, so the same rules apply to the map and to
the test suite.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="one-rule-per-situation">One rule per situation<a href="https://www.voix.md/blog/redirect-mapping-docs-migration/#one-rule-per-situation" class="hash-link" aria-label="Direct link to One rule per situation" title="Direct link to One rule per situation" translate="no">​</a></h2>
<p>Most of a redirect map is mechanical once you agree on the rules. The content
audit already tells you which situation each page is in.</p>
<table><thead><tr><th>Situation</th><th>Rule</th><th>Status</th></tr></thead><tbody><tr><td>Page moved, content unchanged</td><td>One-to-one redirect to the new path</td><td>301</td></tr><tr><td>Two pages merged</td><td>Both old paths to the merged page, with an anchor if the section survived</td><td>301</td></tr><tr><td>Page split into several</td><td>Old path to the best successor, usually the new overview</td><td>301</td></tr><tr><td>Page deleted, successor exists</td><td>Old path to the successor</td><td>301</td></tr><tr><td>Page deleted, no successor</td><td>Old path to the section index, plus a line on that index saying what replaced it</td><td>301</td></tr><tr><td>Heading renamed, page kept</td><td>Client-side anchor mapping on the target page</td><td>none</td></tr><tr><td>Legacy query-string page ids</td><td>Rewrite at the edge, before the static host sees the request</td><td>301</td></tr></tbody></table>
<p>Two hard rules sit on top of the table. Never ship a chain: if A points to B and
B later points to C, collapse it so A points to C. And never let a row point at
the site root — a root redirect is the map's way of saying nobody decided.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="keep-the-map-in-the-repository">Keep the map in the repository<a href="https://www.voix.md/blog/redirect-mapping-docs-migration/#keep-the-map-in-the-repository" class="hash-link" aria-label="Direct link to Keep the map in the repository" title="Direct link to Keep the map in the repository" translate="no">​</a></h2>
<p>The map is a reviewable artefact, not a console UI. A CSV in the docs repo,
changed through pull requests, means the redirect list is diffable and the
reason for each row survives the person who wrote it.</p>
<div class="language-csv codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-csv codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">old_path,new_path,reason</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">/display/DOCS/Authentication,/docs/guides/authentication/,moved</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">/display/DOCS/Auth+Tokens,/docs/guides/authentication/#tokens,merged</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">/display/DOCS/Legacy+SDK,/docs/sdk/,deleted-no-successor</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">/pages/viewpage.action?pageId=1183,/docs/guides/webhooks/,legacy-id</span><br></span></code></pre></div></div>
<p>Generate the host configuration from that file rather than hand-editing it. On a
CDN or reverse proxy the output looks like this:</p>
<div class="language-nginx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-nginx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain"># nginx: exact-match legacy paths, generated from redirects.csv</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">map $request_uri $legacy_target {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    default                             "";</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    /display/DOCS/Authentication        /docs/guides/authentication/;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    /display/DOCS/Legacy+SDK            /docs/sdk/;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">server {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    if ($legacy_target != "") {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        return 301 $legacy_target;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre></div></div>
<p>Where the host cannot issue real redirects, Docusaurus can emit client-side ones
with <code>@docusaurus/plugin-client-redirects</code>:</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string" style="color:#c2185b">'@docusaurus/plugin-client-redirects'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#0a6d6b">redirects</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">{</span><span class="token keyword module" style="color:#00009f">from</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#c2185b">'/display/DOCS/Authentication'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#0a6d6b">to</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#c2185b">'/docs/guides/authentication/'</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><br></span></code></pre></div></div>
<p>Treat that as a fallback. It publishes an HTML page that redirects in the
browser, so the request still costs a round trip and the status code is 200, not
301. Use it for the long tail on a static host, and put the paths that carry
traffic behind a real 301 at the edge.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="test-the-map-before-cutover-then-keep-testing-it">Test the map before cutover, then keep testing it<a href="https://www.voix.md/blog/redirect-mapping-docs-migration/#test-the-map-before-cutover-then-keep-testing-it" class="hash-link" aria-label="Direct link to Test the map before cutover, then keep testing it" title="Direct link to Test the map before cutover, then keep testing it" translate="no">​</a></h2>
<p>A redirect map that nobody executed is a hypothesis. Run it against staging, and
keep the same script in CI so a later reorganisation cannot quietly break it:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token shebang important">#!/usr/bin/env bash</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#6a6558;font-style:italic"># redirect-check.sh https://docs.example.com</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token builtin class-name">set</span><span class="token plain"> </span><span class="token parameter variable" style="color:#0a6d6b">-uo</span><span class="token plain"> pipefail</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token assign-left variable" style="color:#0a6d6b">BASE</span><span class="token operator" style="color:#393A34">=</span><span class="token variable" style="color:#0a6d6b">${1</span><span class="token variable operator" style="color:#393A34">:?</span><span class="token variable" style="color:#0a6d6b">usage</span><span class="token variable operator" style="color:#393A34">:</span><span class="token variable" style="color:#0a6d6b"> redirect-check.sh BASE_URL}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token assign-left variable" style="color:#0a6d6b">fail</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#0a6d6b">0</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">while</span><span class="token plain"> </span><span class="token assign-left variable environment constant" style="color:#0a6d6b">IFS</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">, </span><span class="token builtin class-name">read</span><span class="token plain"> </span><span class="token parameter variable" style="color:#0a6d6b">-r</span><span class="token plain"> old new _reason</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">do</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token assign-left variable" style="color:#0a6d6b">first</span><span class="token operator" style="color:#393A34">=</span><span class="token variable" style="color:#0a6d6b">$(</span><span class="token variable function" style="color:#b31d28">curl</span><span class="token variable" style="color:#0a6d6b"> </span><span class="token variable parameter variable" style="color:#0a6d6b">-s</span><span class="token variable" style="color:#0a6d6b"> </span><span class="token variable parameter variable" style="color:#0a6d6b">-o</span><span class="token variable" style="color:#0a6d6b"> /dev/null </span><span class="token variable parameter variable" style="color:#0a6d6b">-w</span><span class="token variable" style="color:#0a6d6b"> </span><span class="token variable string" style="color:#c2185b">'%{http_code}'</span><span class="token variable" style="color:#0a6d6b"> </span><span class="token variable string" style="color:#c2185b">"</span><span class="token variable string variable" style="color:#0a6d6b">$BASE</span><span class="token variable string variable" style="color:#0a6d6b">$old</span><span class="token variable string" style="color:#c2185b">"</span><span class="token variable" style="color:#0a6d6b">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token builtin class-name">read</span><span class="token plain"> </span><span class="token parameter variable" style="color:#0a6d6b">-r</span><span class="token plain"> final_code hops final </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#b31d28">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:#0a6d6b">-sIL</span><span class="token plain"> </span><span class="token parameter variable" style="color:#0a6d6b">-o</span><span class="token plain"> /dev/null </span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token parameter variable" style="color:#0a6d6b">-w</span><span class="token plain"> </span><span class="token string" style="color:#c2185b">'%{http_code} %{num_redirects} %{url_effective}'</span><span class="token plain"> </span><span class="token string" style="color:#c2185b">"</span><span class="token string variable" style="color:#0a6d6b">$BASE</span><span class="token string variable" style="color:#0a6d6b">$old</span><span class="token string" style="color:#c2185b">"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"> </span><span class="token string" style="color:#c2185b">"</span><span class="token string variable" style="color:#0a6d6b">$first</span><span class="token string" style="color:#c2185b">"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token number" style="color:#0a6d6b">301</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"> </span><span class="token string" style="color:#c2185b">"</span><span class="token string variable" style="color:#0a6d6b">$hops</span><span class="token string" style="color:#c2185b">"</span><span class="token plain"> </span><span class="token parameter variable" style="color:#0a6d6b">-ne</span><span class="token plain"> </span><span class="token number" style="color:#0a6d6b">1</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">     </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"> </span><span class="token string" style="color:#c2185b">"</span><span class="token string variable" style="color:#0a6d6b">$final_code</span><span class="token string" style="color:#c2185b">"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token number" style="color:#0a6d6b">200</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"> </span><span class="token string" style="color:#c2185b">"</span><span class="token string variable" style="color:#0a6d6b">$final</span><span class="token string" style="color:#c2185b">"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token string" style="color:#c2185b">"</span><span class="token string variable" style="color:#0a6d6b">$BASE</span><span class="token string variable" style="color:#0a6d6b">$new</span><span class="token string" style="color:#c2185b">"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">then</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token builtin class-name">echo</span><span class="token plain"> </span><span class="token string" style="color:#c2185b">"FAIL </span><span class="token string variable" style="color:#0a6d6b">$old</span><span class="token string" style="color:#c2185b"> -&gt; </span><span class="token string variable" style="color:#0a6d6b">$final</span><span class="token string" style="color:#c2185b"> (first </span><span class="token string variable" style="color:#0a6d6b">$first</span><span class="token string" style="color:#c2185b">, </span><span class="token string variable" style="color:#0a6d6b">$hops</span><span class="token string" style="color:#c2185b"> hops, final </span><span class="token string variable" style="color:#0a6d6b">$final_code</span><span class="token string" style="color:#c2185b">)"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token assign-left variable" style="color:#0a6d6b">fail</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#0a6d6b">1</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">fi</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">done</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#b31d28">tail</span><span class="token plain"> </span><span class="token parameter variable" style="color:#0a6d6b">-n</span><span class="token plain"> +2 redirects.csv</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token builtin class-name">exit</span><span class="token plain"> </span><span class="token variable" style="color:#0a6d6b">$fail</span><br></span></code></pre></div></div>
<p>Four assertions, and each one catches a different failure. The first status code
catches 302s that someone configured by accident. The hop count catches chains.
The final URL catches rows pointing at the wrong page. The final status catches
redirect targets that are themselves 404 — the most common defect in a map built
the week the content moved.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cutover-checklist">Cutover checklist<a href="https://www.voix.md/blog/redirect-mapping-docs-migration/#cutover-checklist" class="hash-link" aria-label="Direct link to Cutover checklist" title="Direct link to Cutover checklist" translate="no">​</a></h2>
<ul class="contains-task-list containsTaskList_mC6p">
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Source list is the union of sitemap, analytics, logs, Search Console, a
crawl and a source grep, deduplicated and normalised</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Every source URL has a row with a target and a reason</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Zero chains, zero rows pointing at the site root</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->The map lives in the repo and changes through review</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Test script green against staging, then against production within minutes
of DNS or origin switching</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->New sitemap submitted; old host still resolving and still redirecting</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Support macros, in-product help links and onboarding emails updated to the
new URLs anyway — redirects are the safety net, not the plan</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->A dated note in the repo recording when the map was built and from which
exports</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-long-do-you-keep-the-redirects">How long do you keep the redirects?<a href="https://www.voix.md/blog/redirect-mapping-docs-migration/#how-long-do-you-keep-the-redirects" class="hash-link" aria-label="Direct link to How long do you keep the redirects?" title="Direct link to How long do you keep the redirects?" translate="no">​</a></h2>
<p>Permanently, unless you can prove nothing requests them. The cost of a redirect
row is a line in a config file; the cost of removing one that still gets traffic
is a support ticket you never trace back to this decision. If the list grows
uncomfortable, add hit counting at the edge and review annually — retire only
rows with no requests in a full year, and never retire a row that a released
client hardcodes.</p>
<p>The <a class="" href="https://www.voix.md/docs/">migration handbook</a> covers where the map fits in the cutover
sequence, and the <a class="" href="https://www.voix.md/blog/documentation-content-audit/">content audit post</a>
covers the inventory it is generated from. If you want redirects, link checks
and QA handled as part of the move rather than bolted on at the end, that scope
is described on the <a class="" href="https://www.voix.md/services/">services page</a>.</p>]]></content>
        <author>
            <name>Maria Gilca</name>
            <uri>https://www.voix.md/about/</uri>
        </author>
        <category label="Docs Migration" term="Docs Migration"/>
        <category label="Redirects" term="Redirects"/>
        <category label="Information Architecture" term="Information Architecture"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[What a documentation content audit actually finds]]></title>
        <id>https://www.voix.md/blog/documentation-content-audit/</id>
        <link href="https://www.voix.md/blog/documentation-content-audit/"/>
        <updated>2026-06-12T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[The categories of rot a documentation content audit turns up, how to count each one, and what decision each category earns before a migration starts.]]></summary>
        <content type="html"><![CDATA[<p>Every migration proposal contains the phrase "content audit", and almost nobody
says what comes out of one. It is not a reading exercise, and it is not a
quality score out of ten. It is a classification pass that ends with one
decision per page, a count next to each decision, and a named person who agreed
to it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-the-audit-comes-first">Why the audit comes first<a href="https://www.voix.md/blog/documentation-content-audit/#why-the-audit-comes-first" class="hash-link" aria-label="Direct link to Why the audit comes first" title="Direct link to Why the audit comes first" translate="no">​</a></h2>
<p>A migration that starts with the platform moves the rot into a nicer building.
You get clean Markdown, a fast search index, a pipeline that publishes on merge,
and the same 61 duplicate pages you had before, now in Git.</p>
<p>The audit is also the only honest input to the rest of the plan. You cannot size
an information architecture redesign without knowing how many pages exist. You
cannot build a redirect map without a list of source URLs. You cannot promise a
cutover date when nobody has counted what is being cut over. Every downstream
artefact in a migration is a projection of the inventory.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-categories-of-rot">The categories of rot<a href="https://www.voix.md/blog/documentation-content-audit/#the-categories-of-rot" class="hash-link" aria-label="Direct link to The categories of rot" title="Direct link to The categories of rot" translate="no">​</a></h2>
<p>Pages do not rot in one way. They rot in about eight, and each one earns a
different decision.</p>
<p><strong>Duplicates.</strong> The same procedure written twice, usually because two teams
owned the same feature at different times. Duplicates are cheap to find and
politically expensive to resolve, because merging them means one team's page
loses.</p>
<p><strong>Stale pages.</strong> The last meaningful edit predates a release that changed the
behaviour the page describes. Stale is not the same as old: a page about an API
that has not changed in three years is fine. Compare the last edit against the
product's release history, not against today's date.</p>
<p><strong>Orphans.</strong> Reachable by URL, not reachable by navigation, and not surfaced by
search. Orphans are usually the residue of a previous reorganisation. Some of
them still get traffic from Google, which is exactly why you find them before
cutover rather than after.</p>
<p><strong>Stubs.</strong> A heading, one sentence, and a "more coming soon" that was true four
years ago. Stubs cost more than they return: they occupy a navigation slot and
a search result, and they teach readers that the docs do not answer questions.</p>
<p><strong>Vendor artefacts.</strong> Content whose structure only exists because the old editor
produced it. Nested tables used for layout, styled spans wrapping every second
word, screenshots of text, macro-generated tables of contents, anchor names like
<code>id-a71f0e2</code>. The information is fine; the container is not.</p>
<p><strong>Wrong-audience pages.</strong> Internal runbooks published on the customer portal, or
customer-facing setup guides buried in an internal wiki. This category is worth
a separate count because the fix is a move, not a rewrite, and because one of
those moves may be a disclosure problem.</p>
<p><strong>Contradictions.</strong> Two pages that both look current and give different
instructions. This is the most expensive category, because a support answer is
only as good as the worse page, and because resolving it needs someone with
authority over the product, not over the docs.</p>
<p><strong>Dead weight.</strong> No inbound links, no traffic, no owner, no successor. Not
harmful, just carried. Cutting it is a decision, not a cleanup.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-do-you-count-all-this-without-reading-500-pages">How do you count all this without reading 500 pages?<a href="https://www.voix.md/blog/documentation-content-audit/#how-do-you-count-all-this-without-reading-500-pages" class="hash-link" aria-label="Direct link to How do you count all this without reading 500 pages?" title="Direct link to How do you count all this without reading 500 pages?" translate="no">​</a></h2>
<p>You do not read every page; you classify every page, which is a different job.
Classification comes from metadata plus the first screen of the page, and only
the ambiguous rows earn a real read. In practice that means joining three
sources into one table: the file inventory, the traffic export, and the link
graph.</p>
<p>Start with the inventory. If the content is already in Git:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#6a6558;font-style:italic"># One row per file, oldest last-touched first.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#b31d28">git</span><span class="token plain"> ls-files </span><span class="token string" style="color:#c2185b">'docs/**/*.md*'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">while</span><span class="token plain"> </span><span class="token builtin class-name">read</span><span class="token plain"> </span><span class="token parameter variable" style="color:#0a6d6b">-r</span><span class="token plain"> f</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">do</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token builtin class-name">printf</span><span class="token plain"> </span><span class="token string" style="color:#c2185b">'%s\t%s\n'</span><span class="token plain"> </span><span class="token string" style="color:#c2185b">"</span><span class="token string variable" style="color:#0a6d6b">$(</span><span class="token string variable function" style="color:#b31d28">git</span><span class="token string variable" style="color:#0a6d6b"> log </span><span class="token string variable parameter variable" style="color:#0a6d6b">-1</span><span class="token string variable" style="color:#0a6d6b"> </span><span class="token string variable parameter variable" style="color:#0a6d6b">--format</span><span class="token string variable operator" style="color:#393A34">=</span><span class="token string variable" style="color:#0a6d6b">%as -- </span><span class="token string variable string" style="color:#c2185b">"</span><span class="token string variable string variable" style="color:#0a6d6b">$f</span><span class="token string variable string" style="color:#c2185b">"</span><span class="token string variable" style="color:#0a6d6b">)</span><span class="token string" style="color:#c2185b">"</span><span class="token plain"> </span><span class="token string" style="color:#c2185b">"</span><span class="token string variable" style="color:#0a6d6b">$f</span><span class="token string" style="color:#c2185b">"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">done</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> </span><span class="token function" style="color:#b31d28">sort</span><br></span></code></pre></div></div>
<p>If it is still in a proprietary platform, the export manifest or the platform
API carries the same fields. Ask for <code>updated_at</code>, <code>created_at</code>, author and
parent page id before you ask for the HTML.</p>
<p>Duplicate titles are the cheapest first pass:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token function" style="color:#b31d28">grep</span><span class="token plain"> </span><span class="token parameter variable" style="color:#0a6d6b">-rh</span><span class="token plain"> </span><span class="token string" style="color:#c2185b">'^title:'</span><span class="token plain"> docs/ </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> </span><span class="token function" style="color:#b31d28">sort</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> </span><span class="token function" style="color:#b31d28">uniq</span><span class="token plain"> </span><span class="token parameter variable" style="color:#0a6d6b">-d</span><br></span></code></pre></div></div>
<p>Orphan candidates come from the link graph. A crude version that works well
enough to build a review list:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> </span><span class="token for-or-select variable" style="color:#0a6d6b">f</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> </span><span class="token variable" style="color:#0a6d6b">$(</span><span class="token variable function" style="color:#b31d28">git</span><span class="token variable" style="color:#0a6d6b"> ls-files </span><span class="token variable string" style="color:#c2185b">'docs/**/*.md'</span><span class="token variable" style="color:#0a6d6b">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">do</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token assign-left variable" style="color:#0a6d6b">slug</span><span class="token operator" style="color:#393A34">=</span><span class="token variable" style="color:#0a6d6b">$(</span><span class="token variable function" style="color:#b31d28">basename</span><span class="token variable" style="color:#0a6d6b"> </span><span class="token variable string" style="color:#c2185b">"</span><span class="token variable string variable" style="color:#0a6d6b">$f</span><span class="token variable string" style="color:#c2185b">"</span><span class="token variable" style="color:#0a6d6b"> .md</span><span class="token variable" style="color:#0a6d6b">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token plain"> </span><span class="token function" style="color:#b31d28">grep</span><span class="token plain"> </span><span class="token parameter variable" style="color:#0a6d6b">-rq</span><span class="token plain"> -- </span><span class="token string" style="color:#c2185b">"</span><span class="token string variable" style="color:#0a6d6b">$slug</span><span class="token string" style="color:#c2185b">"</span><span class="token plain"> docs/ </span><span class="token parameter variable" style="color:#0a6d6b">--exclude</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#c2185b">"</span><span class="token string variable" style="color:#0a6d6b">$(</span><span class="token string variable function" style="color:#b31d28">basename</span><span class="token string variable" style="color:#0a6d6b"> </span><span class="token string variable string" style="color:#c2185b">"</span><span class="token string variable string variable" style="color:#0a6d6b">$f</span><span class="token string variable string" style="color:#c2185b">"</span><span class="token string variable" style="color:#0a6d6b">)</span><span class="token string" style="color:#c2185b">"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">then</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token builtin class-name">echo</span><span class="token plain"> </span><span class="token string" style="color:#c2185b">"orphan candidate: </span><span class="token string variable" style="color:#0a6d6b">$f</span><span class="token string" style="color:#c2185b">"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">fi</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">done</span><br></span></code></pre></div></div>
<p>Then join traffic. Export twelve months of sessions per URL from analytics and
match on path. Zero sessions is a signal, not a verdict — release notes,
compliance pages and error-code references are supposed to have thin traffic and
still have to exist.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-worked-inventory">A worked inventory<a href="https://www.voix.md/blog/documentation-content-audit/#a-worked-inventory" class="hash-link" aria-label="Direct link to A worked inventory" title="Direct link to A worked inventory" translate="no">​</a></h2>
<p>The table below is a worked example, not a client result. It exists to show the
shape of the output: one row per category, a count, a decision, and the person
who owns the decision.</p>
<table><thead><tr><th>Category</th><th style="text-align:right">Pages</th><th style="text-align:right">Share</th><th>Decision</th><th>Who decides</th></tr></thead><tbody><tr><td>Current and correct</td><td style="text-align:right">214</td><td style="text-align:right">43%</td><td>Migrate as is</td><td>Docs owner</td></tr><tr><td>Duplicate of another page</td><td style="text-align:right">61</td><td style="text-align:right">12%</td><td>Merge, redirect the loser</td><td>Docs owner</td></tr><tr><td>Stale, product moved on</td><td style="text-align:right">78</td><td style="text-align:right">16%</td><td>Rewrite or retire before cutover</td><td>Product manager</td></tr><tr><td>Stub</td><td style="text-align:right">40</td><td style="text-align:right">8%</td><td>Cut, keep the title in a backlog</td><td>Docs owner</td></tr><tr><td>Vendor artefact only</td><td style="text-align:right">33</td><td style="text-align:right">7%</td><td>Migrate, strip markup, re-cut sections</td><td>Migration engineer</td></tr><tr><td>Wrong audience</td><td style="text-align:right">22</td><td style="text-align:right">4%</td><td>Move to the internal space</td><td>Support lead</td></tr><tr><td>Contradictory pair</td><td style="text-align:right">18</td><td style="text-align:right">4%</td><td>Escalate, one owner picks the truth</td><td>Engineering owner</td></tr><tr><td>Dead weight</td><td style="text-align:right">30</td><td style="text-align:right">6%</td><td>Cut, redirect to the parent section</td><td>Docs owner</td></tr><tr><td><strong>Total</strong></td><td style="text-align:right"><strong>496</strong></td><td style="text-align:right"><strong>100%</strong></td><td></td><td></td></tr></tbody></table>
<p>Two things about that table matter more than the numbers. First, the counts add
up, which means every page has exactly one home and no page was quietly skipped.
Second, the last column is filled in. An audit with no names in it is a
document; an audit with names is a plan.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-each-decision-costs-downstream">What each decision costs downstream<a href="https://www.voix.md/blog/documentation-content-audit/#what-each-decision-costs-downstream" class="hash-link" aria-label="Direct link to What each decision costs downstream" title="Direct link to What each decision costs downstream" translate="no">​</a></h2>
<p>Every row generates work somewhere else, and this is the part teams discover
late:</p>
<ul>
<li class=""><strong>Migrate as is</strong> is the only decision with no extra cost. Protect this
category by not inflating it.</li>
<li class=""><strong>Merge</strong> creates at least one redirect row and one editorial pass. Two pages
never merge cleanly; someone has to decide which examples survive.</li>
<li class=""><strong>Rewrite</strong> needs a subject-matter reviewer with time in the same sprint as
the cutover. This is the row that slips.</li>
<li class=""><strong>Cut</strong> creates a redirect row and, if the page had traffic, a note on the
successor page saying what replaced it.</li>
<li class=""><strong>Move</strong> may need an access-control change, not just a path change.</li>
<li class=""><strong>Escalate</strong> needs a decision meeting on a calendar, with the two page owners
in the room. Book it during the audit, not after.</li>
</ul>
<p>Sequence the work so that everything except "rewrite" happens before cutover.
Rewrites can land after launch on real pages in Git; contradictions cannot,
because they will be baked into the new navigation.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-audit-deliverables-checklist">The audit deliverables checklist<a href="https://www.voix.md/blog/documentation-content-audit/#the-audit-deliverables-checklist" class="hash-link" aria-label="Direct link to The audit deliverables checklist" title="Direct link to The audit deliverables checklist" translate="no">​</a></h2>
<p>An audit is done when all of these exist and someone has signed off on the
counts:</p>
<ul class="contains-task-list containsTaskList_mC6p">
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->One row per source URL: title, owner, last meaningful edit, twelve-month
sessions, category, decision, target path</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->A count and a share per category, agreed in a meeting where someone was
allowed to say no</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->A list of contradictory pairs, each with a named decision owner and a date</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->A first-cut target information architecture derived from the surviving
pages, not from the old navigation</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->A redirect map seed, which is simply the inventory filtered to every row
whose target path differs from its source path</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->A rewrite backlog with owners, separated into "before cutover" and "after"</li>
</ul>
<p>The last two are why the audit pays for itself. The redirect map and the
navigation model both fall out of a table you had to build anyway, and both are
much more expensive to reconstruct in the week before launch.</p>
<p>If you are running this yourself, the <a class="" href="https://www.voix.md/docs/">migration handbook</a> covers the
same pass in more detail, including how the target information architecture
comes out of the inventory. If you would rather hand it over, the inventory is
the first deliverable of a migration engagement — see what that covers on the
<a class="" href="https://www.voix.md/services/">services page</a>, and how it is scoped and quoted on
<a class="" href="https://www.voix.md/pricing/">pricing</a>.</p>]]></content>
        <author>
            <name>Maria Gilca</name>
            <uri>https://www.voix.md/about/</uri>
        </author>
        <category label="Content Audit" term="Content Audit"/>
        <category label="Docs Migration" term="Docs Migration"/>
        <category label="Content Structure" term="Content Structure"/>
        <category label="Governance" term="Governance"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Why documentation is a product, not a checklist]]></title>
        <id>https://www.voix.md/blog/importance-of-documentation/</id>
        <link href="https://www.voix.md/blog/importance-of-documentation/"/>
        <updated>2026-02-27T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[What changes when documentation is treated as a product with ownership, architecture, and delivery workflows instead of a pre-release checklist.]]></summary>
        <content type="html"><![CDATA[<p>Documentation is often treated as a final step before release. That is usually
where teams lose adoption, speed, and trust.</p>
<p>When docs are clear, current, and easy to navigate, users solve problems on
their own. They onboard faster, use more of your product, and open fewer
support tickets.</p>
<p>When docs are outdated or scattered across tools, support becomes the fallback
for basic questions. Engineering gets interrupted, launch confidence drops, and
teams spend time repeating answers instead of shipping improvements.</p>
<p>Strong documentation does four jobs at once:</p>
<ul>
<li class="">It reduces support load by answering frequent questions early</li>
<li class="">It improves activation by making key workflows easier to complete</li>
<li class="">It protects quality by keeping release notes, APIs, and guides in sync</li>
<li class="">It preserves institutional knowledge as teams and products evolve</li>
</ul>
<p>Treating documentation like a product means giving it ownership, architecture,
and delivery workflows. Version control, review gates, and CI/CD publishing
make documentation reliable in the same way they make code reliable.</p>
<p>Good docs are not just helpful content. They are infrastructure for growth.</p>]]></content>
        <author>
            <name>voix</name>
            <uri>https://www.voix.md/about/</uri>
        </author>
        <category label="Docs Strategy" term="Docs Strategy"/>
        <category label="Docs Architecture" term="Docs Architecture"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Is your documentation strategy ready for AI?]]></title>
        <id>https://www.voix.md/blog/is-your-docs-strategy-ready-for-ai/</id>
        <link href="https://www.voix.md/blog/is-your-docs-strategy-ready-for-ai/"/>
        <updated>2026-02-27T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Seven architecture decisions, from a single source of truth to metadata and access control, that decide whether documentation is usable by AI.]]></summary>
        <content type="html"><![CDATA[<p>Your documentation is one of your most strategic product assets. But in many
teams, it is still scattered across legacy tools, shared drives, and isolated
portals that do not sync.</p>
<p>The challenge is no longer just writing content. The challenge is delivering
accurate knowledge, securely and at scale, to users, support teams, and AI
systems.</p>
<p>At voix, we design documentation platforms that turn static files into governed,
AI-ready knowledge systems.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-build-a-single-source-of-truth">1. Build a single source of truth<a href="https://www.voix.md/blog/is-your-docs-strategy-ready-for-ai/#1-build-a-single-source-of-truth" class="hash-link" aria-label="Direct link to 1. Build a single source of truth" title="Direct link to 1. Build a single source of truth" translate="no">​</a></h2>
<p>When docs live in multiple tools, version drift is guaranteed. One portal
shows an old workflow while the updated process sits somewhere else.</p>
<p>Consolidating docs into one governed source of truth solves this:</p>
<ul>
<li class="">updates happen once, then flow everywhere</li>
<li class="">teams work from the same approved version</li>
<li class="">users stop seeing contradictory instructions</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-make-content-portable-and-integration-ready">2. Make content portable and integration-ready<a href="https://www.voix.md/blog/is-your-docs-strategy-ready-for-ai/#2-make-content-portable-and-integration-ready" class="hash-link" aria-label="Direct link to 2. Make content portable and integration-ready" title="Direct link to 2. Make content portable and integration-ready" translate="no">​</a></h2>
<p>A modern documentation platform should not be a silo. It should connect to your
product, support, and learning ecosystems.</p>
<p>That means open formats, structured content, and API-friendly delivery so docs
can be reused across portals, in-app help, chat assistants, and internal tools.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-design-for-modular-reuse">3. Design for modular reuse<a href="https://www.voix.md/blog/is-your-docs-strategy-ready-for-ai/#3-design-for-modular-reuse" class="hash-link" aria-label="Direct link to 3. Design for modular reuse" title="Direct link to 3. Design for modular reuse" translate="no">​</a></h2>
<p>Teams lose time rewriting the same instructions for different audiences.
Component-level content architecture fixes that.</p>
<p>Instead of duplicating content, you define reusable blocks for procedures,
policies, and references, then assemble them into multiple outputs with clear
ownership and lifecycle controls.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-add-metadata-that-ai-can-actually-use">4. Add metadata that AI can actually use<a href="https://www.voix.md/blog/is-your-docs-strategy-ready-for-ai/#4-add-metadata-that-ai-can-actually-use" class="hash-link" aria-label="Direct link to 4. Add metadata that AI can actually use" title="Direct link to 4. Add metadata that AI can actually use" translate="no">​</a></h2>
<p>AI search only works when content is structured and tagged properly.</p>
<p>Strong metadata improves:</p>
<ul>
<li class="">semantic retrieval</li>
<li class="">faceted navigation</li>
<li class="">relevance by role, region, and product area</li>
<li class="">deduplication and content quality checks</li>
</ul>
<p>Without architecture and tagging, AI produces noisy answers from partial or
outdated context.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-automate-publishing-and-governance">5. Automate publishing and governance<a href="https://www.voix.md/blog/is-your-docs-strategy-ready-for-ai/#5-automate-publishing-and-governance" class="hash-link" aria-label="Direct link to 5. Automate publishing and governance" title="Direct link to 5. Automate publishing and governance" translate="no">​</a></h2>
<p>Manual uploads and ad hoc publishing create delays and errors.</p>
<p>With Docs-as-Code workflows, CI/CD pipelines, and quality gates, you can:</p>
<ul>
<li class="">publish faster with fewer regressions</li>
<li class="">enforce link, style, and structural checks</li>
<li class="">maintain audit trails across every content change</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="6-measure-usage-not-just-page-views">6. Measure usage, not just page views<a href="https://www.voix.md/blog/is-your-docs-strategy-ready-for-ai/#6-measure-usage-not-just-page-views" class="hash-link" aria-label="Direct link to 6. Measure usage, not just page views" title="Direct link to 6. Measure usage, not just page views" translate="no">​</a></h2>
<p>If you only track page opens, you miss how documentation is actually used.</p>
<p>Operational analytics help teams see where users drop off, what content is
reused, and which pages drive ticket deflection. That data makes content
strategy measurable and improvable.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="7-protect-access-with-enterprise-controls">7. Protect access with enterprise controls<a href="https://www.voix.md/blog/is-your-docs-strategy-ready-for-ai/#7-protect-access-with-enterprise-controls" class="hash-link" aria-label="Direct link to 7. Protect access with enterprise controls" title="Direct link to 7. Protect access with enterprise controls" translate="no">​</a></h2>
<p>Documentation must be accessible and secure at the same time.</p>
<p>Role-based access, SSO integration, and environment-level controls let you
deliver external and internal content safely without fragmenting your platform.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="final-point">Final point<a href="https://www.voix.md/blog/is-your-docs-strategy-ready-for-ai/#final-point" class="hash-link" aria-label="Direct link to Final point" title="Direct link to Final point" translate="no">​</a></h2>
<p>AI readiness is not a plugin. It is a content architecture decision.</p>
<p>Teams that invest in migration, structure, metadata, and governance now are the
ones that will get reliable results from AI later.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="frequently-asked-questions">Frequently asked questions<a href="https://www.voix.md/blog/is-your-docs-strategy-ready-for-ai/#frequently-asked-questions" class="hash-link" aria-label="Direct link to Frequently asked questions" title="Direct link to Frequently asked questions" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-changes-first-tools-or-structure">What changes first: tools or structure?<a href="https://www.voix.md/blog/is-your-docs-strategy-ready-for-ai/#what-changes-first-tools-or-structure" class="hash-link" aria-label="Direct link to What changes first: tools or structure?" title="Direct link to What changes first: tools or structure?" translate="no">​</a></h3>
<p>Structure first. Tool choices are easier and safer once information types,
metadata, and governance are defined.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="can-we-become-ai-ready-without-rewriting-everything">Can we become AI-ready without rewriting everything?<a href="https://www.voix.md/blog/is-your-docs-strategy-ready-for-ai/#can-we-become-ai-ready-without-rewriting-everything" class="hash-link" aria-label="Direct link to Can we become AI-ready without rewriting everything?" title="Direct link to Can we become AI-ready without rewriting everything?" translate="no">​</a></h3>
<p>Yes. The work phases well. Prioritize critical docs, add metadata, then
introduce templates and standards over time.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="do-we-need-to-replace-our-lms-or-support-portal">Do we need to replace our LMS or support portal?<a href="https://www.voix.md/blog/is-your-docs-strategy-ready-for-ai/#do-we-need-to-replace-our-lms-or-support-portal" class="hash-link" aria-label="Direct link to Do we need to replace our LMS or support portal?" title="Direct link to Do we need to replace our LMS or support portal?" translate="no">​</a></h3>
<p>Not always. A well-designed documentation platform can integrate with existing
systems while centralizing ownership and quality controls.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-do-we-avoid-migration-disruption">How do we avoid migration disruption?<a href="https://www.voix.md/blog/is-your-docs-strategy-ready-for-ai/#how-do-we-avoid-migration-disruption" class="hash-link" aria-label="Direct link to How do we avoid migration disruption?" title="Direct link to How do we avoid migration disruption?" translate="no">​</a></h3>
<p>Use phased rollouts, redirect planning, automated validation, and contributor
training so the operating model is stable before full cutover.</p>]]></content>
        <author>
            <name>voix</name>
            <uri>https://www.voix.md/about/</uri>
        </author>
        <category label="Docs Strategy" term="Docs Strategy"/>
        <category label="Docs Architecture" term="Docs Architecture"/>
        <category label="Governance" term="Governance"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Build an acting portfolio that gets you cast]]></title>
        <id>https://www.voix.md/blog/acting-portfolio-gets-you-cast/</id>
        <link href="https://www.voix.md/blog/acting-portfolio-gets-you-cast/"/>
        <updated>2026-01-03T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Portfolio-era guidance for actors on gathering materials, writing an honest bio, and owning the site instead of renting a monthly builder.]]></summary>
        <content type="html"><![CDATA[<p>A resume lists what you did. A portfolio shows what you can do.</p>
<p>For casting directors and agents, your portfolio is the only thing that matters before you enter the room. It proves your range. It validates your talent.</p>
<p>This guide explains how to build a portfolio that works as hard as you do. You will learn to capture attention, define your brand, and own your online presence without paying monthly rent on your website.</p>
<p>Whether you are a student or a working professional, this is how you set yourself apart.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-essentials">The essentials<a href="https://www.voix.md/blog/acting-portfolio-gets-you-cast/#the-essentials" class="hash-link" aria-label="Direct link to The essentials" title="Direct link to The essentials" translate="no">​</a></h2>
<ol>
<li class="">Gather your materials. Get professional headshots, a clean resume, and a sharp showreel.</li>
<li class="">Write a clear bio. State your training and your type. Be honest.</li>
<li class="">Build on solid ground. Use open-source tools like voix. Pay once. Own the code.</li>
<li class="">Add social proof. Display reviews, awards, and press.</li>
<li class="">Stay current. Update your look and your credits regularly.</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-to-build-your-portfolio-in-5-steps">How to build your portfolio in 5 steps<a href="https://www.voix.md/blog/acting-portfolio-gets-you-cast/#how-to-build-your-portfolio-in-5-steps" class="hash-link" aria-label="Direct link to How to build your portfolio in 5 steps" title="Direct link to How to build your portfolio in 5 steps" translate="no">​</a></h2>
<p>Your portfolio must be professional and personal. Follow these steps to build a site that tells the truth about your ability.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-select-your-materials">1. Select your materials<a href="https://www.voix.md/blog/acting-portfolio-gets-you-cast/#1-select-your-materials" class="hash-link" aria-label="Direct link to 1. Select your materials" title="Direct link to 1. Select your materials" translate="no">​</a></h3>
<p>Start with the basics. Do not clutter the page. You need four things:</p>
<ul>
<li class="">Headshots: Pick two or three images. One commercial (warm), one theatrical (serious), one neutral. Use good lighting. High resolution is mandatory.</li>
<li class="">Resume: List your contact info, union status, training, and credits. Keep it detailed. If you have special skills (combat, dance, accents), list them here.</li>
<li class="">Showreel: You have sixty seconds to impress a director. Keep your reel under two minutes. Put your best acting in the first ten seconds.</li>
<li class="">Contact info: Make it easy to find you.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-write-an-honest-bio">2. Write an honest bio<a href="https://www.voix.md/blog/acting-portfolio-gets-you-cast/#2-write-an-honest-bio" class="hash-link" aria-label="Direct link to 2. Write an honest bio" title="Direct link to 2. Write an honest bio" translate="no">​</a></h3>
<p>Your bio defines you. It is your introduction. Keep it to two paragraphs.</p>
<p>Casting directors want to know the person behind the character. Mention your background, your training, and what drives you. If you studied at a specific conservatory or have a niche skill, write it down.</p>
<p>Example:</p>
<blockquote>
<p>"I am a Chicago-based actor focused on gritty, realistic drama. I trained at The Second City and have appeared in independent features and regional theater. I bring intensity and focus to the work."</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-design-and-own-your-platform">3. Design and own your platform<a href="https://www.voix.md/blog/acting-portfolio-gets-you-cast/#3-design-and-own-your-platform" class="hash-link" aria-label="Direct link to 3. Design and own your platform" title="Direct link to 3. Design and own your platform" translate="no">​</a></h3>
<p>Most portfolio builders rent you space. They charge you every month to keep your site alive.</p>
<p>Don't rent. Own.</p>
<p>Use an open-source builder like voix. You pay a single fee for the build. The code belongs to you. The only recurring cost is the domain name (the .com), which you pay directly to the registrar.</p>
<p>Your site needs these pages:</p>
<ul>
<li class="">Home/About: Your face and your bio.</li>
<li class="">Media: Your showreel and voice clips.</li>
<li class="">Gallery: Production stills and headshots.</li>
<li class="">Resume: A viewable list and a downloadable PDF.</li>
<li class="">Contact: Representation info and a direct email.</li>
</ul>
<p>Design tip: Keep it clean. Use white space. Use black text. Do not use flashy animations. Let your acting be the loudest thing on the page.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-prove-your-value">4. Prove your value<a href="https://www.voix.md/blog/acting-portfolio-gets-you-cast/#4-prove-your-value" class="hash-link" aria-label="Direct link to 4. Prove your value" title="Direct link to 4. Prove your value" translate="no">​</a></h3>
<p>Anyone can call themselves an actor. Prove it.</p>
<p>Add a section for press and achievements. This is social proof. It tells a director that others have hired you and liked the result.</p>
<ul>
<li class="">Awards: Display logos or certificates.</li>
<li class="">Reviews: Quote the best lines from reputable sources.</li>
<li class="">Testimonials: Ask directors or coaches for a sentence about your work ethic.</li>
</ul>
<p>If you are working on something now, list it. An active actor is a hireable actor.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-keep-it-fresh">5. Keep it fresh<a href="https://www.voix.md/blog/acting-portfolio-gets-you-cast/#5-keep-it-fresh" class="hash-link" aria-label="Direct link to 5. Keep it fresh" title="Direct link to 5. Keep it fresh" translate="no">​</a></h3>
<p>A stale portfolio hurts your career.</p>
<p>Update your headshots when you change your hair or age. Update your reel when you get better footage. If a credit on your resume is five years old and amateur, cut it.</p>
<p>Your portfolio is a living document. Treat it that way.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="frequently-asked-questions">Frequently asked questions<a href="https://www.voix.md/blog/acting-portfolio-gets-you-cast/#frequently-asked-questions" class="hash-link" aria-label="Direct link to Frequently asked questions" title="Direct link to Frequently asked questions" translate="no">​</a></h2>
<p>How long should my reel be? One to two minutes. Quality beats quantity. If the audio is bad, cut it.</p>
<p>Do I need a printed portfolio? Yes. Digital gets you the audition. Print is for the room. Bring a headshot and resume stapled together.</p>
<p>How often should I update? Every six months. Or immediately after you finish a major project.</p>]]></content>
        <author>
            <name>voix</name>
            <uri>https://www.voix.md/about/</uri>
        </author>
        <category label="Portfolio" term="Portfolio"/>
        <category label="Acting" term="Acting"/>
        <category label="Process" term="Process"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[A practical docs migration playbook]]></title>
        <id>https://www.voix.md/blog/docs-migration-playbook/</id>
        <link href="https://www.voix.md/blog/docs-migration-playbook/"/>
        <updated>2025-11-07T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A short playbook for the parts of a docs migration that outlast cutover, from scope and owners to quality gates, redirects, and contributor training.]]></summary>
        <content type="html"><![CDATA[<p>Documentation migrations fail in the handover phase more often than in content
conversion, and handover is the part nobody scopes.</p>
<p>Teams can usually move content. The hard part is making sure the new system
stays healthy after launch.</p>
<p>Use this playbook:</p>
<ul>
<li class="">Define scope: what is being migrated now vs later</li>
<li class="">Assign owners: who approves content and who maintains the platform</li>
<li class="">Set quality gates: linting, broken-link checks, and content review</li>
<li class="">Plan redirects: preserve URLs users and search engines already depend on</li>
<li class="">Train contributors: new workflows fail if only one person understands them</li>
</ul>
<p>Migration is successful when the new platform is easier to run than the old
one. That requires governance, not just scripts.</p>]]></content>
        <author>
            <name>voix</name>
            <uri>https://www.voix.md/about/</uri>
        </author>
        <category label="Docs Migration" term="Docs Migration"/>
        <category label="Governance" term="Governance"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Maintenance that keeps your voice current]]></title>
        <id>https://www.voix.md/blog/maintenance-that-works/</id>
        <link href="https://www.voix.md/blog/maintenance-that-works/"/>
        <updated>2025-08-26T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Portfolio-era notes on an annual care plan, what routine maintenance covers, and why the site owner keeps control of the repository.]]></summary>
        <content type="html"><![CDATA[<p>A portfolio should not drift out of date. The annual care plan keeps your
content current without a long queue or another subscription.</p>
<p>We handle:</p>
<ul>
<li class="">New projects and updates</li>
<li class="">Small copy edits</li>
<li class="">Publishing support</li>
<li class="">Lightweight design tweaks</li>
</ul>
<p>You keep control of the repo, and we keep your voice consistent.</p>]]></content>
        <author>
            <name>voix</name>
            <uri>https://www.voix.md/about/</uri>
        </author>
        <category label="Maintenance" term="Maintenance"/>
        <category label="Portfolio" term="Portfolio"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[The six beats of a strong case study]]></title>
        <id>https://www.voix.md/blog/case-study-beats/</id>
        <link href="https://www.voix.md/blog/case-study-beats/"/>
        <updated>2025-08-01T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Portfolio-era guidance on the six beats of a short case study, from context and challenge through craft, outcome, and takeaway.]]></summary>
        <content type="html"><![CDATA[<p>Great case studies are short and structured. We use six beats so every project
reads quickly and still feels human.</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>Quick rule</div><div class="admonitionContent_BuS1"><p>If a reader cannot summarize the project in 20 seconds, it is too long.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-six-beats">The six beats<a href="https://www.voix.md/blog/case-study-beats/#the-six-beats" class="hash-link" aria-label="Direct link to The six beats" title="Direct link to The six beats" translate="no">​</a></h2>
<ol>
<li class="">The context</li>
<li class="">The challenge</li>
<li class="">Your approach</li>
<li class="">The craft or tools</li>
<li class="">The outcome</li>
<li class="">The takeaway</li>
</ol>
<p>Keep each beat to 1-3 sentences. Add one metric or quote if you have it.</p>]]></content>
        <author>
            <name>voix</name>
            <uri>https://www.voix.md/about/</uri>
        </author>
        <category label="Portfolio" term="Portfolio"/>
        <category label="Process" term="Process"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[The no-license portfolio stack]]></title>
        <id>https://www.voix.md/blog/no-license-portfolio-stack/</id>
        <link href="https://www.voix.md/blog/no-license-portfolio-stack/"/>
        <updated>2025-05-29T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Portfolio-era notes on an open-source publishing stack with no per-seat fees or paywalled tools, and what is actually billed after the build.]]></summary>
        <content type="html"><![CDATA[<p>voix sites are built on an open-source stack, which means you never pay
software license fees to keep your portfolio live.</p>
<p>Here is what that means in practice:</p>
<ul>
<li class="">No monthly per-seat fees</li>
<li class="">No paywalled publishing tools</li>
<li class="">No surprise charges when your site grows</li>
</ul>
<p>You pay for the build once, then an annual care plan if you want ongoing
updates. Hosting on GitHub Pages is free, and custom domains are billed at
cost.</p>
<p>This structure keeps your portfolio lean, predictable, and fully in your
control.</p>]]></content>
        <author>
            <name>voix</name>
            <uri>https://www.voix.md/about/</uri>
        </author>
        <category label="Pricing" term="Pricing"/>
        <category label="Process" term="Process"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Why voice matters more than a template]]></title>
        <id>https://www.voix.md/blog/voice-first-portfolios/</id>
        <link href="https://www.voix.md/blog/voice-first-portfolios/"/>
        <updated>2025-05-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Portfolio-era advice on shaping the story before the site, so a portfolio sounds like its owner rather than the template it was built from.]]></summary>
        <content type="html"><![CDATA[<p>A portfolio should sound like you, not like the last template you downloaded.
At voix, the goal is simple: shape the story first, then design the site
around it.</p>
<p>When your voice is clear, clients understand what you do, why it matters, and
how to work with you. That clarity is the difference between a skim and a
reply.</p>
<p>We start with a one-line promise, build a short narrative around it, and use
case studies to prove it. Design supports the story, not the other way around.</p>]]></content>
        <author>
            <name>voix</name>
            <uri>https://www.voix.md/about/</uri>
        </author>
        <category label="Voice" term="Voice"/>
        <category label="Portfolio" term="Portfolio"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Information architecture for products that keep growing]]></title>
        <id>https://www.voix.md/blog/information-architecture-for-growing-products/</id>
        <link href="https://www.voix.md/blog/information-architecture-for-growing-products/"/>
        <updated>2025-04-18T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[How to treat documentation information architecture as real architecture, with a shared taxonomy, information types, metadata, and governance.]]></summary>
        <content type="html"><![CDATA[<p>Documentation breaks down when product complexity outgrows the original
navigation model.</p>
<p>Information architecture is how you prevent that breakdown. It defines how
content is grouped, labeled, linked, and maintained over time.</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>IA checkpoint</div><div class="admonitionContent_BuS1"><p>If two teams describe the same feature with different terms, your IA needs a
controlled vocabulary.</p></div></div>
<p>A scalable IA usually includes:</p>
<ol>
<li class="">A product taxonomy shared across docs, support, and release workflows</li>
<li class="">Information types (concept, task, reference, tutorial) with templates</li>
<li class="">Metadata standards for audience, lifecycle, and product area</li>
<li class="">Governance for ownership, review frequency, and deprecation</li>
</ol>
<p>When IA is treated as architecture instead of cleanup work, documentation stays
coherent as teams, APIs, and products evolve.</p>]]></content>
        <author>
            <name>voix</name>
            <uri>https://www.voix.md/about/</uri>
        </author>
        <category label="Information Architecture" term="Information Architecture"/>
        <category label="Docs Architecture" term="Docs Architecture"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Why documentation structure matters more than writing style]]></title>
        <id>https://www.voix.md/blog/why-doc-structure-matters/</id>
        <link href="https://www.voix.md/blog/why-doc-structure-matters/"/>
        <updated>2024-09-03T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Why predictable structure does more for documentation than polished prose, and the page patterns that keep answers findable under pressure.]]></summary>
        <content type="html"><![CDATA[<p>Good writing helps. Good structure scales.</p>
<p>If users cannot predict where information lives, even clear writing becomes
hard to use. Structure is what makes documentation navigable under pressure.</p>
<p>Strong documentation structure gives teams:</p>
<ul>
<li class="">Faster answers through consistent page patterns</li>
<li class="">Better search performance with predictable headings and metadata</li>
<li class="">Easier maintenance because content types are clearly separated</li>
<li class="">Fewer contradictions between guides, references, and release notes</li>
</ul>
<p>A practical baseline is simple:</p>
<ul>
<li class="">Keep one task per page</li>
<li class="">Separate concepts, procedures, and API reference content</li>
<li class="">Use consistent heading depth and naming conventions</li>
<li class="">Add ownership and review cadence to critical pages</li>
</ul>
<p>Style improves readability. Structure improves findability and trust.</p>]]></content>
        <author>
            <name>voix</name>
            <uri>https://www.voix.md/about/</uri>
        </author>
        <category label="Content Structure" term="Content Structure"/>
        <category label="Developer Experience" term="Developer Experience"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[How to migrate docs without vendor lock-in]]></title>
        <id>https://www.voix.md/blog/docs-migration-without-lockin/</id>
        <link href="https://www.voix.md/blog/docs-migration-without-lockin/"/>
        <updated>2024-03-12T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[The five steps that move documentation onto a stack you own, from inventory and target model through conversion, validation, and CI/CD cutover.]]></summary>
        <content type="html"><![CDATA[<p>Moving documentation out of an expensive platform can feel risky, but staying
locked in is usually more expensive over time.</p>
<p>The goal is not just to move pages. The goal is to move to a system you own,
where content is versioned, testable, and portable.</p>
<p>A reliable migration usually follows five steps:</p>
<ol>
<li class="">Inventory your content and identify duplicates, gaps, and stale pages.</li>
<li class="">Define the target information model (types, metadata, and templates).</li>
<li class="">Convert and normalize content into open formats like Markdown.</li>
<li class="">Validate links, embeds, redirects, and navigation before launch.</li>
<li class="">Cut over with CI/CD so updates stay safe after migration.</li>
</ol>
<p>The biggest mistake is treating migration as a copy-and-paste project. It is a
platform change, which means structure and workflows matter as much as content.</p>
<p>When done well, migration reduces tooling cost, improves collaboration, and
makes your documentation easier to maintain for years.</p>]]></content>
        <author>
            <name>voix</name>
            <uri>https://www.voix.md/about/</uri>
        </author>
        <category label="Docs Migration" term="Docs Migration"/>
        <category label="Docs as Code" term="Docs as Code"/>
    </entry>
</feed>