@scribe-atp/next 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,151 @@
1
+ # @scribe-atp/next
2
+
3
+ [![npm](https://img.shields.io/npm/v/@scribe-atp/next)](https://www.npmjs.com/package/@scribe-atp/next)
4
+ [![license](https://img.shields.io/badge/license-MIT-blue)](https://github.com/ACregan/scribe-atp-sdk/blob/main/LICENSE)
5
+
6
+ Next.js App Router adapter for reading [Scribe CMS](https://scribe-cms.app) content from the AT Protocol. Requires Next.js 13 or later.
7
+
8
+ Wraps [`@scribe-atp/core`](https://www.npmjs.com/package/@scribe-atp/core) with Next.js-idiomatic factories for `generateStaticParams` and `generateMetadata`.
9
+
10
+ > **App Router only.** For Pages Router support, use [`@scribe-atp/core`](https://www.npmjs.com/package/@scribe-atp/core) directly — see [Pages Router](#pages-router) below.
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install @scribe-atp/next
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ Create a factory once and export its functions from your route files:
21
+
22
+ ```ts
23
+ // lib/scribe.ts
24
+ import { createScribeSite } from "@scribe-atp/next";
25
+
26
+ export const scribe = createScribeSite("alice.bsky.social", "alice-bsky-social");
27
+ ```
28
+
29
+ ### Group index route — `/blog/[groupSlug]`
30
+
31
+ ```ts
32
+ // app/blog/[groupSlug]/page.tsx
33
+ import { scribe } from "@/lib/scribe";
34
+
35
+ export const generateStaticParams = scribe.generateGroupParams;
36
+ // → [{ groupSlug: "tech" }, { groupSlug: "life" }]
37
+
38
+ export const generateMetadata = ({ params }: { params: { groupSlug: string } }) =>
39
+ scribe.generateGroupMetadata(params.groupSlug);
40
+
41
+ export default function GroupPage({ params }: { params: { groupSlug: string } }) {
42
+ // ...
43
+ }
44
+ ```
45
+
46
+ ### Article route — `/blog/[groupSlug]/[articleSlug]`
47
+
48
+ ```ts
49
+ // app/blog/[groupSlug]/[articleSlug]/page.tsx
50
+ import { scribe } from "@/lib/scribe";
51
+
52
+ export const generateStaticParams = scribe.generateGroupArticleParams;
53
+ // → [{ groupSlug: "tech", articleSlug: "hello" }, ...]
54
+
55
+ export const generateMetadata = ({ params }: { params: { articleSlug: string } }) =>
56
+ scribe.generateArticleMetadata(params.articleSlug);
57
+
58
+ export default function ArticlePage({ params }: { params: { groupSlug: string; articleSlug: string } }) {
59
+ // ...
60
+ }
61
+ ```
62
+
63
+ ### Flat article route — `/blog/[articleSlug]`
64
+
65
+ ```ts
66
+ // app/blog/[articleSlug]/page.tsx
67
+ import { scribe } from "@/lib/scribe";
68
+
69
+ export const generateStaticParams = scribe.generateArticleParams;
70
+ // → [{ articleSlug: "hello" }, { articleSlug: "second" }, ...]
71
+ ```
72
+
73
+ ### Site index metadata
74
+
75
+ ```ts
76
+ // app/blog/page.tsx
77
+ import { scribe } from "@/lib/scribe";
78
+
79
+ export const generateMetadata = scribe.generateSiteMetadata;
80
+ ```
81
+
82
+ ## Metadata
83
+
84
+ 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.
85
+
86
+ | Generator | `title` | `description` | OpenGraph |
87
+ | --------- | ------- | ------------- | --------- |
88
+ | `generateSiteMetadata` | `site.title` | `site.description` | title, description, splash image |
89
+ | `generateGroupMetadata` | `"Group — Site"` | — | title |
90
+ | `generateArticleMetadata` | `"Article — Site"` | `article.synopsis` | title, description, splash image |
91
+
92
+ Article metadata uses the cached `ArticleRef` snapshot already present in the site record — no extra network request per article at build time.
93
+
94
+ **Need custom metadata?** Call `fetchSite` or `fetchArticle` from `@scribe-atp/core` directly and compose your own `Metadata` object:
95
+
96
+ ```ts
97
+ import { fetchSite } from "@scribe-atp/core";
98
+ import type { Metadata } from "next";
99
+
100
+ export async function generateMetadata(): Promise<Metadata> {
101
+ const site = await fetchSite("alice.bsky.social", "alice-bsky-social");
102
+ return {
103
+ title: `${site.title} | My Platform`,
104
+ // custom fields...
105
+ };
106
+ }
107
+ ```
108
+
109
+ ## ISR (Incremental Static Regeneration)
110
+
111
+ The SDK does not configure Next.js fetch caching. Use Next.js [route segment config](https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config) to control revalidation:
112
+
113
+ ```ts
114
+ // app/blog/[groupSlug]/[articleSlug]/page.tsx
115
+ export const revalidate = 3600; // revalidate every hour
116
+ ```
117
+
118
+ ## Pages Router
119
+
120
+ `@scribe-atp/next` targets the App Router only. For Pages Router, use `@scribe-atp/core` directly:
121
+
122
+ ```ts
123
+ // pages/blog/[slug].tsx
124
+ import type { GetStaticPaths, GetStaticProps } from "next";
125
+ import { fetchSite, fetchArticle, toSlug } from "@scribe-atp/core";
126
+
127
+ export const getStaticPaths: GetStaticPaths = async () => {
128
+ const site = await fetchSite("alice.bsky.social", toSlug("alice.bsky.social"));
129
+ const paths = site.groups.flatMap((group) =>
130
+ group.articles.map((article) => ({ params: { slug: article.url ?? "" } }))
131
+ );
132
+ return { paths, fallback: false };
133
+ };
134
+
135
+ export const getStaticProps: GetStaticProps = async ({ params }) => {
136
+ const article = await fetchArticle("alice.bsky.social", params!.slug as string);
137
+ return { props: { article } };
138
+ };
139
+ ```
140
+
141
+ ## TypeScript types
142
+
143
+ All types from `@scribe-atp/core` are re-exported:
144
+
145
+ ```ts
146
+ import type { Site, Article, ArticleRef, SiteGroup } from "@scribe-atp/next";
147
+ ```
148
+
149
+ ## License
150
+
151
+ [MIT](https://github.com/ACregan/scribe-atp-sdk/blob/main/LICENSE)
package/dist/index.cjs ADDED
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ createScribeSite: () => createScribeSite
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/create-scribe-site.ts
28
+ var import_core = require("@scribe-atp/core");
29
+ function createScribeSite(author, siteSlug) {
30
+ return {
31
+ generateGroupParams: async () => {
32
+ const site = await (0, import_core.fetchSite)(author, siteSlug);
33
+ return site.groups.map((g) => ({ groupSlug: g.slug }));
34
+ },
35
+ generateArticleParams: async () => {
36
+ const site = await (0, import_core.fetchSite)(author, siteSlug);
37
+ return site.groups.flatMap((g) => g.articles).filter((a) => !!a.url).map((a) => ({ articleSlug: a.url }));
38
+ },
39
+ generateGroupArticleParams: async () => {
40
+ const site = await (0, import_core.fetchSite)(author, siteSlug);
41
+ return site.groups.flatMap(
42
+ (g) => g.articles.filter((a) => !!a.url).map((a) => ({ groupSlug: g.slug, articleSlug: a.url }))
43
+ );
44
+ },
45
+ generateSiteMetadata: async () => {
46
+ const site = await (0, import_core.fetchSite)(author, siteSlug);
47
+ return {
48
+ title: site.title,
49
+ description: site.description ?? void 0,
50
+ openGraph: {
51
+ title: site.title,
52
+ description: site.description ?? void 0,
53
+ ...site.splashImageUrl ? { images: [site.splashImageUrl] } : {}
54
+ }
55
+ };
56
+ },
57
+ generateGroupMetadata: async (groupSlug) => {
58
+ const site = await (0, import_core.fetchSite)(author, siteSlug);
59
+ const group = site.groups.find((g) => g.slug === groupSlug);
60
+ const title = group ? `${group.title} \u2014 ${site.title}` : site.title;
61
+ return {
62
+ title,
63
+ openGraph: { title }
64
+ };
65
+ },
66
+ generateArticleMetadata: async (articleSlug) => {
67
+ const site = await (0, import_core.fetchSite)(author, siteSlug);
68
+ const article = site.groups.flatMap((g) => g.articles).find((a) => a.url === articleSlug);
69
+ const title = article ? `${article.title} \u2014 ${site.title}` : site.title;
70
+ return {
71
+ title,
72
+ description: article?.synopsis ?? void 0,
73
+ openGraph: {
74
+ title,
75
+ description: article?.synopsis ?? void 0,
76
+ ...article?.splashImageUrl ? { images: [article.splashImageUrl] } : {}
77
+ }
78
+ };
79
+ }
80
+ };
81
+ }
82
+ // Annotate the CommonJS export names for ESM import in node:
83
+ 0 && (module.exports = {
84
+ createScribeSite
85
+ });
@@ -0,0 +1,20 @@
1
+ import { Metadata } from 'next';
2
+ export { Article, ArticleRef, Site, SiteGroup } from '@scribe-atp/core';
3
+
4
+ declare function createScribeSite(author: string, siteSlug: string): {
5
+ generateGroupParams: () => Promise<{
6
+ groupSlug: string;
7
+ }[]>;
8
+ generateArticleParams: () => Promise<{
9
+ articleSlug: string;
10
+ }[]>;
11
+ generateGroupArticleParams: () => Promise<{
12
+ groupSlug: string;
13
+ articleSlug: string;
14
+ }[]>;
15
+ generateSiteMetadata: () => Promise<Metadata>;
16
+ generateGroupMetadata: (groupSlug: string) => Promise<Metadata>;
17
+ generateArticleMetadata: (articleSlug: string) => Promise<Metadata>;
18
+ };
19
+
20
+ export { createScribeSite };
@@ -0,0 +1,20 @@
1
+ import { Metadata } from 'next';
2
+ export { Article, ArticleRef, Site, SiteGroup } from '@scribe-atp/core';
3
+
4
+ declare function createScribeSite(author: string, siteSlug: string): {
5
+ generateGroupParams: () => Promise<{
6
+ groupSlug: string;
7
+ }[]>;
8
+ generateArticleParams: () => Promise<{
9
+ articleSlug: string;
10
+ }[]>;
11
+ generateGroupArticleParams: () => Promise<{
12
+ groupSlug: string;
13
+ articleSlug: string;
14
+ }[]>;
15
+ generateSiteMetadata: () => Promise<Metadata>;
16
+ generateGroupMetadata: (groupSlug: string) => Promise<Metadata>;
17
+ generateArticleMetadata: (articleSlug: string) => Promise<Metadata>;
18
+ };
19
+
20
+ export { createScribeSite };
package/dist/index.js ADDED
@@ -0,0 +1,58 @@
1
+ // src/create-scribe-site.ts
2
+ import { fetchSite } from "@scribe-atp/core";
3
+ function createScribeSite(author, siteSlug) {
4
+ return {
5
+ generateGroupParams: async () => {
6
+ const site = await fetchSite(author, siteSlug);
7
+ return site.groups.map((g) => ({ groupSlug: g.slug }));
8
+ },
9
+ generateArticleParams: async () => {
10
+ const site = await fetchSite(author, siteSlug);
11
+ return site.groups.flatMap((g) => g.articles).filter((a) => !!a.url).map((a) => ({ articleSlug: a.url }));
12
+ },
13
+ generateGroupArticleParams: async () => {
14
+ const site = await fetchSite(author, siteSlug);
15
+ return site.groups.flatMap(
16
+ (g) => g.articles.filter((a) => !!a.url).map((a) => ({ groupSlug: g.slug, articleSlug: a.url }))
17
+ );
18
+ },
19
+ generateSiteMetadata: async () => {
20
+ const site = await fetchSite(author, siteSlug);
21
+ return {
22
+ title: site.title,
23
+ description: site.description ?? void 0,
24
+ openGraph: {
25
+ title: site.title,
26
+ description: site.description ?? void 0,
27
+ ...site.splashImageUrl ? { images: [site.splashImageUrl] } : {}
28
+ }
29
+ };
30
+ },
31
+ generateGroupMetadata: async (groupSlug) => {
32
+ const site = await fetchSite(author, siteSlug);
33
+ const group = site.groups.find((g) => g.slug === groupSlug);
34
+ const title = group ? `${group.title} \u2014 ${site.title}` : site.title;
35
+ return {
36
+ title,
37
+ openGraph: { title }
38
+ };
39
+ },
40
+ generateArticleMetadata: async (articleSlug) => {
41
+ const site = await fetchSite(author, siteSlug);
42
+ const article = site.groups.flatMap((g) => g.articles).find((a) => a.url === articleSlug);
43
+ const title = article ? `${article.title} \u2014 ${site.title}` : site.title;
44
+ return {
45
+ title,
46
+ description: article?.synopsis ?? void 0,
47
+ openGraph: {
48
+ title,
49
+ description: article?.synopsis ?? void 0,
50
+ ...article?.splashImageUrl ? { images: [article.splashImageUrl] } : {}
51
+ }
52
+ };
53
+ }
54
+ };
55
+ }
56
+ export {
57
+ createScribeSite
58
+ };
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@scribe-atp/next",
3
+ "version": "0.1.0",
4
+ "description": "Next.js App Router adapter for reading Scribe CMS content from the AT Protocol.",
5
+ "license": "MIT",
6
+ "engines": {
7
+ "node": ">=22"
8
+ },
9
+ "type": "module",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
15
+ }
16
+ },
17
+ "main": "./dist/index.cjs",
18
+ "module": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "scripts": {
27
+ "build": "tsup",
28
+ "typecheck": "tsc --noEmit"
29
+ },
30
+ "dependencies": {
31
+ "@scribe-atp/core": "*"
32
+ },
33
+ "peerDependencies": {
34
+ "next": ">=13.0.0"
35
+ },
36
+ "devDependencies": {
37
+ "next": "^15.0.0"
38
+ },
39
+ "keywords": [
40
+ "scribe",
41
+ "atproto",
42
+ "at-protocol",
43
+ "bluesky",
44
+ "next",
45
+ "nextjs",
46
+ "cms",
47
+ "blog"
48
+ ],
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "https://github.com/ACregan/scribe-atp-sdk.git"
52
+ }
53
+ }