@kc-cms/blog
v0.2.0
Published
Client library for reading published articles from a KC Partners CMS blog (the headless content API). Framework-agnostic, zero runtime dependencies, with a Next.js App Router starter.
Downloads
363
Maintainers
Readme
@kc-cms/blog
Client library for reading published articles from a KC Partners CMS blog.
The CMS is the headless content source; your app is the head. This package is a
thin, fully-typed wrapper over the CMS public REST API (/api/v1), with zero
runtime dependencies — it uses the platform fetch, so it runs in Node 18+,
edge runtimes, and the browser (server-side use only — see below).
It ships with a Next.js App Router starter for a complete blog in minutes.
Install
npm install @kc-cms/blogUsage
Configure the client from the environment — set KC_BLOG_API_KEY and
KC_BLOG_API_URL (your CMS host, without the /api/v1 suffix), then:
import { BlogClient } from "@kc-cms/blog";
const client = BlogClient.fromEnv(); // reads KC_BLOG_API_KEY + KC_BLOG_API_URL
const { data, pagination } = await client.getPosts(1, 12);
const tagged = await client.getPostsByTag("seo", 1, 12);
const post = await client.getPost("how-to-rank"); // null if not found
const everything = await client.getAllPosts();Prefer fromEnv() so the host and key are never hardcoded. If you must
construct it explicitly, still read both from the environment — don't inline a
literal URL:
const client = new BlogClient(process.env.KC_BLOG_API_KEY!, {
baseUrl: process.env.KC_BLOG_API_URL!, // CMS host, without /api/v1
});Keep your API key server-side only. Use it from Server Components, route handlers, or server utilities — never expose it to the browser (in Next.js, never prefix it with
NEXT_PUBLIC_).
API
new BlogClient(apiKey, options)
| Option | Type | Default | |
| --- | --- | --- | --- |
| baseUrl | string | — | Required. CMS host without /api/v1. |
| timeoutMs | number | 30000 | Per-request timeout. |
| fetch | typeof fetch | global fetch | Custom fetch (tests, proxies). |
Methods
| Method | Returns |
| --- | --- |
| getPosts(page?, limit?) | PostListResponse — { data: PostSummary[], pagination } |
| getPostsByTag(tag, page?, limit?) | PostListResponse |
| getPost(slug) | Post \| null (null on 404) |
| getAllPosts(pageSize?) | PostSummary[] (pages through everything) |
| getTags() | TagCount[] |
| getAuthors() | PublicAuthor[] |
| getSitemapXml() | string (raw XML) |
| getFeedXml() | string (raw RSS XML) |
Pages are one-based.
Errors
Every failure throws a BlogApiError:
import { BlogApiError } from "@kc-cms/blog";
try {
await client.getPosts();
} catch (err) {
if (err instanceof BlogApiError) {
console.error(err.status, err.message); // status is undefined for network errors
}
}Types
Post extends PostSummary with the article body and SEO block:
type PostSummary = {
slug: string;
title: string;
excerpt: string;
tags: string[];
readingMinutes: number;
thumbnailUrl?: string;
author: { name: string; avatarUrl?: string; link?: string };
publishedAt: string; // ISO 8601
updatedAt: string; // ISO 8601
};
type Post = PostSummary & {
markdown: string;
seo: {
metaTitle: string;
metaDescription: string;
canonicalUrl: string;
ogImage?: string;
jsonLd: Record<string, unknown>; // schema.org BlogPosting
};
};