npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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

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/blog

Usage

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
  };
};