@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 +151 -0
- package/dist/index.cjs +85 -0
- package/dist/index.d.cts +20 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +58 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# @scribe-atp/next
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@scribe-atp/next)
|
|
4
|
+
[](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
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|