a standard.site publication renderer for SvelteKit.
TypeScript 58.5%
Svelte 38.3%
JavaScript 1.5%
HTML 0.8%
CSS 0.1%
Other 0.7%
4 1 0

Clone this repository

https://tangled.org/ewancroft.uk/svelte-standard-site https://tangled.org/did:plc:ofrbh253gwicbkc5nktqepol/svelte-standard-site
git@tangled.org:ewancroft.uk/svelte-standard-site git@tangled.org:did:plc:ofrbh253gwicbkc5nktqepol/svelte-standard-site

For self-hosted knots, clone URLs may differ based on your setup.

Download tar.gz
README.md

svelte-standard-site#

A SvelteKit library for fetching and working with site.standard.* records from the AT Protocol.

Features#

  • 🔄 Automatic PDS Resolution - Resolves DIDs to their Personal Data Server endpoints
  • 📦 Type-Safe - Full TypeScript support with complete type definitions
  • 🚀 SSR Ready - Works seamlessly with SvelteKit's server-side rendering
  • 💾 Built-in Caching - Reduces API calls with intelligent caching
  • 🎯 Simple API - Easy to use, set it and forget it configuration
  • 🔗 AT URI Support - Parse and convert AT URIs to HTTPS URLs

Installation#

pnpm add svelte-standard-site
# or
npm install svelte-standard-site
# or
yarn add svelte-standard-site

Quick Start#

1. Configure Environment Variables#

Create a .env file in your project root:

PUBLIC_ATPROTO_DID=did:plc:your-did-here
# Optional: specify a custom PDS endpoint
PUBLIC_ATPROTO_PDS=https://your-pds.example.com
# Optional: cache TTL in milliseconds (default: 300000 = 5 minutes)
PUBLIC_CACHE_TTL=300000

2. Create a Client#

import { createClient } from 'svelte-standard-site';

const client = createClient({
	did: 'did:plc:revjuqmkvrw6fnkxppqtszpv'
});

// Fetch a single publication
const publication = await client.fetchPublication('3lwafzkjqm25s');

// Fetch all publications
const publications = await client.fetchAllPublications();

// Fetch all documents
const documents = await client.fetchAllDocuments();

// Fetch documents for a specific publication
const pubDocs = await client.fetchDocumentsByPublication(
	'at://did:plc:revjuqmkvrw6fnkxppqtszpv/site.standard.publication/3lwafzkjqm25s'
);

3. Use in SvelteKit Load Functions#

// src/routes/+page.server.ts
import { createClient } from 'svelte-standard-site';
import { getConfigFromEnv } from 'svelte-standard-site/config/env';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ fetch }) => {
	const config = getConfigFromEnv();
	if (!config) {
		throw new Error('Missing configuration');
	}

	const client = createClient(config);

	const [publications, documents] = await Promise.all([
		client.fetchAllPublications(fetch),
		client.fetchAllDocuments(fetch)
	]);

	return {
		publications,
		documents
	};
};
<!-- src/routes/+page.svelte -->
<script lang="ts">
	import type { PageData } from './$types';

	const { data }: { data: PageData } = $props();
</script>

