Ewan’s Blog

Building a Decentralised Link Shortener with AT Protocol

Because... well, just because.

November 24, 2025

ewan's avatar
ewan
3mo

I really should make use of croft.click.

So here's a fun project: a link shortener that doesn't actually store any links. At least, not in the traditional sense.

In the world of centralised link shorteners like bit.ly and TinyURL, your shortened URLs live on someone else's server, subject to their terms of service and longevity. What if we could build a link shortener that's truly decentralised, storing your links on the AT Protocol network instead? Well, turns out you can. And it's surprisingly elegant.

The Big Picture

This project is a SvelteKit application that creates shortened URLs from your Linkat board on AT Protocol – the network that powers Bluesky. Instead of storing links in a traditional database, it reads from your personal Linkat collection, generates deterministic shortcodes, and redirects visitors accordingly.

The beauty of this approach is that your links live on your personal data server (PDS) in the decentralised network. The shortener simply reads and interprets that data, making it a stateless solution that can be deployed anywhere. No database to manage, no state to worry about, just a clever view layer on top of protocol data.

Architecture Overview

The codebase is organised into three main areas:

Services Layer handles all the heavy lifting – AT Protocol communication, caching, and link management. This is where the interesting technical challenges live.

Routes Layer provides the SvelteKit endpoints for the web interface and redirects. Clean, straightforward, does what it says on the tin.

Utils Layer contains specialised functions like URL encoding for shortcode generation. The clever bits that make everything work.

The Core Components

1. Linkat Integration: Reading from AT Protocol

The heart of the application is the Linkat service, which fetches your link board from the AT Protocol network. Here's how it works:

The fetcher (linkat/fetcher.ts) connects to your Personal Data Server using an AT Protocol agent and retrieves your Linkat board from the blue.linkat.board collection. This collection contains cards with URLs, titles, and emojis that you've saved.

The generator (linkat/generator.ts) takes this raw data and converts each card into a short link with a generated shortcode. The shortcode generation is deterministic, meaning the same URL will always produce the same shortcode, which is perfect for a stateless application.

2. Deterministic Shortcode Generation

One of the most interesting parts of this project is the URL encoding algorithm in utils/encoding.ts. This isn't your typical "random string generator" approach – it's deterministic, meaning the same URL always produces the same shortcode.

This is crucial for a stateless application. There's no database to remember which shortcode maps to which URL. The shortcode IS the URL, just encoded cleverly.

The algorithm is surprisingly sophisticated:

Deterministic – The same URL always produces the same shortcode, making the system stateless and cacheable. Brilliant for not needing a database.

Collision-resistant – Uses hashing that considers domain, subdomains, and the full URL path. The chances of two different URLs producing the same shortcode are astronomical.

