The redirect map is content

M31V treated old URLs as maintained public identifiers, giving redirects source files, validation, preview, release identity, and retirement policy.

M31V renamed an article from /articles/builds/ to /notes/reliable-builds/. I added a redirect directly to the server configuration and moved on.

Months later, a second migration redirected /notes/reliable-builds/ to /writing/reliable-builds/. The original URL now crossed two redirects. Another old path pointed at an article removed during a topic cleanup and ended at the not-found page.

The article source lived in version control, passed validation, appeared in preview, and belonged to a release. The URLs that led readers to it lived as accumulated server trivia.

I moved redirects into M31V's content model. An old URL was a public identifier with history, ownership, and a destination—not an incidental line added after publishing.

URLs outlived layouts and generators

M31V could change templates, content folders, and build tools without readers noticing. A public URL appeared in bookmarks, feeds, messages, search indexes, and other sites outside the project's control.

That made URL stability different from file organization. Moving a source file did not require a public path change. Changing a section label did not automatically justify renaming its entire archive.

When a public path did change, the old one retained value as an entry point. The redirect was part of the publication's promise that old references remained useful.

I wrote URL decisions into the content graph:

stable content ID
current canonical path
historical public paths
redirect status and destination
reason and introduction date

The current article and its old paths became related content records. A template redesign could no longer erase that history by moving directories.

The map used one-hop destinations

The first redirect file allowed one redirect to target another. It was easy to append a new rule and created chains.

M31V normalized every historical path directly to the current canonical destination:

/articles/builds/          -> /writing/reliable-builds/
/notes/reliable-builds/    -> /writing/reliable-builds/

When the canonical path changed again, the build updated all related historical sources. Validation rejected redirect destinations that were themselves redirect sources unless the rule explicitly represented an external boundary the build could not flatten.

One hop reduced latency and failure surface. More importantly, it made the map state the current relationship rather than replay every administrative move a URL had experienced.

History remained in version control and metadata. Readers did not need to traverse it one response at a time.

Paths occupied one namespace

Articles, topic archives, author pages, static assets, special endpoints, and redirect sources could all claim paths. Previously, the generator discovered collisions only when one file overwrote another.

The content graph created a public path registry:

canonical content path
generated archive path
redirect source path
reserved system path
asset path

Every claim carried source provenance. A collision error named both owners and their editable files.

A redirect source could not equal a live canonical path. A destination had to resolve to a public node or an explicitly allowed external URL. A new article could not casually reuse an old path merely because the original file was gone.

This made URL allocation a build-time decision. The filesystem stopped choosing public identity through last write.

The source file was intentionally boring

Redirects lived in a small structured file:

- from: /articles/builds/
  to: /writing/reliable-builds/
  status: permanent
  introduced: 2015-10-27
  reason: article section renamed

The format favored review over compactness. Paths were explicit. Status used a controlled value. Reason explained the editorial decision without becoming public response text.

Generated redirects from known slug changes could propose entries, but activation required the source record. I did not infer every missing page should redirect to a similarly named current page.

The file supported comments through fields rather than undocumented server syntax. It was portable across hosting implementations because it described URL relationships, not one server's directive language.

The build renderer converted the map into the target host configuration and a preview representation.

M31V used permanent redirects for intentional public moves and temporary redirects only when the canonical relationship was expected to revert or remain undecided.

The choice was not a performance toggle. Caches and search systems could treat permanent and temporary moves differently. Using a permanent status for an experiment could make reversal difficult for readers with cached responses.

The map required status and validation limited choices to project policy. A temporary rule carried a review date or owner. A permanent move required a destination intended to be canonical.

I tested actual response status and Location header after deployment. A correct map rendered through a misconfigured server could still emit an unexpected status.

The article did not turn into an HTTP-status tutorial. The key was that response semantics were content policy and deserved deliberate source, not a default copied from the previous rule.

Query strings needed an explicit policy

Path redirects could preserve, drop, or transform query parameters depending on server behavior. M31V's old campaign and feed parameters should generally survive a canonical path move, while obsolete preview parameters should not become part of the new canonical URL.

The redirect renderer had a declared default and allowed narrow exceptions. Validation fixtures tested representative URLs:

/articles/builds/?ref=feed
  -> /writing/reliable-builds/?ref=feed

The destination page's canonical metadata omitted tracking parameters. Redirect preservation and canonical identity solved different problems.

I avoided broad regular expressions that captured arbitrary query text into a destination path. Transformation rules were rare, reviewed, and tested with unsafe input.

