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

@larkup/blog-sdk

v1.3.0

Published

SDK for fetching and rendering posts from Larkup Blog Studio

Readme

Larkup Blog SDK

A TypeScript SDK for fetching and rendering blog content from a Larkup Studio backend. Ships with a framework-agnostic HTTP client and optional React bindings.


Installation

npm install @larkup/blog-sdk
# or
yarn add @larkup/blog-sdk

Peer dependencies (React bindings only): React ≥ 17


Quick start

Vanilla / Node

import { LarkupBlogClient } from "@larkup/blog-sdk";

const client = new LarkupBlogClient({
  apiKey: "lk_larkup_xxxxxxxxxxxx",
  studioUrl: "https://your-studio.larkup.io",
});

const posts = await client.getPosts();
const post = await client.getPostBySlug("hello-world");

React

import { LarkupBlogClient } from "@larkup/blog-sdk";
import {
  LarkupProvider,
  usePosts,
  LarkupArticle,
} from "@larkup/blog-sdk/react";

const client = new LarkupBlogClient({
  apiKey: process.env.LARKUP_API_KEY!,
  studioUrl: process.env.LARKUP_STUDIO_URL!,
});

// Wrap your app (or just the blog section)
export function App() {
  return (
    <LarkupProvider client={client}>
      <BlogIndex />
    </LarkupProvider>
  );
}

function BlogIndex() {
  const { data: posts, loading, error } = usePosts();

  if (loading) return <p>Loading…</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <ul>
      {posts?.map((p) => (
        <li key={p.id}>
          <a href={`/blog/${p.slug}`}>{p.title}</a>
        </li>
      ))}
    </ul>
  );
}

API reference

LarkupBlogClient

new LarkupBlogClient(options: LarkupBlogOptions)

| Option | Type | Required | Description | | ----------- | -------- | -------- | --------------------------------------- | | apiKey | string | ✅ | Your Larkup API key | | studioUrl | string | ✅ | Base URL of your Larkup Studio instance |

Methods

| Method | Returns | Description | | ----------------------------------- | ----------------- | ------------------------------------------------------------------ | | getPosts({ preview? }) | Promise<Post[]> | Fetch all published posts. Pass preview: true to include drafts. | | getPostBySlug(slug, { preview? }) | Promise<Post> | Fetch a single post by its URL slug. |


React bindings (@larkup/blog-sdk/react)

<LarkupProvider client={client}>

Context provider. Wrap any component tree that uses Larkup hooks or components.

useLarkupClient(): LarkupBlogClient

Returns the client from the nearest <LarkupProvider>. Throws if called outside one.

usePosts({ preview? }): AsyncState<Post[]>

Fetches all posts. Re-runs when preview changes.

interface AsyncState<T> {
  data: T | null;
  loading: boolean;
  error: Error | null;
}

usePost(slug, { preview? }): AsyncState<Post>

Fetches a single post by slug. Re-runs when slug or preview changes.

<LarkupArticle>

Renders a full article with header, cover image, prose content, and optional table-of-contents sidebar. By default the layout matches the Larkup Blog Studio aesthetic.

Accepts either a pre-fetched post object or a slug to fetch automatically.

// Auto-fetch by slug (requires LarkupProvider)
<LarkupArticle
  slug="hello-world"
  preview={false}
  prose={true}
  renderLoading={() => <Spinner />}
  renderError={(err) => <p>{err.message}</p>}
/>

// Pass a pre-fetched post (e.g. from getServerSideProps / server component)
<LarkupArticle post={post} prose={true} />
Props

| Prop | Type | Default | Description | | ----------------- | ----------------------------- | -------- | ------------------------------------------------------------------------------ | | slug | string | — | Slug to fetch (mutually exclusive with post) | | post | Post | — | Pre-fetched post object (mutually exclusive with slug) | | preview | boolean | false | Fetch draft version (only valid with slug) | | prose | boolean | true | Inject built-in prose typography + layout styles | | className | string | — | Applied to the outer <article> element | | style | CSSProperties | — | Inline styles for the outer <article> element | | maxWidth | string | "56rem"| Max-width for header, cover, and content sections (--larkup-max-width) | | showHeader | boolean | true | Show the centered header (title, author, meta) | | showAuthor | boolean | true | Show author avatar + name | | showAuthorImage | boolean | true | Show author's profile image (falls back to initial) | | showReadingTime | boolean | true | Show reading-time badge | | showDate | boolean | true | Show publish date | | showCategory | boolean | true | Show category breadcrumb | | showCover | boolean | true | Show cover image section | | showToc | boolean | true | Show table-of-contents sidebar (desktop) + mobile sheet | | classNames | LarkupClassNames | {} | Class overrides for individual sub-elements (see below) | | renderLoading | () => ReactNode | — | Custom loading state | | renderError | (err: Error) => ReactNode | — | Custom error state |

