@lobsterlead/blog-sdk
v0.1.1
Published
Official client SDK for the Lobsterlead blog-system — fetch and publish blog posts with 3 lines of code
Downloads
288
Maintainers
Readme
@lobsterlead/blog-sdk
Official TypeScript client SDK for blog-system — a multi-tenant blog API. Fetch posts, publish content, generate same-origin sitemaps, and ship SEO-ready Next.js pages with three lines of code.
📦 npm: https://www.npmjs.com/package/@lobsterlead/blog-sdk
Installation
npm install @lobsterlead/blog-sdk
# or
pnpm add @lobsterlead/blog-sdk
# or
yarn add @lobsterlead/blog-sdknext is an optional peer dependency — only required if you import from @lobsterlead/blog-sdk/next.
Quick start
import { BlogClient } from "@lobsterlead/blog-sdk";
const blog = new BlogClient({
baseUrl: process.env.BLOG_BASE_URL!, // e.g. https://blog.example.com
siteKey: process.env.BLOG_SITE_KEY!, // e.g. "guneyexport"
apiKey: process.env.BLOG_API_KEY, // server-only — required for writes
// readKey: process.env.BLOG_READ_KEY, // only for gated sites
});
// Read
const { posts } = await blog.posts.list({ limit: 20, locale: "tr" });
const detail = await blog.posts.get("my-slug", { locale: "tr" });
// Write
const created = await blog.posts.create({
title: "Hello world",
locale: "tr",
bodyHtml: "<p>First post</p>",
status: "published",
});Next.js (App Router)
// lib/blog.ts
import { BlogClient } from "@lobsterlead/blog-sdk";
export const blog = new BlogClient({
baseUrl: process.env.BLOG_BASE_URL!,
siteKey: process.env.BLOG_SITE_KEY!,
apiKey: process.env.BLOG_API_KEY,
fetchInit: { next: { revalidate: 300, tags: ["blog"] } },
});// app/blog/[slug]/page.tsx — SEO meta + JSON-LD ready
import { notFound } from "next/navigation";
import type { Metadata } from "next";
import { BlogApiError } from "@lobsterlead/blog-sdk";
import { toNextMetadata, jsonLdScript } from "@lobsterlead/blog-sdk/next";
import { blog } from "@/lib/blog";
type Props = { params: Promise<{ slug: string }> };
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params;
try {
const detail = await blog.posts.get(slug, { locale: "tr" });
return toNextMetadata(detail.meta);
} catch (e) {
if (e instanceof BlogApiError && e.status === 404) return {};
throw e;
}
}
export default async function BlogPostPage({ params }: Props) {
const { slug } = await params;
let detail;
try {
detail = await blog.posts.get(slug, { locale: "tr" });
} catch (e) {
if (e instanceof BlogApiError && e.status === 404) notFound();
throw e;
}
return (
<article>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: jsonLdScript(detail) }}
/>
<h1>{detail.post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: detail.post.bodyHtml }} />
</article>
);
}Same-origin sitemap
// app/sitemap.ts
import type { MetadataRoute } from "next";
import { blog } from "@/lib/blog";
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const host = "https://your-landing.com";
const posts = await blog.sitemapEntries({ origin: host, pathPrefix: "/blog" });
return [
{ url: `${host}/`, priority: 1.0 },
{ url: `${host}/blog`, priority: 0.8 },
...posts,
];
}API reference
new BlogClient(options)
| Option | Type | Required | Description |
|---|---|---|---|
| baseUrl | string | yes | Base URL of the blog-system service |
| siteKey | string | yes | Public site identifier |
| apiKey | string | for writes | Server-only — required for create/update/remove |
| readKey | string | for gated reads | Required if the site is isPublicRead: false |
| fetch | typeof fetch | no | Custom fetch implementation |
| fetchInit | RequestInit & { next?: { revalidate?, tags? } } | no | Extra init for caching |
Methods
blog.posts.list({ limit?, cursor?, locale?, tag? })→{ posts, nextCursor }blog.posts.get(slug, { locale? })→{ post, meta, jsonLd }blog.posts.create(input)→Post(apiKey required)blog.posts.update(slug, patch, { locale? })→Post(apiKey required)blog.posts.remove(slug, { locale? })→void(apiKey required, soft-delete to archived)blog.posts.all(params?)→ async iterator paginating through all postsblog.sitemapEntries({ origin, pathPrefix? })→Array<{ url, lastModified, changeFrequency, priority }>inMetadataRoute.Sitemapshapeblog.pingSearchEngines()→ triggers Google + Bing sitemap ping (apiKey required)
Next.js helpers (@lobsterlead/blog-sdk/next)
toNextMetadata(meta)— convertsPostMetato a Next.jsMetadataobjectjsonLdScript(detail)— returns a JSON string ready to inject into a<script type="application/ld+json">tag
Errors
All HTTP errors throw BlogApiError with status, code, message, and details. Common codes:
SITE_NOT_FOUND(404)MISSING_API_KEY/INVALID_API_KEY(401)MISSING_READ_KEY/INVALID_READ_KEY(401)FORBIDDEN_ORIGIN(403)RATE_LIMITED(429) — retry afterRetry-AfterheaderVALIDATION_ERROR(400)DUPLICATE_SLUG(409)
import { BlogApiError } from "@lobsterlead/blog-sdk";
try {
await blog.posts.get("nonexistent");
} catch (e) {
if (e instanceof BlogApiError && e.status === 404) {
// not found
}
throw e;
}License
MIT © Sirius Tedarik
