Docs
Helpers
Documentation

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 VariableRequiredDescription
AUTOBLOGWRITER_API_KEYyesWorkspace API key
AUTOBLOGWRITER_WORKSPACE_SLUGyesWorkspace slug for content APIs
AUTOBLOGWRITER_API_URLnoDefaults to https://api.autoblogwriter.app
AUTOBLOGWRITER_WORKSPACE_IDnoOptional workspace ID
SITE_URL or NEXT_PUBLIC_SITE_URLnoDefaults 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:

ParamTypeDefaultDescription
limitnumber20Posts per page
cursorstringPagination cursor
categorystringFilter 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:

ParamTypeDescription
slugstringThe post's URL slug

Returns: BlogPost | nullnull 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
FrameworkAny Node.js (Remix, Astro, Express, …)Next.js App Router only
ISR cache tagsNoYes (next: { tags: [...] })
On config/API errorReturns safe default, logs warningReturns safe default, logs warning
fetchPost not-foundReturns null — you handle the 404Calls notFound() — Next.js renders not-found.tsx
Next.js peer depNot requiredRequired

:::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