<h1>Publications</h1>
{#each data.publications as pub}
	<article>
		<h2>{pub.value.name}</h2>
		<p>{pub.value.description}</p>
		<a href={pub.value.url}>Visit</a>
	</article>
{/each}

<h1>Documents</h1>
{#each data.documents as doc}
	<article>
		<h2>{doc.value.title}</h2>
		<p>{doc.value.description}</p>
		<time>{new Date(doc.value.publishedAt).toLocaleDateString()}</time>
	</article>
{/each}

API Reference#

SiteStandardClient#

The main client for interacting with site.standard records.

Constructor#

new SiteStandardClient(config: SiteStandardConfig)

Methods#

  • fetchPublication(rkey: string, fetchFn?: typeof fetch): Promise<AtProtoRecord<Publication> | null>

    • Fetch a single publication by record key
  • fetchAllPublications(fetchFn?: typeof fetch): Promise<AtProtoRecord<Publication>[]>

    • Fetch all publications for the configured DID
  • fetchDocument(rkey: string, fetchFn?: typeof fetch): Promise<AtProtoRecord<Document> | null>

    • Fetch a single document by record key
  • fetchAllDocuments(fetchFn?: typeof fetch): Promise<AtProtoRecord<Document>[]>

    • Fetch all documents for the configured DID, sorted by publishedAt (newest first)
  • fetchDocumentsByPublication(publicationUri: string, fetchFn?: typeof fetch): Promise<AtProtoRecord<Document>[]>

    • Fetch all documents belonging to a specific publication
  • fetchByAtUri<T>(atUri: string, fetchFn?: typeof fetch): Promise<AtProtoRecord<T> | null>

    • Fetch any record by its AT URI
  • clearCache(): void

    • Clear all cached data
  • getPDS(fetchFn?: typeof fetch): Promise<string>

    • Get the resolved PDS endpoint

Types#

interface Publication {
	$type: 'site.standard.publication';
	url: string;
	name: string;
	icon?: AtProtoBlob;
	description?: string;
	basicTheme?: BasicTheme;
	preferences?: PublicationPreferences;
}

interface Document {
	$type: 'site.standard.document';
	site: string; // AT URI or HTTPS URL
	title: string;
	path?: string;
	description?: string;
	coverImage?: AtProtoBlob;
	content?: any;
	textContent?: string;
	bskyPostRef?: StrongRef;
	tags?: string[];
	publishedAt: string;
	updatedAt?: string;
}

interface AtProtoRecord<T> {
	uri: string;
	cid: string;
	value: T;
}

Utility Functions#

AT URI Utilities#

import { parseAtUri, atUriToHttps, buildAtUri, isAtUri } from 'svelte-standard-site';

// Parse an AT URI
const parsed = parseAtUri('at://did:plc:xxx/site.standard.publication/rkey');
// Returns: { did: 'did:plc:xxx', collection: 'site.standard.publication', rkey: 'rkey' }

// Convert AT URI to HTTPS URL
const url = atUriToHttps(
	'at://did:plc:xxx/site.standard.publication/rkey',
	'https://pds.example.com'
);
// Returns: 'https://pds.example.com/xrpc/com.atproto.repo.getRecord?repo=...'

// Build an AT URI
const uri = buildAtUri('did:plc:xxx', 'site.standard.publication', 'rkey');
// Returns: 'at://did:plc:xxx/site.standard.publication/rkey'

// Validate AT URI
const valid = isAtUri('at://did:plc:xxx/site.standard.publication/rkey');
// Returns: true

PDS Resolution#

import { resolveIdentity, buildPdsBlobUrl } from 'svelte-standard-site';

// Resolve a DID to its PDS
const identity = await resolveIdentity('did:plc:xxx');
// Returns: { did: 'did:plc:xxx', pds: 'https://...', handle?: 'user.bsky.social' }

// Build a blob URL
const blobUrl = buildPdsBlobUrl('https://pds.example.com', 'did:plc:xxx', 'bafyrei...');
// Returns: 'https://pds.example.com/xrpc/com.atproto.sync.getBlob?did=...&cid=...'

Configuration#

From Environment Variables#

import { getConfigFromEnv, validateEnv } from 'svelte-standard-site/config/env';

// Get config (returns null if missing)
const config = getConfigFromEnv();

// Validate config (throws if missing)
validateEnv();

Manual Configuration#

import { createClient } from 'svelte-standard-site';

const client = createClient({
	did: 'did:plc:revjuqmkvrw6fnkxppqtszpv',
	pds: 'https://cortinarius.us-west.host.bsky.network', // optional
	cacheTTL: 300000 // optional, in milliseconds
});

Caching#

The library includes built-in caching to reduce API calls:

  • Default TTL: 5 minutes (300,000ms)
  • Configurable via cacheTTL option or PUBLIC_CACHE_TTL env var
  • Cache can be cleared manually with client.clearCache()

AT URI Structure#

AT URIs follow this format:

at://did:plc:revjuqmkvrw6fnkxppqtszpv/site.standard.publication/3lwafzkjqm25s
     └─────────┬─────────┘ └──────────┬──────────┘ └────┬────┘
              DID              Collection           Record Key

The library automatically converts these to HTTPS URLs for API calls:

https://cortinarius.us-west.host.bsky.network/xrpc/com.atproto.repo.getRecord?repo=did:plc:revjuqmkvrw6fnkxppqtszpv&collection=site.standard.publication&rkey=3lwafzkjqm25s

Example: Building a Blog#

// src/routes/blog/+page.server.ts
import { createClient } from 'svelte-standard-site';
import { PUBLIC_ATPROTO_DID } from '$env/static/public';

export const load = async ({ fetch }) => {
	const client = createClient({ did: PUBLIC_ATPROTO_DID });
	const documents = await client.fetchAllDocuments(fetch);

	return {
		posts: documents.map((doc) => ({
			title: doc.value.title,
			description: doc.value.description,
			publishedAt: doc.value.publishedAt,
			slug: doc.uri.split('/').pop(),
			tags: doc.value.tags || []
		}))
	};
};
// src/routes/blog/[slug]/+page.server.ts
import { createClient } from 'svelte-standard-site';
import { PUBLIC_ATPROTO_DID } from '$env/static/public';
import { error } from '@sveltejs/kit';

export const load = async ({ params, fetch }) => {
	const client = createClient({ did: PUBLIC_ATPROTO_DID });
	const document = await client.fetchDocument(params.slug, fetch);

	if (!document) {
		throw error(404, 'Post not found');
	}

	return {
		post: document.value
	};
};

License#

AGPL-3.0

Contributing#

Contributions are welcome! Please feel free to submit a Pull Request.