Framework guides

React Router v7/v8

@scribe-atp/react-router-framework provides loader factories for React Router v7/v8 framework mode. The factories wire up request.signal automatically so fetches are cancelled if the user navigates away before the response arrives.

Note

This package is for framework (server) mode only. For React Router in SPA / library mode, use @scribe-atp/react instead.

Install

npm install @scribe-atp/react-router-framework

Site index route

Use createSiteLoader to create a loader for a route that displays the full site — groups and article listings.

// app/routes/blog.tsx
import { createSiteLoader } from '@scribe-atp/react-router-framework';
import { useLoaderData } from 'react-router';

export const loader = createSiteLoader('alice.bsky.social', 'https://alice.bsky.social');

export default function Blog() {
  const site = useLoaderData<typeof loader>();

  return (
    <main>
      <h1>{site.title}</h1>
      {site.groups.map((group) => (
        <section key={group.slug}>
          <h2>{group.title}</h2>
          <ul>
            {group.articles.map((article) => (
              <li key={article.uri}>
                <a href={`/blog/${group.slug}/${article.slug}`}>{article.title}</a>
              </li>
            ))}
          </ul>
        </section>
      ))}
    </main>
  );
}

Dynamic article route

Use createArticleRouteLoader for routes where the slug comes from URL params. It fetches the article and its AT URI and returns ArticleWithUri{ ...article, documentUri }.

// app/routes/blog.$group.$articleSlug.tsx
import { createArticleRouteLoader } from '@scribe-atp/react-router-framework';
import { useLoaderData } from 'react-router';

export const loader = createArticleRouteLoader('alice.bsky.social', 'https://alice.bsky.social');

export default function Article() {
  const { title, content, documentUri } = useLoaderData<typeof loader>();

  return (
    <article>
      <h1>{title}</h1>
      <div dangerouslySetInnerHTML={{ __html: content }} />
    </article>
  );
}

Pass documentUri to @scribe-atp/social's LikeButton if you want social interactions on the article page.

Open Graph meta tags

Use articleMeta to populate Open Graph and Twitter Card tags for rich link previews. Your loader must also fetch the site object:

import { createArticleRouteLoader, articleMeta } from '@scribe-atp/react-router-framework';
import { fetchSite } from '@scribe-atp/core';
import type { Route } from './+types/Article';

export async function loader({ request, params }: Route.LoaderArgs) {
  const [articleData, site] = await Promise.all([
    createArticleRouteLoader('alice.bsky.social', 'https://alice.bsky.social')({ request, params }),
    fetchSite('alice.bsky.social', 'https://alice.bsky.social', request.signal),
  ]);
  return { ...articleData, publicationUri: site.uri, site };
}

export function meta({ loaderData }: Route.MetaArgs) {
  if (!loaderData) return [{ title: 'My Blog' }];
  return [
    ...articleMeta(loaderData, loaderData.site),
    { title: `${loaderData.title} | My Blog` },
  ];
}

See the Open Graph meta tags guide for more detail.

TypeScript types

All types from @scribe-atp/core are re-exported from @scribe-atp/react-router-framework, plus ArticleWithUri:

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

Settings

Appearance