Loading...
The Fediverse is a decentralized social network that allows users to interact across different platforms. Fediverse servers use protocols like ActivityPub to communicate seamlessly with each other.
Over the past few years, I’ve used many Fediverse servers, but I always wanted to set up my own minimal instance. Last year, I experimented with snac2, a minimal ActivityPub server, but it felt too complex for my needs. I wanted something simpler, lightweight, and easy to maintain.
For this project, I wanted a minimal server that could:
This project is ideal for anyone who wants to set up a minimal Fediverse server without managing a full-fledged instance. It’s especially useful for developers and hobbyists who want to experiment with Fediverse protocols and features.
Currently, this server is not intended for production use or for hosting multiple users. I mainly use it to integrate my blog posts with my Fediverse account so that whenever I publish a new post, it automatically shares on my Fediverse profile.
route.ts for handling requests due to its simplicity and ease of use.// src/app/.well-known/webfinger/route.ts
import { NextRequest, NextResponse } from 'next/server';
// Import a list of valid ActivityPub domains that you want to support (i.e., ['example.com', 'example.org', ...])
import { validActivitypubDomains } from '@/api-lib/fediverse';
export const runtime = 'edge';
export async function OPTIONS(req: NextRequest) {
const origin = req.nextUrl.origin || '*';
return new NextResponse(null, {
status: 204,
headers: {
'Access-Control-Allow-Origin': origin,
'Access-Control-Allow-Methods': 'GET, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type'
}
});
}
export async function GET(req: NextRequest) {
const resource = req.nextUrl.searchParams.get('resource');
if (!resource) {
return NextResponse.json(
{ error: 'Missing resource parameter' },
{
status: 400,
headers: { 'Content-Type': 'application/jrd+json; charset=utf-8' }
}
);
}
// Change 'sivothajan' to your Fediverse username
const match = /^acct:sivothajan@(.+)$/.exec(resource);
const domain = match?.[1]?.toLowerCase();
if (!domain || !validActivitypubDomains.includes(domain)) {
return NextResponse.json(
{ error: 'Resource not found' },
{
status: 404,
headers: { 'Content-Type': 'application/jrd+json; charset=utf-8' }
}
);
}
const jrd = {
subject: `acct:sivothajan@${domain}`,
aliases: [`https://${domain}`, `https://${domain}/fediverse`],
links: [
{
rel: 'http://webfinger.net/rel/profile-page',
type: 'text/html',
href: `https://${domain}`
},
{
rel: 'self',
type: 'application/activity+json',
href: `https://${domain}/fediverse`
},
{
rel: 'http://webfinger.net/rel/avatar',
type: 'image/png',
href: `https://${domain}/images/pfp.png`
}
]
};
return NextResponse.json(jrd, {
headers: { 'Content-Type': 'application/jrd+json; charset=utf-8' }
});
}
if you integrate the above code snippet into a Next.js project, it will create a WebFinger endpoint that responds to requests for the specified Fediverse username.
⚠️ Note 1: This is a simplified example. A complete Fediverse server would require handling many more aspects of the ActivityPub protocol.
⚠️ Note 2: TypeScript may not recognize files under the
.well-knownfolder by default. To fix this, add the following to yourtsconfig.json:{ "compilerOptions": { "include": ["**/.well-known/**/*.ts", "src"] } }
Finally, I deployed the project on Vercel as a Next.js project.
You can check out my testing account here: @sivothajan@Sivothajan.me.
The project is still a work in progress. I’m refining features, optimizing performance, and making it more stable before sharing it with the community. Once I’m satisfied with its functionality and scalability, I’ll consider open-sourcing it.
For now, the project’s name is Fediman.