DEV Community

Sub Engel
Sub Engel

Posted on

Writing docs that survive framework churn

Most of us have opened a project's docs, followed the setup steps, and hit a wall by step three. The CLI flag was renamed, the config file moved, the recommended package is deprecated. Usually the explanation of how the system works in that same document is still mostly right. But because it sits next to steps that are visibly broken, you stop trusting any of it.

That's the core problem: docs contain information with very different lifespans, and we tend to mix it all together. Separate it, and most of the rot becomes contained and easy to fix.

Sort information by how fast it goes stale

Roughly four layers:

Why (slow to change). Principles, architecture, trade-offs, the reasoning behind big decisions. "We keep business logic out of route handlers so it can be tested without HTTP." This stays true across framework rewrites.

Patterns (changes occasionally). How we usually do things. "Validate input at the edge, pass typed objects inward." "Every async UI state is one of idle, loading, success, error." The details shift; the idea holds.

How-to (changes with major versions). Setup, deployment, "how to add a new endpoint". Tied to specific tools and versions.

Reference (changes every release). CLI flags, config schemas, API endpoints. Ideally generated from the code, not written by hand.

The mistake is putting all four in one README section. A single framework upgrade then makes the whole thing look abandoned.

Keep "why" and "how" in separate documents

A typical setup doc mixes both:

# Getting started
1. Run `npm install`
2. Run `npm run dev`
3. Open http://localhost:3000

The app uses the Next.js App Router, so pages live in `app/` and data fetching happens in server components...
Enter fullscreen mode Exit fullscreen mode

When the project moves to a different framework, or even changes its dev port, every line here is suspect.

Split it instead. The architecture doc explains the idea and links to the current implementation:

# Architecture: rendering and data loading

Pages render on the server by default and fetch their own data, so the client bundle
stays small and there's no separate API layer for page data. Interactive pieces are
isolated client components.

Current implementation: Next.js App Router. See [Setup (Next.js)](./how-to/setup-next.md).
Previous approach: see [archive/pages-router.md](./archive/pages-router.md).
Enter fullscreen mode Exit fullscreen mode

And the setup doc is openly version-specific:

# Setup (Next.js)

Last verified: 2026-09-01 with Node 24, Next.js 16.

1. ...
Enter fullscreen mode Exit fullscreen mode

When the framework changes, you rewrite the setup doc and change one line in the architecture doc. The explanation, which is the expensive part to write, survives.

Describe contracts, not syntax

The same idea works at the level of individual examples. Instead of documenting "here's how we use useState for errors", document the shape of the thing:

type AsyncState<T> =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: T }
  | { status: "error"; error: Error };
Enter fullscreen mode Exit fullscreen mode

Then explain the rules in plain language: a retry moves from error back to loading, so stale errors never show next to fresh data. That holds whether the implementation is React hooks, a state machine library, or something that doesn't exist yet. Link to the current implementation for the framework-specific part.

Put a date and versions on anything that can go stale

Every how-to page gets a line at the top:

Last verified: 2026-09-01 · Node 24 · pnpm 10 · Next.js 16
Enter fullscreen mode Exit fullscreen mode

This does two things. Readers can judge at a glance whether to trust the page. And you get an obvious list of what to re-check when you bump a major dependency: search for the old version number.

If your docs have frontmatter, make it a field (last_verified: 2026-09-01) so a script can list pages that haven't been verified in, say, six months.

Deprecate instead of deleting

When a doc is outdated but some code still depends on it, don't delete it. Mark it:

> **Deprecated (2026-03).** This describes the Create React App setup.
> New projects: see [Setup (Vite)](./setup-vite.md).
> Kept for the legacy admin app, which still uses CRA.
Enter fullscreen mode Exit fullscreen mode

New readers get sent to the right place immediately, people maintaining old code still have what they need, and you have an explicit list of docs to remove once the legacy code is gone. Moving them into an archive/ folder with the banner intact works too.

Record decisions where the docs can link to them

Big "why" questions (why this database, why a monorepo, why this auth model) deserve short decision records: context, options considered, what was chosen, consequences. Keep them in the repo, link to them from the architecture docs, and mark them superseded rather than deleting them when things change. The architecture doc then stays short, and the detailed reasoning is one click away.

Review on a schedule, triggered by events

Different layers need different attention. A starting point:

Layer When to review
Architecture / why Once a year, or when the architecture actually changes
Patterns A few times a year, or when the team's habits shift
How-to / setup On every major dependency or tooling upgrade
Reference Every release (automate it if you can)
Deprecated docs When the legacy code they support is removed

The event-based triggers matter more than the calendar. Add "update the setup doc" to the checklist for dependency upgrades, and "does this PR change how someone uses the system?" to your review habits.

Let tooling catch the mechanical rot

Some rot is easy to detect automatically:

  • Broken links. Run a link checker like lychee or markdown-link-check in CI. Internal links break every time someone renames a file.
  • Code examples drifting from code. Where possible, embed examples from real source or test files instead of pasting them, or keep examples as small tests that run in CI. An example that's compiled is an example that's still true.
  • Stale pages. A small scheduled script that reads last_verified from frontmatter and opens an issue for anything older than six months turns review into a to-do list instead of a good intention.
  • Versioned doc sites. If users run old versions of your software, publish docs per version. Docusaurus has built-in versioning; for MkDocs, mike is the common choice.

Give each layer an owner

"Everyone owns the docs" means nobody does. Even on a small team, assign names: someone reviews the architecture docs yearly, whoever does a major upgrade updates the setup docs, reference docs are generated as part of the release. On a solo project the owner is you, but writing down when you'll review each layer still helps.

Where to start

You don't need to restructure everything. Start with the next doc you touch:

  1. Pull the "why" paragraphs out of setup instructions into their own page.
  2. Add a Last verified line with versions to every how-to you edit.
  3. Add a link checker to CI; it takes ten minutes and catches real problems immediately.

Over time the pages that explain your system stop being dragged down by the pages that describe this month's commands, and the parts that do go stale are small, dated, and obvious to fix.

More templates and a free Dataview starter pack are at forge.engelailabs.com.

Top comments (0)