API reference
@scribe-atp/react-router-framework
Functions
createSiteLoader
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');
createArticleRouteLoader
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.
createWellKnownLoader
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');
articleMeta
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.
siteMeta
function siteMeta(site: Site): MetaDescriptor[]
Returns MetaDescriptor[] for a site index or group page.
| Parameter | Type | Description |
|---|---|---|
site | Site | The site object |
Testing
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 });
});
});
Types
ArticleWithUri
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.