@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.
Files changed (2) hide show
  1. package/README.md +285 -169
  2. 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 App Router Usage
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
- All examples assume you run in server files where environment variables are available. **Do not** move these snippets to client components without a secure proxy.
88
+ ### `fetchBlogPosts(options?)`
51
89
 
52
- ### `app/blog/page.tsx`
90
+ Fetches a paginated list of posts with automatic cache tagging.
53
91
 
54
92
  ```ts
55
- // app/blog/page.tsx
56
- import { createBlogAutoClient, renderMarkdownToHtml } from "@autoblogwriter/sdk";
93
+ import { fetchBlogPosts } from "@autoblogwriter/sdk/next";
57
94
 
58
- const workspaceSlug = process.env.BLOGAUTO_WORKSPACE!;
59
- const blogClient = createBlogAutoClient({
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
- export default async function BlogIndexPage() {
66
- const { posts } = await blogClient.getPosts({
67
- limit: 20,
68
- next: { tags: [`blogauto:${workspaceSlug}:posts`] },
69
- });
70
-
71
- return (
72
- <div>
73
- {posts.map((post) => (
74
- <article key={post.id}>
75
- <h2>{post.title}</h2>
76
- <div dangerouslySetInnerHTML={{ __html: renderMarkdownToHtml(post.excerpt ?? "") }} />
77
- </article>
78
- ))}
79
- </div>
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
- ### `app/blog/[slug]/page.tsx`
120
+ ### `generateBlogSitemap()`
121
+
122
+ Returns a `MetadataRoute.Sitemap` array for `app/sitemap.ts`.
85
123
 
86
124
  ```ts
87
- // app/blog/[slug]/page.tsx
88
- import { notFound } from "next/navigation";
89
- import { createBlogAutoClient, renderMarkdownToHtml } from "@autoblogwriter/sdk";
125
+ import { generateBlogSitemap } from "@autoblogwriter/sdk/next";
90
126
 
91
- const workspaceSlug = process.env.BLOGAUTO_WORKSPACE!;
92
- const blogClient = createBlogAutoClient({
93
- apiUrl: process.env.BLOGAUTO_API_URL!,
94
- apiKey: process.env.BLOGAUTO_API_KEY!,
95
- workspaceSlug,
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 blogClient.getPostBySlug(params.slug, {
104
- next: { tags: [`blogauto:${workspaceSlug}:post:${params.slug}`] },
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
- ### `app/sitemap.ts`
187
+ ## React Components (`@autoblogwriter/sdk/react`)
121
188
 
122
- ```ts
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
- const blogClient = createBlogAutoClient({
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
- export default async function sitemap() {
133
- const entries = await blogClient.getSitemapEntries();
134
- return buildSitemap({
135
- siteUrl: process.env.SITE_URL!,
136
- entries,
137
- routePrefix: "/blog",
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
- ### `app/robots.ts`
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/robots.ts
146
- import { buildRobots } from "@autoblogwriter/sdk";
271
+ // app/layout.tsx or src/main.tsx
272
+ import "@autoblogwriter/sdk/styles.css";
273
+ ```
147
274
 
148
- export default function robots() {
149
- return buildRobots({
150
- siteUrl: process.env.SITE_URL!,
151
- sitemapPath: "/sitemap.xml",
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
- ## Vite/React Usage with Server Proxy
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
- Never pipe the API key directly to the browser. Instead, add a tiny proxy route inside your server (Express, Fastify, Next API route, etc.) that calls the SDK.
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 router = express.Router();
166
- const blogClient = createBlogAutoClient({
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
- router.get("/posts", async (req, res) => {
173
- const { posts, nextCursor } = await blogClient.getPosts({
174
- limit: Number(req.query.limit) || 10,
175
- cursor: req.query.cursor?.toString(),
176
- });
177
- res.json({ posts, nextCursor });
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
- router.get("/posts/:slug", async (req, res) => {
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
- export default router;
189
- ```
364
+ When AutoBlogWriter publishes a post it can ping your Next.js site so cached pages refresh immediately.
190
365
 
191
- React (client) calls your proxy:
366
+ **Quick setup** (env-based, one line):
192
367
 
193
368
  ```ts
194
- // src/pages/Blog.tsx
195
- import useSWR from "swr";
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
- ## Automatic Revalidation Setup
372
+ export const POST = createEnvRevalidateHandler();
373
+ ```
214
374
 
215
- When AutoBlogWriter publishes a post it can ping your Next.js App Router site so sitemap, robots, and cached blog pages refresh immediately.
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
- 1. **Configure your workspace**
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
- 3. **Tag your data fetches**
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, // string or Buffer
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autoblogwriter/sdk",
3
- "version": "2.0.5",
3
+ "version": "2.0.6",
4
4
  "description": "Official AutoBlogWriter SDK for fetching posts, building sitemaps, rendering helpers, and revalidation utilities.",
5
5
  "license": "MIT",
6
6
  "type": "module",