Skip to content

@scribe-atp/react-router-framework

function createSiteLoader(
author: string,
publicationUrl: string,
deps?: { fetchSite?: typeof fetchSite }
): (args: LoaderFunctionArgs) => Promise<Site>

Returns a React Router loader function that fetches a site. The loader passes request.signal automatically so the fetch is cancelled if the user navigates away.

Parameter Type Description
author string Author handle or DID
publicationUrl string The site’s canonical HTTPS URL, e.g. "https://alice.bsky.social"
deps.fetchSite function Optional. Override the fetch function — useful for testing
app/routes/blog.tsx
export const loader = createSiteLoader('alice.bsky.social', 'https://alice.bsky.social');

function createArticleRouteLoader(
author: string,
publicationUrl: string,
slugParam?: string,
deps?: { fetchArticleBySlug?: typeof fetchArticleBySlug }
): (args: LoaderFunctionArgs) => Promise<ArticleWithUri>

Returns a React Router loader function that reads an article slug from URL params, fetches the full article and its AT URI, and returns ArticleWithUri. The loader passes request.signal automatically.

Parameter Type Description
author string Author handle or DID
publicationUrl string The site’s canonical HTTPS URL, e.g. "https://alice.bsky.social"
slugParam string Optional. URL param name to read the slug from. Defaults to "articleSlug"
deps.fetchArticleBySlug function Optional. Override the fetch function — useful for testing
// app/routes/blog.$articleSlug.tsx
export const loader = createArticleRouteLoader('alice.bsky.social', 'https://alice.bsky.social');
// custom param name:
export const loader = createArticleRouteLoader('alice.bsky.social', 'https://alice.bsky.social', 'slug');

The returned loader throws a 404 Response if the slug param is missing, and an error if the article is not found.


function createWellKnownLoader(
author: string,
publicationUrl: string,
deps?: { resolvePublicationUri?: typeof resolvePublicationUri }
): (args: LoaderFunctionArgs) => Promise<Response>

Returns a React Router loader function that responds with the author’s publication AT URI as plain text. Used to serve the /.well-known/ endpoint required for AT Protocol client discovery.

Parameter Type Description
author string Author handle or DID
publicationUrl string The site’s canonical HTTPS URL
deps.resolvePublicationUri function Optional. Override the resolve function — useful for testing
app/routes/well-known.ts
export const loader = createWellKnownLoader('alice.bsky.social', 'https://alice.bsky.social');

function articleMeta(article: Article, site: Site): MetaDescriptor[]

Returns a MetaDescriptor[] array ready to spread into a React Router v7/v8 meta function. Produces Open Graph and Twitter Card tags for rich link previews.

Parameter Type Description
article Article The full article object
site Site The site object
import { articleMeta } from '@scribe-atp/react-router-framework';
import type { Route } from './+types/Article';
export function meta({ loaderData }: Route.MetaArgs) {
if (!loaderData) return [{ title: 'My Blog' }];
return [
...articleMeta(loaderData.article, loaderData.site),
{ title: `${loaderData.article.title} — My Blog` },
];
}

The spread comes first so that your explicit { title } at the end takes precedence in the browser tab, while all OG/Twitter tags are populated automatically. See the Open Graph meta tags guide for the full pattern.


function siteMeta(site: Site): MetaDescriptor[]

Returns MetaDescriptor[] for a site index or group page.

Parameter Type Description
site Site The site object

All three factory functions accept an optional deps parameter for dependency injection. Pass mock functions directly — no vi.mock needed.

import { describe, it, expect, vi } from 'vitest';
import { createSiteLoader, createArticleRouteLoader, createWellKnownLoader } from '@scribe-atp/react-router-framework';
describe('blog loaders', () => {
it('fetches the site', async () => {
const fetchSite = vi.fn().mockResolvedValueOnce(mockSite);
const loader = createSiteLoader('alice.bsky.social', 'https://alice.bsky.social', { fetchSite });
const result = await loader({ request: new Request('https://example.com'), params: {}, context: {} });
expect(result).toEqual(mockSite);
expect(fetchSite).toHaveBeenCalled();
});
it('fetches an article by slug', async () => {
const fetchArticleBySlug = vi.fn().mockResolvedValueOnce({ article: mockArticle, uri: mockUri });
const loader = createArticleRouteLoader('alice.bsky.social', 'https://alice.bsky.social', 'articleSlug', { fetchArticleBySlug });
const result = await loader({ request: new Request('https://example.com'), params: { articleSlug: 'hello' }, context: {} });
expect(result).toEqual({ ...mockArticle, documentUri: mockUri });
});
});

The return type of createArticleRouteLoader. Extends Article with the AT URI of the article record.

interface ArticleWithUri extends Article {
documentUri: string; // AT URI, e.g. "at://did:plc:…/site.standard.document/3abc123"
}

Pass documentUri to @scribe-atp/social’s LikeButton component.

All types from @scribe-atp/core are also re-exported:

import type { Site, Article, ArticleRef, SiteGroup, ArticleWithUri } from '@scribe-atp/react-router-framework';

See the core reference for full type definitions.