A redirect record described more than two strings when request components affected the result.

URL fragments are handled by the browser and ordinarily do not travel in the HTTP request. A server redirect from an old article path could not inspect or translate an old heading fragment reliably.

Changing heading IDs therefore had a different compatibility policy. M31V generated stable heading identifiers from explicit or preserved source IDs and retained legacy anchors in the article when headings were renamed significantly.

For example, an old #release-checks anchor could remain as an empty positioned target before the renamed “Artifact verification” heading. The article source recorded that alias.

The redirect map handled paths. The content document handled fragment identity. Treating both as public references prevented a path move from succeeding while every deep link still broke.

This was one reason the normalized document graph owned heading IDs rather than deriving them afresh from visible text on every build.

Loops were graph errors

Handwritten server rules could create direct or indirect loops:

/old-a/ -> /new-a/
/new-a/ -> /old-a/

M31V represented redirects as a directed graph and traversed every source to a terminal destination. It rejected cycles and produced the path involved.

The check also rejected self-redirects after normalization. Trailing slash or case normalization could make two visually different strings equivalent under host policy.

Redirect loop:
/articles/builds -> /articles/builds/ -> /articles/builds

The validator used the same normalization rules as the server renderer. Otherwise the source graph could appear acyclic while host behavior looped.

Candidate verification requested a sample of generated rules through the real host configuration, adding evidence that rendering preserved the graph.

A redirect could point at an article excluded as draft, a removed topic archive, or a misspelled path. The server would faithfully send readers to a 404.

The graph required internal destinations to resolve in the candidate public view. A scheduled destination could not receive a public redirect before it published unless the redirect activation shared the same release.

External destinations passed a stricter review path. The build validated URL shape and safe scheme but did not make a transient remote check the sole release gate. The source record named why M31V was sending readers outside its domain.

If content was intentionally gone with no meaningful replacement, the project could serve a deliberate gone or archival response according to policy. It did not redirect every removed article to the home page, where the apparent success hid loss of meaning.

Destination validity was editorial judgment backed by graph checks.

Similarity was a suggestion, not a rule

When an old path lacked a destination, I experimented with choosing the current article whose title or slug looked closest. The suggestions were useful during migration and unsafe for automatic publication.

/guides/secure-cookies/ and /guides/cookie-recipes/ could be textually similar and serve very different intent. A confident redirect would prevent a not-found signal while misleading the reader.

M31V's migration report proposed candidates with title, description, and topic context. A person selected a semantically appropriate destination or marked the old content retired.

The accepted decision became source data with a reason. The build never reran similarity at request time.

Automation helped inventory and triage. Public identity changes required accountable editorial choice.

The old preview server rendered current pages and ignored host redirects. Reviewers could not test historical URLs until publication.

Artifact preview loaded the generated redirect map into candidate routing. The review index listed added, changed, and removed rules. Following an old URL showed status, destination, and final page.

A redirect inspector detected chains at runtime, compared response with source record, and verified that canonical metadata on the destination agreed with the map.

The candidate manifest counted redirects and included their normalized digest. Upload verification ensured the host received the same map. Activation moved content and redirect configuration under one release identity as closely as the hosting layer allowed.

Redirect behavior became reviewable release output, not a post-publish server tweak.

An old feed entry became a test fixture

Feeds preserve URLs for years in readers and archives. M31V kept representative historical feed entries and external-link fixtures as URL tests.

Each fixture started from an old public URL and asserted:

first response status
number of hops
final canonical path
destination build contains the content
query preservation where expected
legacy fragment target where relevant

The suite also tested trailing slash, uppercase variants under known host policy, and percent-encoded paths without inventing dangerous normalization.

This turned vague backward compatibility into executable examples. The redirect map could be refactored or rendered for a different server while old references retained their expected journey.

Not every URL ever emitted became a fixture. High-value patterns and every migration boundary did.

A canonical link in HTML can tell consumers which URL the page prefers. It does nothing for a request to an old path if the server has no page there.

Conversely, a redirect moves the request but a misconfigured destination page could declare a different canonical URL. M31V checked both.

The normalized article node owned canonical path. Redirect records targeted it. The page renderer emitted it. Feed and sitemap used it. Verification compared the actual response chain and document metadata.

This prevented each SEO-flavored mechanism from becoming an isolated checklist item. They were different expressions of one public identity model.

I used the language of readers and identifiers more than ranking. Search behavior mattered, but preserving a bookmark's meaning was sufficient reason.

