Framework guides

Next.js

@scribe-atp/next provides a createScribeSite factory for the Next.js 13+ App Router. It returns ready-made implementations of generateStaticParams and generateMetadata so you don't have to write them yourself.

Install

npm install @scribe-atp/next

Setup

Call createScribeSite once per site, outside your components:

// lib/scribe.ts
import { createScribeSite } from '@scribe-atp/next';

export const scribe = createScribeSite('alice.bsky.social', 'https://alice.bsky.social');

Site index page

// app/blog/page.tsx
import { scribe } from '@/lib/scribe';

export const generateMetadata = scribe.generateSiteMetadata;

export default async function BlogPage() {
  const site = await scribe.fetchSite();

  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>
  );
}

Group page

// app/blog/[group]/page.tsx
import { scribe } from '@/lib/scribe';

export const generateStaticParams = scribe.generateGroupParams;
export const generateMetadata = scribe.generateGroupMetadata;

export default async function GroupPage({ params }: { params: { group: string } }) {
  const site = await scribe.fetchSite();
  const group = site.groups.find((g) => g.slug === params.group);

  if (!group) return <p>Not found</p>;

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

Article page

// app/blog/[group]/[slug]/page.tsx
import { scribe } from '@/lib/scribe';

export const generateStaticParams = scribe.generateGroupArticleParams;
export const generateMetadata = scribe.generateArticleMetadata;

export default async function ArticlePage({ params }: { params: { slug: string } }) {
  const article = await scribe.fetchArticle(params.slug);

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

Tip

generateMetadata functions produce complete OpenGraph tags by default — og:title, og:description, and og:image (when a splash image is set). You can override individual fields by spreading the result:

export async function generateMetadata({ params }) {
  const base = await scribe.generateArticleMetadata({ params });
  return { ...base, title: `${base.title} | My Blog` };
}

ISR (Incremental Static Regeneration)

Control revalidation via Next.js route segment config — the SDK does not manage this:

// Revalidate every hour
export const revalidate = 3600;

TypeScript types

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

import type { Site, Article, ArticleRef, SiteGroup } from '@scribe-atp/next';

Settings

Appearance