@scribe-atp/next
v1.2.0
Published
Next.js App Router adapter for reading Scribe CMS content from the AT Protocol.
Downloads
1,446
Maintainers
Readme
@scribe-atp/next
Next.js App Router adapter for reading Scribe CMS content from the AT Protocol. Requires Next.js 13 or later.
Wraps @scribe-atp/core with Next.js-idiomatic factories for generateStaticParams and generateMetadata.
App Router only. For Pages Router support, use
@scribe-atp/coredirectly — see Pages Router below.
Installation
npm install @scribe-atp/nextUsage
Create a factory once and export its functions from your route files:
// lib/scribe.ts
import { createScribeSite } from "@scribe-atp/next";
export const scribe = createScribeSite("alice.bsky.social", "https://alice.bsky.social");Group index route — /blog/[groupSlug]
// app/blog/[groupSlug]/page.tsx
import { scribe } from "@/lib/scribe";
export const generateStaticParams = scribe.generateGroupParams;
// → [{ groupSlug: "tech" }, { groupSlug: "life" }]
export const generateMetadata = ({ params }: { params: { groupSlug: string } }) =>
scribe.generateGroupMetadata(params.groupSlug);
export default function GroupPage({ params }: { params: { groupSlug: string } }) {
// ...
}Article route — /blog/[groupSlug]/[articleSlug]
// app/blog/[groupSlug]/[articleSlug]/page.tsx
import { scribe } from "@/lib/scribe";
export const generateStaticParams = scribe.generateGroupArticleParams;
// → [{ groupSlug: "tech", articleSlug: "hello" }, ...]
export const generateMetadata = ({ params }: { params: { articleSlug: string } }) =>
scribe.generateArticleMetadata(params.articleSlug);
export default function ArticlePage({ params }: { params: { groupSlug: string; articleSlug: string } }) {
// ...
}Flat article route — /blog/[articleSlug]
// app/blog/[articleSlug]/page.tsx
import { scribe } from "@/lib/scribe";
export const generateStaticParams = scribe.generateArticleParams;
// → [{ articleSlug: "hello" }, { articleSlug: "second" }, ...]Site index metadata
// app/blog/page.tsx
import { scribe } from "@/lib/scribe";
export const generateMetadata = scribe.generateSiteMetadata;Metadata
The metadata generators are opinionated by design. They produce complete, ready-to-use Metadata objects including OpenGraph tags — you don't need to wire these up manually.
| Generator | title | description | OpenGraph |
| --------- | ------- | ------------- | --------- |
| generateSiteMetadata | site.title | site.description | title, description, splash image |
| generateGroupMetadata | "Group — Site" | — | title |
| generateArticleMetadata | "Article — Site" | article.synopsis | title, description, splash image |
Article metadata uses the cached ArticleRef snapshot already present in the site record — no extra network request per article at build time.
Standalone metadata helpers
Outside the factory pattern, articleMetadata and siteMetadata are exported as standalone functions that accept an Article/Site object directly. These are useful when you're already fetching content yourself and just need the Metadata object:
import { articleMetadata, siteMetadata } from "@scribe-atp/next";
import { fetchArticleBySlug, fetchSite } from "@scribe-atp/core";
export async function generateMetadata({ params }: { params: { slug: string } }) {
const [{ article }, site] = await Promise.all([
fetchArticleBySlug("alice.bsky.social", "https://alice.bsky.social", params.slug),
fetchSite("alice.bsky.social", "https://alice.bsky.social"),
]);
return articleMetadata(article, site);
}Need custom metadata? Call fetchSite or fetchArticle from @scribe-atp/core directly and compose your own Metadata object:
import { fetchSite } from "@scribe-atp/core";
import type { Metadata } from "next";
export async function generateMetadata(): Promise<Metadata> {
const site = await fetchSite("alice.bsky.social", "https://alice.bsky.social");
return {
title: `${site.title} | My Platform`,
// custom fields...
};
}ISR (Incremental Static Regeneration)
The SDK does not configure Next.js fetch caching. Use Next.js route segment config to control revalidation:
// app/blog/[groupSlug]/[articleSlug]/page.tsx
export const revalidate = 3600; // revalidate every hourPages Router
@scribe-atp/next targets the App Router only. For Pages Router, use @scribe-atp/core directly:
// pages/blog/[slug].tsx
import type { GetStaticPaths, GetStaticProps } from "next";
import { fetchSite, fetchArticle } from "@scribe-atp/core";
export const getStaticPaths: GetStaticPaths = async () => {
const site = await fetchSite("alice.bsky.social", "https://alice.bsky.social");
const paths = site.groups.flatMap((group) =>
group.articles.map((article) => ({ params: { slug: article.url ?? "" } }))
);
return { paths, fallback: false };
};
export const getStaticProps: GetStaticProps = async ({ params }) => {
const article = await fetchArticle("alice.bsky.social", params!.slug as string);
return { props: { article } };
};TypeScript types
All types from @scribe-atp/core are re-exported:
import type { Site, Article, ArticleRef, SiteGroup } from "@scribe-atp/next";