@autoblogwriter/sdk 2.0.5 → 2.0.6
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 +285 -169
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,6 +12,33 @@ npm install @autoblogwriter/sdk
|
|
|
12
12
|
|
|
13
13
|
> Requires Node.js 18+ and native `fetch`. Bundles ship as ESM, CJS, and types via `tsup`.
|
|
14
14
|
|
|
15
|
+
## Entry Points
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
// Core client, types, and utilities
|
|
19
|
+
import { createBlogAutoClient } from "@autoblogwriter/sdk";
|
|
20
|
+
|
|
21
|
+
// Next.js App Router helpers (server-side)
|
|
22
|
+
import { fetchBlogPosts, fetchBlogPost } from "@autoblogwriter/sdk/next";
|
|
23
|
+
|
|
24
|
+
// React components
|
|
25
|
+
import { BlogPost, BlogPostList, Markdown } from "@autoblogwriter/sdk/react";
|
|
26
|
+
|
|
27
|
+
// Webhook signature verification (server-side)
|
|
28
|
+
import { verifyWebhookSignature } from "@autoblogwriter/sdk/revalidate";
|
|
29
|
+
|
|
30
|
+
// Default styles
|
|
31
|
+
import "@autoblogwriter/sdk/styles.css";
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
| Entry Point | Environment | Description |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| `@autoblogwriter/sdk` | Server | Core client, markdown renderer, sitemap/robots builders |
|
|
37
|
+
| `@autoblogwriter/sdk/next` | Server (Next.js) | `fetchBlogPosts`, `fetchBlogPost`, `generatePostMetadata`, `generateBlogSitemap`, `generateBlogRobots`, `createEnvRevalidateHandler` |
|
|
38
|
+
| `@autoblogwriter/sdk/react` | Client or Server | `<BlogPost>`, `<BlogPostList>`, `<Markdown>` components |
|
|
39
|
+
| `@autoblogwriter/sdk/revalidate` | Server | `createRevalidateRouteHandler`, `verifyWebhookSignature` |
|
|
40
|
+
| `@autoblogwriter/sdk/styles.css` | Client | Default dark-theme styles for all components |
|
|
41
|
+
|
|
15
42
|
## Security Notes (Read First)
|
|
16
43
|
- Never ship AutoBlogWriter API keys in public, client-side code.
|
|
17
44
|
- Run the SDK inside Next.js server components, route handlers, or your own backend proxy.
|
|
@@ -45,198 +72,319 @@ const client = createBlogAutoClient({
|
|
|
45
72
|
| `fetch` | `typeof fetch` | no | Provide a custom fetch implementation (e.g., for SSR polyfills). |
|
|
46
73
|
| `timeoutMs` | `number` | no | Optional request timeout enforced client-side. |
|
|
47
74
|
|
|
48
|
-
## Next.js
|
|
75
|
+
## Next.js Helper Functions (`@autoblogwriter/sdk/next`)
|
|
76
|
+
|
|
77
|
+
The `next` entry point provides high-level helpers that read configuration from environment variables automatically (via `createBlogAutoFromEnv`). Set these env vars and the helpers handle client creation, cache tagging, and metadata for you:
|
|
78
|
+
|
|
79
|
+
| Env Variable | Required | Description |
|
|
80
|
+
| --- | --- | --- |
|
|
81
|
+
| `BLOGAUTO_API_KEY` | yes | Workspace API key |
|
|
82
|
+
| `BLOGAUTO_WORKSPACE_SLUG` | yes | Workspace slug for content APIs |
|
|
83
|
+
| `BLOGAUTO_API_URL` | no | Defaults to `https://api.autoblogwriter.app` |
|
|
84
|
+
| `BLOGAUTO_WORKSPACE_ID` | no | Optional workspace ID |
|
|
85
|
+
| `SITE_URL` or `NEXT_PUBLIC_SITE_URL` | no | Defaults to `http://localhost:3000` |
|
|
86
|
+
| `BLOGAUTO_REVALIDATE_SECRET` | no | Required for the revalidation handler |
|
|
49
87
|
|
|
50
|
-
|
|
88
|
+
### `fetchBlogPosts(options?)`
|
|
51
89
|
|
|
52
|
-
|
|
90
|
+
Fetches a paginated list of posts with automatic cache tagging.
|
|
53
91
|
|
|
54
92
|
```ts
|
|
55
|
-
|
|
56
|
-
import { createBlogAutoClient, renderMarkdownToHtml } from "@autoblogwriter/sdk";
|
|
93
|
+
import { fetchBlogPosts } from "@autoblogwriter/sdk/next";
|
|
57
94
|
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
apiUrl: process.env.BLOGAUTO_API_URL!,
|
|
61
|
-
apiKey: process.env.BLOGAUTO_API_KEY!,
|
|
62
|
-
workspaceSlug,
|
|
63
|
-
});
|
|
95
|
+
const { posts, nextCursor } = await fetchBlogPosts({ limit: 20 });
|
|
96
|
+
```
|
|
64
97
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
98
|
+
### `fetchBlogPost(slug)`
|
|
99
|
+
|
|
100
|
+
Fetches a single post by slug. Automatically calls `notFound()` if the post doesn't exist.
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import { fetchBlogPost } from "@autoblogwriter/sdk/next";
|
|
104
|
+
|
|
105
|
+
const post = await fetchBlogPost(params.slug);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### `generatePostMetadata(slug)`
|
|
109
|
+
|
|
110
|
+
Returns a Next.js `Metadata` object (title, description, Open Graph, Twitter cards) for a blog post. Use it in your `generateMetadata` export.
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import { generatePostMetadata } from "@autoblogwriter/sdk/next";
|
|
114
|
+
|
|
115
|
+
export async function generateMetadata({ params }: Props) {
|
|
116
|
+
return generatePostMetadata(params.slug);
|
|
81
117
|
}
|
|
82
118
|
```
|
|
83
119
|
|
|
84
|
-
### `
|
|
120
|
+
### `generateBlogSitemap()`
|
|
121
|
+
|
|
122
|
+
Returns a `MetadataRoute.Sitemap` array for `app/sitemap.ts`.
|
|
85
123
|
|
|
86
124
|
```ts
|
|
87
|
-
|
|
88
|
-
import { notFound } from "next/navigation";
|
|
89
|
-
import { createBlogAutoClient, renderMarkdownToHtml } from "@autoblogwriter/sdk";
|
|
125
|
+
import { generateBlogSitemap } from "@autoblogwriter/sdk/next";
|
|
90
126
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
127
|
+
export default async function sitemap() {
|
|
128
|
+
return generateBlogSitemap();
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### `generateBlogRobots()`
|
|
133
|
+
|
|
134
|
+
Returns a `MetadataRoute.Robots` object for `app/robots.ts`.
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
import { generateBlogRobots } from "@autoblogwriter/sdk/next";
|
|
138
|
+
|
|
139
|
+
export default function robots() {
|
|
140
|
+
return generateBlogRobots();
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### `createEnvRevalidateHandler()`
|
|
145
|
+
|
|
146
|
+
Creates a POST route handler for webhook-based revalidation. Reads `BLOGAUTO_REVALIDATE_SECRET` from env, verifies the HMAC signature, and calls `revalidatePath`/`revalidateTag`.
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
// app/api/blogauto/revalidate/route.ts
|
|
150
|
+
import { createEnvRevalidateHandler } from "@autoblogwriter/sdk/next";
|
|
151
|
+
|
|
152
|
+
export const POST = createEnvRevalidateHandler();
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Full Next.js Example
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
// app/blog/page.tsx
|
|
159
|
+
import { fetchBlogPosts } from "@autoblogwriter/sdk/next";
|
|
160
|
+
import { BlogPostList } from "@autoblogwriter/sdk/react";
|
|
161
|
+
|
|
162
|
+
export default async function BlogIndexPage() {
|
|
163
|
+
const { posts } = await fetchBlogPosts({ limit: 20 });
|
|
164
|
+
return <BlogPostList posts={posts} />;
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
// app/blog/[slug]/page.tsx
|
|
170
|
+
import { fetchBlogPost, generatePostMetadata } from "@autoblogwriter/sdk/next";
|
|
171
|
+
import { BlogPost } from "@autoblogwriter/sdk/react";
|
|
97
172
|
|
|
98
173
|
interface Props {
|
|
99
174
|
params: { slug: string };
|
|
100
175
|
}
|
|
101
176
|
|
|
177
|
+
export async function generateMetadata({ params }: Props) {
|
|
178
|
+
return generatePostMetadata(params.slug);
|
|
179
|
+
}
|
|
180
|
+
|
|
102
181
|
export default async function BlogPostPage({ params }: Props) {
|
|
103
|
-
const post = await
|
|
104
|
-
|
|
105
|
-
});
|
|
106
|
-
if (!post) {
|
|
107
|
-
notFound();
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return (
|
|
111
|
-
<article>
|
|
112
|
-
<h1>{post.title}</h1>
|
|
113
|
-
<time dateTime={post.publishedAt ?? post.updatedAt}>{post.publishedAt}</time>
|
|
114
|
-
<div dangerouslySetInnerHTML={{ __html: renderMarkdownToHtml(post.content) }} />
|
|
115
|
-
</article>
|
|
116
|
-
);
|
|
182
|
+
const post = await fetchBlogPost(params.slug);
|
|
183
|
+
return <BlogPost post={post} />;
|
|
117
184
|
}
|
|
118
185
|
```
|
|
119
186
|
|
|
120
|
-
|
|
187
|
+
## React Components (`@autoblogwriter/sdk/react`)
|
|
121
188
|
|
|
122
|
-
|
|
123
|
-
// app/sitemap.ts
|
|
124
|
-
import { buildSitemap, createBlogAutoClient } from "@autoblogwriter/sdk";
|
|
189
|
+
Pre-built components for rendering blog content. Works in Next.js, Vite, or any React 18+ app.
|
|
125
190
|
|
|
126
|
-
|
|
127
|
-
apiUrl: process.env.BLOGAUTO_API_URL!,
|
|
128
|
-
apiKey: process.env.BLOGAUTO_API_KEY!,
|
|
129
|
-
workspaceSlug: process.env.BLOGAUTO_WORKSPACE!,
|
|
130
|
-
});
|
|
191
|
+
### `<Markdown>`
|
|
131
192
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
193
|
+
Renders a markdown string to HTML using the built-in renderer.
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
import { Markdown } from "@autoblogwriter/sdk/react";
|
|
197
|
+
|
|
198
|
+
<Markdown source={post.content} />
|
|
199
|
+
<Markdown source={post.content} className="custom-markdown" />
|
|
140
200
|
```
|
|
141
201
|
|
|
142
|
-
|
|
202
|
+
| Prop | Type | Default | Description |
|
|
203
|
+
| --- | --- | --- | --- |
|
|
204
|
+
| `source` | `string \| null` | — | Markdown string to render. Returns `null` if empty. |
|
|
205
|
+
| `className` | `string` | `"ba-markdown"` | CSS class on the wrapper `<div>`. |
|
|
206
|
+
|
|
207
|
+
### `<BlogPost>`
|
|
208
|
+
|
|
209
|
+
Renders a full blog post with title, date, reading time, and markdown content.
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
import { BlogPost } from "@autoblogwriter/sdk/react";
|
|
213
|
+
|
|
214
|
+
<BlogPost post={post} />
|
|
215
|
+
<BlogPost post={post} showDate={false} />
|
|
216
|
+
<BlogPost post={post} renderContent={(md) => <MyCustomRenderer source={md} />} />
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
| Prop | Type | Default | Description |
|
|
220
|
+
| --- | --- | --- | --- |
|
|
221
|
+
| `post` | `BlogPost` | — | The post object from the SDK. |
|
|
222
|
+
| `showTitle` | `boolean` | `true` | Show the post title as an `<h1>`. |
|
|
223
|
+
| `showDate` | `boolean` | `true` | Show the published/updated date. |
|
|
224
|
+
| `className` | `string` | `"ba-post"` | CSS class on the `<article>`. |
|
|
225
|
+
| `renderContent` | `(content: string) => ReactNode` | — | Override the default `<Markdown>` renderer. |
|
|
226
|
+
|
|
227
|
+
### `<BlogPostList>`
|
|
228
|
+
|
|
229
|
+
Renders a list of post cards with titles, dates, and excerpts.
|
|
230
|
+
|
|
231
|
+
```tsx
|
|
232
|
+
import { BlogPostList } from "@autoblogwriter/sdk/react";
|
|
233
|
+
|
|
234
|
+
<BlogPostList posts={posts} />
|
|
235
|
+
<BlogPostList posts={posts} title="Blog" routePrefix="/articles" />
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
With Next.js `<Link>`:
|
|
239
|
+
|
|
240
|
+
```tsx
|
|
241
|
+
import Link from "next/link";
|
|
242
|
+
|
|
243
|
+
<BlogPostList posts={posts} linkComponent={Link} />
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
With a custom card:
|
|
247
|
+
|
|
248
|
+
```tsx
|
|
249
|
+
<BlogPostList
|
|
250
|
+
posts={posts}
|
|
251
|
+
renderCard={(post, href) => (
|
|
252
|
+
<a href={href}>{post.title} — {post.excerpt}</a>
|
|
253
|
+
)}
|
|
254
|
+
/>
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
| Prop | Type | Default | Description |
|
|
258
|
+
| --- | --- | --- | --- |
|
|
259
|
+
| `posts` | `BlogPost[]` | — | Array of post objects. |
|
|
260
|
+
| `title` | `string` | `"Latest posts"` | Section heading. Pass empty string to hide. |
|
|
261
|
+
| `routePrefix` | `string` | `"/blog"` | URL prefix for post links (`{routePrefix}/{slug}`). |
|
|
262
|
+
| `linkComponent` | `React.ComponentType` | `<a>` | Custom link component (e.g. Next.js `Link`). |
|
|
263
|
+
| `className` | `string` | `"ba-listing"` | CSS class on the `<section>`. |
|
|
264
|
+
| `renderCard` | `(post, href) => ReactNode` | — | Override the default card rendering. |
|
|
265
|
+
|
|
266
|
+
## Styles (`@autoblogwriter/sdk/styles.css`)
|
|
267
|
+
|
|
268
|
+
The SDK ships a default dark-theme stylesheet that styles all the React components out of the box. Import it in your app entry point:
|
|
143
269
|
|
|
144
270
|
```ts
|
|
145
|
-
// app/
|
|
146
|
-
import
|
|
271
|
+
// app/layout.tsx or src/main.tsx
|
|
272
|
+
import "@autoblogwriter/sdk/styles.css";
|
|
273
|
+
```
|
|
147
274
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
275
|
+
All values are driven by CSS custom properties on `:root`, so you can override them to match your theme:
|
|
276
|
+
|
|
277
|
+
```css
|
|
278
|
+
:root {
|
|
279
|
+
--ba-color-bg: #0c0d10;
|
|
280
|
+
--ba-color-text: #f2f4f8;
|
|
281
|
+
--ba-color-text-muted: rgba(242, 244, 248, 0.6);
|
|
282
|
+
--ba-color-link: #6cf;
|
|
283
|
+
--ba-color-link-hover: #8df;
|
|
284
|
+
--ba-color-border: rgba(255, 255, 255, 0.08);
|
|
285
|
+
--ba-color-card-bg: rgba(255, 255, 255, 0.02);
|
|
286
|
+
--ba-color-card-bg-hover: rgba(255, 255, 255, 0.04);
|
|
287
|
+
--ba-color-code-bg: rgba(255, 255, 255, 0.1);
|
|
288
|
+
--ba-color-pre-bg: rgba(255, 255, 255, 0.05);
|
|
289
|
+
--ba-radius: 8px;
|
|
290
|
+
--ba-max-width: 720px;
|
|
291
|
+
--ba-font-sans: system-ui, -apple-system, sans-serif;
|
|
292
|
+
--ba-font-mono: ui-monospace, SFMono-Regular, monospace;
|
|
153
293
|
}
|
|
154
294
|
```
|
|
155
295
|
|
|
156
|
-
|
|
296
|
+
For a light theme, override the color variables:
|
|
297
|
+
|
|
298
|
+
```css
|
|
299
|
+
:root {
|
|
300
|
+
--ba-color-bg: #ffffff;
|
|
301
|
+
--ba-color-text: #1a1a1a;
|
|
302
|
+
--ba-color-text-muted: rgba(0, 0, 0, 0.5);
|
|
303
|
+
--ba-color-text-secondary: rgba(0, 0, 0, 0.7);
|
|
304
|
+
--ba-color-text-content: rgba(0, 0, 0, 0.85);
|
|
305
|
+
--ba-color-link: #0066cc;
|
|
306
|
+
--ba-color-link-hover: #0052a3;
|
|
307
|
+
--ba-color-border: rgba(0, 0, 0, 0.1);
|
|
308
|
+
--ba-color-card-bg: rgba(0, 0, 0, 0.02);
|
|
309
|
+
--ba-color-card-bg-hover: rgba(0, 0, 0, 0.04);
|
|
310
|
+
--ba-color-code-bg: rgba(0, 0, 0, 0.06);
|
|
311
|
+
--ba-color-pre-bg: rgba(0, 0, 0, 0.03);
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### CSS Class Reference
|
|
316
|
+
|
|
317
|
+
| Class | Used by | Description |
|
|
318
|
+
| --- | --- | --- |
|
|
319
|
+
| `.ba-listing` | `<BlogPostList>` | Outer section wrapper |
|
|
320
|
+
| `.ba-listing-title` | `<BlogPostList>` | Section heading |
|
|
321
|
+
| `.ba-posts` | `<BlogPostList>` | Flex column container for cards |
|
|
322
|
+
| `.ba-post-card` | `<BlogPostList>` | Individual post card |
|
|
323
|
+
| `.ba-post-card-title` | `<BlogPostList>` | Card title |
|
|
324
|
+
| `.ba-post-card-link` | `<BlogPostList>` | Title link |
|
|
325
|
+
| `.ba-post-card-meta` | `<BlogPostList>` | Card date |
|
|
326
|
+
| `.ba-post-card-excerpt` | `<BlogPostList>` | Card excerpt |
|
|
327
|
+
| `.ba-post` | `<BlogPost>` | Article wrapper |
|
|
328
|
+
| `.ba-post-title` | `<BlogPost>` | Post title |
|
|
329
|
+
| `.ba-post-meta` | `<BlogPost>` | Date and reading time |
|
|
330
|
+
| `.ba-markdown` | `<Markdown>` | Markdown content wrapper |
|
|
157
331
|
|
|
158
|
-
|
|
332
|
+
## Low-Level Client Configuration
|
|
333
|
+
|
|
334
|
+
For advanced use cases where you need more control than the env-based helpers provide, you can create a client directly:
|
|
159
335
|
|
|
160
336
|
```ts
|
|
161
|
-
// server/routes/blogauto.ts (Express example)
|
|
162
|
-
import express from "express";
|
|
163
337
|
import { createBlogAutoClient } from "@autoblogwriter/sdk";
|
|
164
338
|
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
apiUrl: process.env.BLOGAUTO_API_URL!,
|
|
339
|
+
const client = createBlogAutoClient({
|
|
340
|
+
apiUrl: "https://api.autoblogwriter.app",
|
|
168
341
|
apiKey: process.env.BLOGAUTO_API_KEY!,
|
|
169
|
-
workspaceId: process.env.BLOGAUTO_WORKSPACE_ID
|
|
342
|
+
workspaceId: process.env.BLOGAUTO_WORKSPACE_ID,
|
|
343
|
+
workspaceSlug: process.env.BLOGAUTO_WORKSPACE_SLUG,
|
|
344
|
+
authMode: "bearer", // or "x-api-key"
|
|
345
|
+
headers: { "x-trace-id": "..." },
|
|
346
|
+
timeoutMs: 10_000,
|
|
347
|
+
fetch: globalThis.fetch, // optional custom fetch
|
|
170
348
|
});
|
|
349
|
+
```
|
|
171
350
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
351
|
+
| Option | Type | Required | Description |
|
|
352
|
+
| --- | --- | --- | --- |
|
|
353
|
+
| `apiUrl` | `string` | yes | Root API URL, e.g. `https://api.autoblogwriter.app`. Trailing slash removed automatically. |
|
|
354
|
+
| `apiKey` | `string` | yes | Workspace-scoped API key created in the AutoBlogWriter dashboard. |
|
|
355
|
+
| `workspaceId` | `string` | one of `workspaceId` or `workspaceSlug` | Optional; used for account scoping. |
|
|
356
|
+
| `workspaceSlug` | `string` | one of `workspaceId` or `workspaceSlug` | Required for public content APIs (`/v1/public/:workspaceSlug/...`). |
|
|
357
|
+
| `authMode` | `"bearer" \| "x-api-key"` | no (default `"bearer"`) | Sends the API key via `Authorization: Bearer` or `x-api-key`. |
|
|
358
|
+
| `headers` | `Record<string, string>` | no | Additional static headers (merged on each request). |
|
|
359
|
+
| `fetch` | `typeof fetch` | no | Provide a custom fetch implementation (e.g., for SSR polyfills). |
|
|
360
|
+
| `timeoutMs` | `number` | no | Optional request timeout enforced client-side. |
|
|
179
361
|
|
|
180
|
-
|
|
181
|
-
const post = await blogClient.getPostBySlug(req.params.slug);
|
|
182
|
-
if (!post) {
|
|
183
|
-
return res.status(404).json({ error: "Not Found" });
|
|
184
|
-
}
|
|
185
|
-
res.json(post);
|
|
186
|
-
});
|
|
362
|
+
## Revalidation
|
|
187
363
|
|
|
188
|
-
|
|
189
|
-
```
|
|
364
|
+
When AutoBlogWriter publishes a post it can ping your Next.js site so cached pages refresh immediately.
|
|
190
365
|
|
|
191
|
-
|
|
366
|
+
**Quick setup** (env-based, one line):
|
|
192
367
|
|
|
193
368
|
```ts
|
|
194
|
-
//
|
|
195
|
-
import
|
|
196
|
-
|
|
197
|
-
const fetcher = (url: string) => fetch(url).then((res) => res.json());
|
|
198
|
-
|
|
199
|
-
export function BlogList() {
|
|
200
|
-
const { data } = useSWR("/api/blogauto/posts", fetcher);
|
|
201
|
-
if (!data) return <p>Loading…</p>;
|
|
202
|
-
|
|
203
|
-
return (
|
|
204
|
-
<ul>
|
|
205
|
-
{data.posts.map((post: any) => (
|
|
206
|
-
<li key={post.id}>{post.title}</li>
|
|
207
|
-
))}
|
|
208
|
-
</ul>
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
```
|
|
369
|
+
// app/api/blogauto/revalidate/route.ts
|
|
370
|
+
import { createEnvRevalidateHandler } from "@autoblogwriter/sdk/next";
|
|
212
371
|
|
|
213
|
-
|
|
372
|
+
export const POST = createEnvRevalidateHandler();
|
|
373
|
+
```
|
|
214
374
|
|
|
215
|
-
|
|
375
|
+
Set `BLOGAUTO_REVALIDATE_SECRET` in your environment and configure the webhook URL in the AutoBlogWriter dashboard under **Workspace Settings → Revalidation URL**.
|
|
216
376
|
|
|
217
|
-
|
|
218
|
-
- In the AutoBlogWriter dashboard open **Workspace Settings → Revalidation URL** and set your endpoint (e.g., `https://yoursite.com/api/blogauto/revalidate`).
|
|
219
|
-
- Generate a **Revalidation Webhook secret**, copy the plain secret once, and store it as `BLOGAUTO_REVALIDATE_SECRET`.
|
|
220
|
-
2. **Create the route handler**
|
|
377
|
+
**Advanced setup** (manual control via `@autoblogwriter/sdk/revalidate`):
|
|
221
378
|
|
|
222
379
|
```ts
|
|
223
380
|
// app/api/blogauto/revalidate/route.ts
|
|
224
381
|
import { revalidatePath, revalidateTag } from "next/cache";
|
|
225
382
|
import { createRevalidateRouteHandler } from "@autoblogwriter/sdk/revalidate";
|
|
226
383
|
|
|
227
|
-
const secret = process.env.BLOGAUTO_REVALIDATE_SECRET;
|
|
228
|
-
if (!secret) {
|
|
229
|
-
throw new Error("Set BLOGAUTO_REVALIDATE_SECRET on the server");
|
|
230
|
-
}
|
|
231
|
-
|
|
232
384
|
export const POST = createRevalidateRouteHandler({
|
|
233
|
-
secret
|
|
385
|
+
secret: process.env.BLOGAUTO_REVALIDATE_SECRET!,
|
|
234
386
|
revalidatePath,
|
|
235
387
|
revalidateTag,
|
|
236
|
-
// Optional overrides:
|
|
237
|
-
// allowedSkewSeconds: 600,
|
|
238
|
-
// revalidatePaths: (payload) => [...custom paths...],
|
|
239
|
-
// revalidateTags: (payload) => [...custom tags...],
|
|
240
388
|
});
|
|
241
389
|
```
|
|
242
390
|
|
|
@@ -245,50 +393,18 @@ The handler verifies the `X-BlogAuto-Signature` HMAC header, enforces timestamp
|
|
|
245
393
|
- Paths: `/sitemap.xml`, `/robots.txt`, `/blog`, `/blog/:slug`
|
|
246
394
|
- Tags: `blogauto:<workspaceSlug>:sitemap`, `blogauto:<workspaceSlug>:posts`, and `blogauto:<workspaceSlug>:post:<slug>`
|
|
247
395
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
Use the `next` option when calling SDK methods so `revalidateTag` knows which caches to expire:
|
|
251
|
-
|
|
252
|
-
```ts
|
|
253
|
-
await blogClient.getPosts({
|
|
254
|
-
limit: 20,
|
|
255
|
-
next: { tags: [`blogauto:${workspaceSlug}:posts`] },
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
await blogClient.getPostBySlug(slug, {
|
|
259
|
-
next: { tags: [`blogauto:${workspaceSlug}:post:${slug}`] },
|
|
260
|
-
});
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
### Cache Invalidation Notes
|
|
264
|
-
|
|
265
|
-
- In production, rely on tag-based revalidation via the webhook handler above. This prevents users from needing hard refreshes after publishing.
|
|
266
|
-
- For very dynamic pages, you can opt out of caching per request with `cache: "no-store"` in the fetch options.
|
|
267
|
-
- For client-side lists (SWR/React Query), call `mutate`/`invalidateQueries` after your proxy endpoint updates, or on a webhook-driven event from your backend.
|
|
268
|
-
|
|
269
|
-
4. **Need manual verification?**
|
|
396
|
+
For manual signature verification:
|
|
270
397
|
|
|
271
398
|
```ts
|
|
272
399
|
import { verifyWebhookSignature } from "@autoblogwriter/sdk/revalidate";
|
|
273
400
|
|
|
274
401
|
const isValid = verifyWebhookSignature({
|
|
275
|
-
rawBody,
|
|
402
|
+
rawBody,
|
|
276
403
|
signature: req.headers.get("x-blogauto-signature"),
|
|
277
404
|
secret: process.env.BLOGAUTO_REVALIDATE_SECRET!,
|
|
278
405
|
});
|
|
279
406
|
```
|
|
280
407
|
|
|
281
|
-
Reject missing/invalid signatures or stale timestamps to prevent replay attacks, and avoid logging the secret or signature hash.
|
|
282
|
-
|
|
283
|
-
## Markdown Rendering Helper
|
|
284
|
-
|
|
285
|
-
`renderMarkdownToHtml(markdown: string)` ships a dependency-free renderer that covers headings, links, emphasis, inline/code fences, and paragraphs. For richer formatting you can bring your own renderer—this helper simply provides a safe default.
|
|
286
|
-
|
|
287
|
-
## Dynamic Sitemaps & Robots
|
|
288
|
-
|
|
289
|
-
- `buildSitemap({ siteUrl, entries, routePrefix })` returns a `MetadataRoute.Sitemap` array ready for `app/sitemap.ts`.
|
|
290
|
-
- `buildRobots({ siteUrl, sitemapPath })` outputs a `MetadataRoute.Robots` structure. Defaults: route prefix `/blog`, sitemap path `/sitemap.xml`.
|
|
291
|
-
|
|
292
408
|
## Testing
|
|
293
409
|
|
|
294
410
|
```bash
|
package/package.json
CHANGED