classNames slots

Override any sub-element's class with the classNames prop:

<LarkupArticle
  post={post}
  classNames={{
    article: "my-article",
    header: "my-header",
    title: "text-foreground",
    category: "text-muted",
    authorWrapper: "gap-3",
    authorName: "font-bold",
    metaContainer: "text-muted-foreground",
    metaItem: "gap-2",
    coverSection: "bg-gray-50",
    coverInner: "px-0",
    coverImage: "rounded-2xl",
    bodyLayout: "gap-12",
    content: "prose-lg",
    sidebar: "w-64",
    sidebarSticky: "top-20",
    tocTitle: "text-xs",
  }}
/>

| Slot | Target element | | ---------------- | ------------------------------------------------------- | | article | Outer <article> wrapper | | header | Header wrapper (centered block) | | title | Article <h1> | | category | Category breadcrumb <span> | | authorWrapper | Author row (avatar + name) | | authorName | Author name <span> | | metaContainer | Meta row (reading time, date) | | metaItem | Individual meta item | | coverSection | Cover image outer section | | coverInner | Cover image inner container (respects maxWidth) | | coverImage | Cover <img> element | | bodyLayout | Body flex row (spacer | content | sidebar) | | content | Article content wrapper | | sidebar | Sidebar <aside> | | sidebarSticky | Sidebar sticky container | | tocTitle | TOC heading ("On this page") |

Adjusting layout width

The default max-width is 56rem (matching Larkup Blog Studio). Override it with the maxWidth prop:

<LarkupArticle post={post} maxWidth="72rem" />

Or set the CSS variable directly:

.my-wrapper {
  --larkup-max-width: 72rem;
}

Post type

interface Post {
  id: number;
  title: string;
  slug: string;
  content?: any;
  contentHtml?: string;
  coverImage?: string;
  category?: string;
  authorName?: string;
  authorImage?: string;
  readingTime?: number;
  published: boolean;
  publishedAt?: string;
  updatedAt: string;
  showCover?: boolean;
  coverSize?: "small" | "medium" | "large";
  coverFit?: "cover" | "contain";
  coverZoom?: number;
  coverX?: number;
  coverY?: number;
  showToc?: boolean;
  isMain?: boolean;
}

Draft / preview mode

Both the client methods and hooks accept a preview flag. When true, the SDK appends ?preview=true to the API request so your Studio returns unpublished drafts alongside published content. Gate this flag behind a server-side session check to avoid leaking drafts in production.


Styling prose content

When prose={true} (the default), <LarkupArticle> injects built-in styles that match the Larkup Blog Studio aesthetic out of the box — no extra CSS required.

To customise, override the relevant .larkup-* classes in your own CSS:

/* Widen the layout */
.larkup-layout {
  --larkup-max-width: 72rem;
}

/* Custom prose font */
.larkup-prose {
  font-family: "Georgia", serif;
  font-size: 1.125rem;
}

/* Custom heading colours */
.larkup-prose h2 {
  color: #1d4ed8;
}

To disable built-in styles entirely and provide your own, pass prose={false}:

<LarkupArticle post={post} prose={false} className="my-custom-article" />

Project layout

src/
├── client.ts              # LarkupBlogClient + Post types
├── index.ts               # Main package entry
└── react/
    ├── styles.ts          # Default embedded CSS
    ├── types.ts           # Shared TypeScript types
    ├── toc.tsx            # LarkupToc + LarkupMobileToc
    ├── components.tsx     # LarkupArticle, ArticleView, FetchingArticle
    ├── context.tsx        # LarkupProvider, hooks
    └── index.ts           # React sub-package entry

License

MIT © Larkup