One redirect adds another request-response exchange before content. On a warm fast connection it feels small. On a constrained network, chains are visible delay.

Flattening internal redirect chains kept the cost to one hop. M31V also updated its own source links, feed URLs, sitemap, and canonical references to point directly at current paths. Redirects existed for external and historical references, not as routine internal navigation.

The build warned when an internal source link targeted a redirect. It offered the canonical destination and source line.

HTTP/2 did not remove the round trip of an initial redirect. Connection reuse could help the next request, but the browser still needed the redirect response before knowing the destination.

URL maintenance was performance work as well as compatibility work.

Security constrained destinations

Redirects can become an open-redirect vulnerability if arbitrary user input controls destinations. M31V's static map used validated, declared targets and did not offer a generic ?next= endpoint.

Internal destinations had origin-relative paths. External destinations required an allowed secure scheme and explicit host in source review. Header rendering rejected control characters and malformed encodings.

The map did not interpolate request query values into external URLs. Narrow parameter-preservation rules operated through the server's safe configuration primitives.

Deployment tests included hostile-looking encoded paths to ensure normalization did not bypass reserved namespaces or create a loop.

Moving redirect rules into content did not make them harmless. It made their input finite, reviewable, and testable before reaching server configuration.

Adding a redirect preserved access at small runtime and maintenance cost. Removing one could break references the publication could not inventory completely.

M31V tracked introduction date, observed request activity where privacy-respecting server logs allowed it, and whether the source path belonged to a documented migration. Lack of recent requests was evidence, not proof that no bookmark existed.

Permanent content moves defaulted to long retention. Temporary campaign or preview rules carried explicit expiry and owner. Security or legal needs could override retention with a documented response policy.

The build did not delete a rule because its source file was old. Retirement was an editorial change reviewed like publication.

This asymmetry reflected consequence. Stale redirect entries cost some configuration and graph complexity. Removed identifiers cost reader trust.

The old server configuration contained redirect rules added for campaign measurement. Some created alternate public paths solely to count clicks, then became indexed and shared.

M31V separated campaign parameters from canonical paths and avoided manufacturing path aliases without a content reason. Where a short path was genuinely useful, it entered the redirect map with owner and retention policy.

Request metrics grouped historical sources under the destination's stable content ID while preserving which path was requested. That allowed migration analysis without treating each old URL as a separate article.

I kept query logging and retention constrained. URL maintenance did not justify collecting unnecessary reader data.

The content graph remained the authority for identity; analytics observed use rather than creating new canonical names through convenience.

Migration reports were content reviews

Before a large path change, M31V produced a report:

canonical paths added
canonical paths removed
redirects added or retargeted
historical paths without destination
internal links relying on redirects
feed and sitemap identity changes
legacy fragments at risk

The report made the blast radius visible before source edits became a candidate. A path removal without redirect required an explicit decision. A redirect to another redirect failed.

Reviewers could inspect content meaning, not just server syntax. “Does this old guide actually correspond to that new article?” was the central question.

The resulting candidate then proved implementation through graph validation and host checks.

Treating URL migration as editorial work prevented a technically clean rule set from mapping people to the wrong content.

M31V's first rules targeted one server syntax. Keeping the source map independent allowed a later renderer to generate another host's format.

The renderer owned details such as directive ordering, escaping, trailing-slash normalization, and status syntax. Golden fixtures compared representative source rules to expected configuration. Candidate tests exercised the generated behavior.

Not every host supported identical semantics. A renderer declared unsupported rules and failed rather than approximating silently. The content model stayed within the intersection M31V actually required.

This portability was a side effect of naming redirects as relationships rather than configuration lines. It did not promise effortless migration; it gave differences a place to surface.

The map belonged to the publication. Server configuration was one compiled representation.

Public identifiers became part of the archive

Once redirects entered the content graph, several practices aligned:

  • Article identity stayed stable across path changes.
  • Historical paths remained direct aliases to the current canonical path.
  • Page, feed, sitemap, and internal links shared one canonical URL.
  • Redirect loops, chains, collisions, and missing destinations failed before release.
  • Preview and deployment verified actual response behavior.
  • Retirement followed content policy rather than configuration cleanup.

The map was not glamorous, and it changed how I understood publishing. An article is more than its current file and rendered page. It is also the set of public references people have accumulated around it.

M31V had always treated prose and images as content. The breakthrough was recognizing that routes carried meaning too.

A redirect is not housekeeping around the archive. Properly maintained, it is the part of the archive that remembers where the reader came from.