What is Webring?
The first iteration of webring
I originally created webring as a fun project to try to connect people sort of how it was done in the internet of old. The idea is that you can register your site and get links to the previous and next site in the ring, and I wanted to see how one could do it with modern frameworks. Here was my initial try:
- An API built with Cloud Run that serves the previous and next site based on the email address you registered with.
- The app itself was a FastAPI concept app that stored the sites in a ring data structure in memory. To persist the data, I dumped the ring to a file periodically and loaded it on startup.
- The ring is a doubly linked list stored as a hashmap - operations are all O(1) and the validate (which is a walk to validate no cycles exist) is O(n)
Limitations:
- Not very scalable, the data structure is neither a singleton, distributed, nor thread safe. For this poc, it mostly works since I started uvicorn with a single worker
- This can also lead to torn reads as part of the dump to file. In a more active read scenario, we would at least need some locking mechanism to ensure we don’t read while writing.
- I also had to think about handling api logistics like rate limiting, authentication, etc. Ideally would want to have a more robust solution like oauth or keys when adding to the webring.
- Furthermore, each featured person in the ring would have needed to have something like this React component added to their website:
import React, { useEffect, useState } from "react";
import { SITE } from "@config";
interface WebringLink {
url: string;
email: string;
}
const apiUrl = "https://webring-725549621836.us-central1.run.app/";
const email = SITE.email; // Your email address that you registered with
const Webring: React.FC = () => {
const [prevSite, setPrevSite] = useState<WebringLink | null>(null);
const [nextSite, setNextSite] = useState<WebringLink | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!apiUrl || !email) {
setError("Webring not configured properly.");
return;
}
const fetchLinks = async () => {
try {
const [prevRes, nextRes] = await Promise.all([
fetch(`${apiUrl}/prev?cur=${encodeURIComponent(email)}`),
fetch(`${apiUrl}/next?cur=${encodeURIComponent(email)}`),
]);
if (prevRes.ok && nextRes.ok) {
const prevJson = (await prevRes.json()) as WebringLink;
const nextJson = (await nextRes.json()) as WebringLink;
setPrevSite(prevJson);
setNextSite(nextJson);
} else {
setError(`API responded with ${prevRes.status}`);
}
} catch (e) {
const message =
e instanceof Error ? e.message : "Failed to fetch webring";
console.error("Webring fetch error:", message);
setError(message);
}
};
fetchLinks();
}, []);
if (error) {
return (
<div className="flex justify-center py-6">
<p className="text-center text-xs text-gray-400">Webring: {error}</p>
</div>
);
}
if (!prevSite || !nextSite) return null;
return (
<div className="flex justify-center py-6">
<div className="flex items-center gap-4">
<a
href={prevSite.url}
className="text-sm font-medium"
title={`Previous Site belongs to ${prevSite.email}`}
>
<span className="truncate">← Previous Site</span>
</a>
<a
href="https://doshir.dev/posts/whatiswebring"
className="text-xs"
>
Why Webring?
</a>
<a
href={nextSite.url}
className="text-sm font-medium"
title={`Next Site belongs to ${nextSite.email}`}
>
<span className="truncate">Next Site →</span>
</a>
</div>
</div>
);
};
export default Webring;
- While not fully unreasonable, individuals would have to adjust based on their framework, style system, etc.
So this begs the question - what did I settle on when I started to understand some of the limitations here? I eventually just did a “Featured Sites” modal that brings up data fetched from https://github.com/rithvik-doshi/webring-json. The data is already hosted for me on GitHub and can be edited simply via source control. I also don’t have to depend on other sites to show the full extent of the connected websites, though they are welcome to do so. Ultimately, trying to resurrect the webring was a fun thought experiment, but perhaps there is a reason it is slowly dying off in favor of other methods of connection and discovery on the web.