Human-readable – The shortcodes use a mixed-case alphanumeric charset (though arguably less "readable" than pure lowercase, it's certainly typeable).

The algorithm breaks down URLs into components: a 2-character domain prefix based on the apex domain (like "example.com"), a URL core hash representing the full normalised URL, subdomain hints encoded as single characters, and padding to reach the desired length (default 10 characters).

The result? Shortcodes that cluster by domain whilst remaining unique for each URL. All links from the same domain might start with the same two characters, making them visually related. It's rather elegant, actually.

3. AT Protocol Agent Management

The AT Protocol integration is handled through a sophisticated agent system:

The identity resolver uses the Slingshot service to look up a user's DID (Decentralized Identifier) and find their Personal Data Server endpoint.

The agent factory creates properly configured AT Protocol agents with optional fetch function injection, which is crucial for server-side rendering in SvelteKit.

The agent manager ties everything together, resolving identities, creating agents, and managing the connection lifecycle.

4. Intelligent Caching

Since fetching data from the AT Protocol network involves network requests, the application implements a smart caching layer. The Cache class provides TTL-based in-memory caching with automatic expiration and pruning. Because hammering the infrastructure like some sort of barbarian would be rude (and I've learnt this lesson the hard way in other projects).

By default, Linkat data is cached for 5 minutes, striking a balance between freshness and performance. The cache key includes the DID, so multiple users could theoretically use the same deployment. Not that you'd want to, but the option's there.

5. The Redirect Endpoint

The magic happens in routes/[shortcode]/+server.ts. When someone visits a shortened URL, this endpoint:

  • Extracts the shortcode from the URL path

  • Looks up the corresponding link in the cached Linkat data

  • Issues a permanent 301 redirect to the target URL

  • Or returns a beautiful 404 page if the shortcode doesn't exist

The 301 redirect is important – it tells browsers and search engines that this redirect is permanent, allowing them to cache it. Good for performance, good for the network, good all around.

Configuration and Constants

The application uses a centralised constants file (lib/constants.ts) that defines:

  • Cache TTL settings (5 minutes by default)

  • Shortcode configuration (length, character set, collision attempts)

  • AT Protocol endpoints (Slingshot for identity resolution, public API)

  • HTTP status codes for consistent response handling

This centralisation makes it easy to tune the application's behavior without hunting through multiple files.

The User Experience

From a user's perspective, the flow is simple:

  • Configure your DID in the environment variables

  • Manage your links in your Linkat board

  • The shortener automatically picks them up and generates consistent shortcodes

  • Share your short URLs, knowing they'll always work as long as your Linkat board exists

The homepage displays all available short links with their titles and emojis, giving visitors a directory of where they can go. Each link shows its shortcode, making it easy to construct short URLs manually if needed.

Why This Approach Works

This architecture demonstrates several principles worth caring about:

Separation of concerns – Services, routes, and utilities are cleanly separated. Makes the codebase maintainable and testable without giving you a headache.

Statelessness – No database means no state to manage. Deployment becomes trivial. Scaling becomes trivial. Everything becomes trivial (well, relatively).

Deterministic behaviour – The same inputs always produce the same outputs. Perfect for caching, perfect for debugging, perfect for sanity.

Progressive enhancement – The system works with just the AT Protocol network; the cache is purely for performance. If caching breaks, things slow down but don't stop working.

Decentralisation – Your data lives on your PDS, not in the shortener's database. True ownership, not just marketing fluff about "your data."

The Technical Stack

The application leverages:

  • SvelteKit for the web framework with server-side rendering

  • @atproto/api for AT Protocol communication

  • tldts for intelligent domain parsing in the encoding algorithm

  • TypeScript for type safety throughout (sanity preservation is important)

Nothing fancy, nothing bleeding-edge. Just solid tools that do the job well.

Potential Enhancements

The current implementation is solid, but there are interesting directions for expansion:

  • Analytics tracking – Store click data, referrers, timestamps back to AT Protocol records

  • Custom shortcode support – Allow vanity URLs for specific links

  • Multi-user support – Subdomain routing (user.example.com) for different Linkat boards

  • Browser extension – Quick link shortening without opening the website

  • QR code generation – Because apparently people still use those

Whether any of these actually get implemented depends entirely on whether I get another burst of motivation at 01:00. We'll see.

Conclusion

This AT Protocol link shortener demonstrates how decentralised systems can replace traditional centralised services whilst maintaining simplicity and elegance. By storing data on the AT Protocol network and generating deterministic shortcodes, it achieves statelessness without sacrificing functionality.

The codebase is clean, modular, and well-documented – an excellent example of building applications on top of AT Protocol. Whether you're interested in decentralised systems, clever algorithms, or just building practical web applications, there's something to learn here.

The future of link shortening might just be decentralised. And this project proves it's entirely possible today, without needing a database, without vendor lock-in, and without sacrificing the user experience.

The AT Protocol is our sandbox, and this is just one small example of what you can build when you take data ownership seriously. Not bad for a stateless shortener.

You can take a look at the code on Tangled!

Subscribe to Ewan’s Blog
to get updates in Reader, RSS, or via Bluesky Feed
An Autistic Silver Bullet
On Scottish Gaelic, Heritage, and the Right to Care

atproto