{
"path": "/3m3zys47zgctb",
"site": "at://did:plc:ofrbh253gwicbkc5nktqepol/site.standard.publication/3m3x4bgbsh22k",
"tags": [
"website"
],
"$type": "site.standard.document",
"title": "Link Rot and Eight Iterations of ewancroft.uk",
"content": {
"$type": "pub.leaflet.content",
"pages": [
{
"$type": "pub.leaflet.pages.linearDocument",
"blocks": [
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "I’ve somehow managed to create my own little link rot ecosystem. Eight versions of ewancroft.uk, each leaving a trail of dead URLs like breadcrumbs that have gone stale."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "The Hugo Years and the Great Migration"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "The biggest changes came during my Hugo phase – versions 2 through 4, roughly from late January to early December 2024. I loved Hugo back then: clean, fast, minimal fuss. But moving to my current custom data-display site shifted the entire file structure under my feet."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "It wasn’t a massive content library – maybe 20 links – but every single one instantly went obsolete. The sitemap got a full makeover, old permalinks disappeared, and anyone who had bookmarked posts ended up staring at 404s."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "Digital Entropy (and why stats are dodgy)"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Link rot isn’t new, and a few stats get quoted a lot. The issue? Not all of them are reliable."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 3,
"plaintext": "Early Studies"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 33,
"byteStart": 24
},
"features": [
{
"uri": "https://en.wikipedia.org/wiki/Link_rot",
"$type": "pub.leaflet.richtext.facet#link"
}
]
}
],
"plaintext": "One oft-cited line from Wikipedia says a 2003 study found about one link in every 200 breaks each week, suggesting a link “half-life” of 138 weeks. Makes sense mathematically, but the original study is hard to track down – it mostly circulates as a secondary citation. More folklore than fact."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 3,
"plaintext": "Recent Data"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 61,
"byteStart": 44
},
"features": [
{
"uri": "https://ahrefs.com/blog/link-rot-study/",
"$type": "pub.leaflet.richtext.facet#link"
}
]
},
{
"index": {
"byteEnd": 136,
"byteStart": 124
},
"features": [
{
"uri": "https://www.pewresearch.org/wp-content/uploads/sites/20/2024/05/pl_2024.05.17_link-rot_report.pdf",
"$type": "pub.leaflet.richtext.facet#link"
}
]
}
],
"plaintext": "Recent research paints a starker picture. A 2024 Ahrefs study sampled links created since 2013 and found around 66.5% dead. Pew Research found about 25% of pages from 2013–2023 were inaccessible by 2024. Credible studies, but still just a slice of reality. Online, entropy reigns: links decay unless we intervene."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "The Personal Cost"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "The tricky part isn’t technical – redirects and compatibility layers exist. It’s the realisation that I’d broken the implicit promise of permalinks. People expect URLs to be permanent. Sharing a link shouldn’t mean it disappears in months."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "I could have set up 301 redirects, or kept a compatibility layer. Instead, I did what most devs do: got excited about something new and ignored the old stuff."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "Stability, Version 2.0"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 63,
"byteStart": 32
},
"features": [
{
"uri": "https://atproto.com/specs/record-key",
"$type": "pub.leaflet.richtext.facet#link"
}
]
}
],
"plaintext": "Now, blog posts are anchored to AT Protocol record keys (rkeys) – stable identifiers in a repository collection. That’s why URLs look like this:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"plaintext": "ewancroft.uk/blog/3lylpbqlw4c2h"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "No titles, no slugs, no fragile folder structures. Ugly? Maybe. Stable? Definitely. Even if I rebuild the frontend or redo the architecture, the rkey sticks to the post. Identity is decoupled from presentation."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "It’s not immortal – if my DID disappears or the AT Protocol collapses, the links vanish. But compared to the handmade, slug-based URLs I was constantly breaking, it’s a major improvement. The weak point has moved closer to the protocol layer, making links more resilient to my own digital restlessness."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "Slugs as a Surface Layer"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 30,
"byteStart": 23
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#italic"
}
]
},
{
"index": {
"byteEnd": 64,
"byteStart": 50
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#italic"
}
]
}
],
"plaintext": "What if slugs didn’t replace the rkey, but just resolved to it? Keep the rkey as the canonical identifier, but let a human-friendly alias point to it."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 3,
"plaintext": "Inspiration from AT Protocol Handles"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 24,
"byteStart": 5
},
"features": [
{
"uri": "https://atproto.com/specs/handle",
"$type": "pub.leaflet.richtext.facet#link"
}
]
},
{
"index": {
"byteEnd": 64,
"byteStart": 52
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 120,
"byteStart": 88
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 210,
"byteStart": 187
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "Like AT Protocol handles: your domain handle (e.g., ewancroft.uk) resolves to your DID (did:plc:ofrbh253gwicbkc5nktqepol for me), which is the canonical identity. Verified via DNS TXT or .well-known/atproto-did. Once resolved, the DID is the truth."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 3,
"plaintext": "Slug → rkey Example"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"plaintext": "ewancroft.uk/blog/wolf-king-vs-wereworld\n ↳ resolves to ewancroft.uk/blog/3lylpbqlw4c2h"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 22,
"byteStart": 9
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 40,
"byteStart": 30
},
"features": [
{
"uri": "https://atproto.com/specs/record-key",
"$type": "pub.leaflet.richtext.facet#link"
}
]
},
{
"index": {
"byteEnd": 65,
"byteStart": 44
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
},
{
"index": {
"byteEnd": 155,
"byteStart": 70
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#italic"
}
]
}
],
"plaintext": "The post 3lylpbqlw4c2h is the record key in com.whtwnd.blog.entry for “Wolf King vs. Wereworld: My Completely Unqualified Take on Netflix's Adaptation”. Slugs are cosmetic – readable and SEO-friendly – rkeys are the truth."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 3,
"plaintext": "Resolver in Practice"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Each blog entry has its title, so a resolver can slugify it, compare it to the incoming slug, and redirect to the rkey if needed:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.code",
"language": "ts",
"plaintext": "function slugify(title) {\n return title.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n}\n\nfunction resolveRkeyFromSlug(slug, posts) {\n for (let post of posts) {\n if (slugify(post.title) === slug) return post.rkey;\n }\n return null;\n}"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Rkeys remain canonical. Slugs can change, titles can change, but the rkey doesn’t move."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "How AT Protocol Works"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.unorderedList",
"children": [
{
"content": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 17,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
},
{
"index": {
"byteEnd": 65,
"byteStart": 42
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "Handles → DIDs: Verified via DNS TXT or .well-known/atproto-did. DID = canonical identity."
}
},
{
"content": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 22,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
}
],
"plaintext": "DIDs → Repositories: Each DID owns a repository (posts, profiles, lists)."
}
},
{
"content": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 12,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
},
{
"index": {
"byteEnd": 71,
"byteStart": 50
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "Collections: Records live inside collections like com.whtwnd.blog.entry."
}
},
{
"content": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 12,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
}
],
"plaintext": "Record Keys: Each record gets a stable rkey."
}
},
{
"content": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 11,
"byteStart": 0
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#bold"
}
]
},
{
"index": {
"byteEnd": 43,
"byteStart": 12
},
"features": [
{
"$type": "pub.leaflet.richtext.facet#code"
}
]
}
],
"plaintext": "Permalinks: ewancroft.uk/blog/3lylpbqlw4c2h maps to DID → repo → collection → rkey."
}
}
]
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Slugs sit on top as sugar; rkeys are the bedrock."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "The Irony of Citation"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Writing about link rot while citing external sources is a bit ironic. The Wikipedia article might vanish tomorrow. Ahrefs’ study? One redesign away from a dead link. Pew Research? Subject to institutional web policies."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Even rkey-based posts aren’t immune. They last as long as the protocol ecosystem does. Digital permanence is always a bet against entropy."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "Lessons Learned"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "I’ve become more deliberate. The new setup is simple, shallow, and identifiers don’t rely on mutable text. I’m thinking more about 301 redirects, archiving, and recording migrations."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "The rkey system has given me a frame of reference. I can’t freeze the web, but I can design my corner so changes cause less damage. Not immortality, but resilience."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.header",
"level": 2,
"plaintext": "Entropy vs Immortality"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"index": {
"byteEnd": 145,
"byteStart": 128
},
"features": [
{
"uri": "https://atproto.com/specs/record-key",
"$type": "pub.leaflet.richtext.facet#link"
}
]
}
],
"plaintext": "The web doesn’t last forever. Domains expire, protocols age, even institutions let archives rot. Entropy wins eventually. But AT Protocol rkeys slow decay, strengthen content, and push back against fragility."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Immortality online is a myth. Durability? That’s achievable if we design for it."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Eight versions down – who knows how many more to go. At least the next iteration of ewancroft.uk will stand on stronger foundations, with a few more stubborn URLs that refuse to die."
}
}
]
}
]
},
"publishedAt": "2025-09-14T15:39:48.761Z"
}