Framework-Agnostic Helpers
Server-side helper functions for any Node.js framework — Remix, Astro, Express, or plain Node.
Framework-Agnostic Helpers
The @autoblogwriter/sdk/helpers entry point provides the same high-level helpers as @autoblogwriter/sdk/next, but with no Next.js dependencies. Use it with Remix, Astro, Express, Vite SSR, or any other Node.js server environment.
import {
fetchPosts,
fetchPost,
buildBlogSitemap,
buildBlogRobots,
buildJsonLdScript,
} from "@autoblogwriter/sdk/helpers";:::note[Always graceful]
Every function in this module catches all errors internally. On a missing env var or API failure it logs a console.warn and returns a safe empty default — it never throws. fetchPost returns null instead of calling notFound(), so your framework's 404 handling stays in your hands.
:::
Environment Variables
| Env Variable | Required | Description |
|---|---|---|
AUTOBLOGWRITER_API_KEY | yes | Workspace API key |
AUTOBLOGWRITER_WORKSPACE_SLUG | yes | Workspace slug for content APIs |
AUTOBLOGWRITER_API_URL | no | Defaults to https://api.autoblogwriter.app |
AUTOBLOGWRITER_WORKSPACE_ID | no | Optional workspace ID |
SITE_URL or NEXT_PUBLIC_SITE_URL | no | Defaults to http://localhost:3000 |
Data Fetching
fetchPosts(options?)
Fetches a paginated list of published posts.
import { fetchPosts } from "@autoblogwriter/sdk/helpers";
const { posts, nextCursor } = await fetchPosts({ limit: 20 });Options:
| Param | Type | Default | Description |
|---|---|---|---|
limit | number | 20 | Posts per page |
cursor | string | — | Pagination cursor |
category | string | — | Filter by category slug |
Returns: PostsResponse — { posts: BlogPost[], nextCursor?: string, pagination? }
On error: returns { posts: [], nextCursor: undefined, pagination: { page: 1, limit: 20, total: 0, totalPages: 0, hasMore: false } } and logs a warning.
Example: Remix loader
// app/routes/blog._index.tsx
import { fetchPosts } from "@autoblogwriter/sdk/helpers";
import type { LoaderFunctionArgs } from "@remix-run/node";
export async function loader({ request }: LoaderFunctionArgs) {
const url = new URL(request.url);
const cursor = url.searchParams.get("cursor") ?? undefined;
const { posts, nextCursor } = await fetchPosts({ limit: 10, cursor });
return { posts, nextCursor };
}Example: Astro page
---
// src/pages/blog/index.astro
import { fetchPosts } from "@autoblogwriter/sdk/helpers";
const { posts } = await fetchPosts({ limit: 20 });
---
<ul>
{posts.map(post => (
<li><a href={`/blog/${post.slug}`}>{post.title}</a></li>
))}
</ul>fetchPost(slug)
Fetches a single post by slug. Returns null if the post is not found or if there's any error.
import { fetchPost } from "@autoblogwriter/sdk/helpers";
const post = await fetchPost("my-first-post");
if (!post) {
// handle not-found in your framework
}Parameters:
| Param | Type | Description |
|---|---|---|
slug | string | The post's URL slug |
Returns: BlogPost | null — null if not found or on any error.
On error: logs a warning and returns null. Your code is responsible for showing a 404.
Example: Remix loader with 404
// app/routes/blog.$slug.tsx
import { fetchPost } from "@autoblogwriter/sdk/helpers";
import { json, redirect } from "@remix-run/node";
export async function loader({ params }: { params: { slug: string } }) {
const post = await fetchPost(params.slug);
if (!post) throw new Response("Not Found", { status: 404 });
return json({ post });
}Example: Astro with 404
---
// src/pages/blog/[slug].astro
import { fetchPost } from "@autoblogwriter/sdk/helpers";
const { slug } = Astro.params;
const post = await fetchPost(slug!);
if (!post) return Astro.redirect("/404");
---
<h1>{post.title}</h1>SEO
buildBlogSitemap()
Builds a sitemap array from all published posts. Returns plain objects — no Next.js types required.
import { buildBlogSitemap } from "@autoblogwriter/sdk/helpers";
const entries = await buildBlogSitemap();
// [{ url: "https://yoursite.com/blog/my-post", lastModified: "2025-01-01T00:00:00.000Z" }, ...]Returns: Array<{ url: string; lastModified?: string }>
On error: returns [] and logs a warning.
Example: Remix sitemap route
// app/routes/sitemap[.xml].tsx
import { buildBlogSitemap } from "@autoblogwriter/sdk/helpers";
export async function loader() {
const entries = await buildBlogSitemap();
const xml = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
...entries.map(e =>
`<url><loc>${e.url}</loc>${e.lastModified ? `<lastmod>${e.lastModified}</lastmod>` : ""}</url>`
),
"</urlset>",
].join("\n");
return new Response(xml, { headers: { "Content-Type": "application/xml" } });
}Example: Astro sitemap
// src/pages/sitemap.xml.ts
import { buildBlogSitemap } from "@autoblogwriter/sdk/helpers";
import type { APIRoute } from "astro";
export const GET: APIRoute = async () => {
const entries = await buildBlogSitemap();
const xml = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
...entries.map(e =>
`<url><loc>${e.url}</loc>${e.lastModified ? `<lastmod>${e.lastModified}</lastmod>` : ""}</url>`
),
"</urlset>",
].join("\n");
return new Response(xml, { headers: { "Content-Type": "application/xml" } });
};buildBlogRobots()
Builds a robots configuration object.
import { buildBlogRobots } from "@autoblogwriter/sdk/helpers";
const robots = buildBlogRobots();
// { rules: [{ userAgent: "*", allow: "/" }], sitemap: "https://yoursite.com/sitemap.xml" }Returns: { rules: MetadataRouteRobotsRule | MetadataRouteRobotsRule[]; sitemap?: string | string[] }
On error: returns { rules: [{ userAgent: "*", allow: "/" }] } and logs a warning.
Example: Remix robots route
// app/routes/robots[.txt].tsx
import { buildBlogRobots } from "@autoblogwriter/sdk/helpers";
export async function loader() {
const { rules, sitemap } = buildBlogRobots();
const lines = [
"User-agent: *",
"Allow: /",
sitemap ? `Sitemap: ${sitemap}` : "",
].filter(Boolean);
return new Response(lines.join("\n"), { headers: { "Content-Type": "text/plain" } });
}buildJsonLdScript(post)
Re-exported from @autoblogwriter/sdk. Generates a <script type="application/ld+json"> string for a blog post. Returns null if the post has no JSON-LD metadata.
import { buildJsonLdScript } from "@autoblogwriter/sdk/helpers";
const jsonLd = buildJsonLdScript(post);When to use ./helpers vs ./next
@autoblogwriter/sdk/helpers | @autoblogwriter/sdk/next | |
|---|---|---|
| Framework | Any Node.js (Remix, Astro, Express, …) | Next.js App Router only |
| ISR cache tags | No | Yes (next: { tags: [...] }) |
| On config/API error | Returns safe default, logs warning | Returns safe default, logs warning |
fetchPost not-found | Returns null — you handle the 404 | Calls notFound() — Next.js renders not-found.tsx |
| Next.js peer dep | Not required | Required |
:::tip
If you only need one entry point and your project uses Next.js, prefer @autoblogwriter/sdk/next — it handles notFound() automatically and adds ISR cache tags so pages revalidate via webhooks. Use @autoblogwriter/sdk/helpers for every other framework or when you want explicit control over 404 handling.
:::
Next Steps
- Next.js Helpers — Next.js-specific helpers with ISR cache tagging.
- Webhooks & Revalidation — Cache invalidation when posts are published.
- Client Reference — Manual client usage for advanced scenarios.