Content Modules

Working with the Blog module

Updated 93 views

Everything you need for a blog: a filterable index, detail pages by slug, tags, and reactions.

Listing posts

TypeScript
const response = await kookee.blog.list({
  page: 1,
  limit: 10,
  tags: ['engineering', 'product'],
  search: 'typescript',
  locale: 'en',
});

response.data;       // BlogEntryListItem[]
response.total;      // total matching posts
response.totalPages; // page count

All parameters are optional. limit defaults to 10 and maxes out at 100.

Multiple tags match posts carrying any of them, and search runs a full-text match over posts. Combine them freely — a tag filter plus a search query behaves the way you would expect.

Pagination

total and totalPages are what you build controls from:

TypeScript
const { data, page, totalPages } = await kookee.blog.list({ page: 2, limit: 10 });

const hasNext = page < totalPages;
const hasPrevious = page > 1;

Tags

Fetch every tag with its post count — useful for a filter sidebar that shows how much is behind each one:

TypeScript
const tags = await kookee.blog.getTags();
// [{ name: 'engineering', count: 12 }, ...]

A single post

TypeScript
const post = await kookee.blog.getBySlug('hello-world');

post.title;
post.contentHtml; // full HTML body — list items do not include this

There is also getById(id) when you are holding an ID rather than a slug. Prefer slugs for public URLs.

If the slug does not exist, the SDK throws a KookeeApiError with status 404. In a framework like Next.js, catch it and return your not-found page.

Reactions

Posts support emoji reactions. Five types are built in — fire, heart, rocket, eyes and mindblown — and the API also accepts a custom type matching [a-z0-9_]{1,50}:

TypeScript
await kookee.blog.react(post.id, { reactionType: 'fire', action: 'add' });
await kookee.blog.react(post.id, { reactionType: 'fire', action: 'remove' });

Reactions work with your normal API key, so this can run directly in the browser. Track which reactions the current user has added in local storage so your UI can render the toggled state and send the right action. In React, Reactions from @kookee/react/entry/client does this for you.

Translations

If your project has more than one locale:

TypeScript
const posts = await kookee.blog.list({ locale: 'de', fallback: true });

const translations = await kookee.blog.getTranslationsBySlug('hello-world');

fallback defaults to true, which returns the default-locale version when no German translation exists. Set it to false when you would rather show nothing than the wrong language.

getTranslationsBySlug() gives you every available language for one post — exactly what a language switcher needs.

A minimal index page

tsx
export default async function BlogPage() {
  const { data: posts } = await kookee.blog.list({ limit: 10 });

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

Pair it with a detail route that calls getBySlug() and renders contentHtml. See Rendering content and styling code blocks for the styling side.

Ready-made React components

In a React app, @kookee/react/blog renders post cards, the tag filter with counts and the post page on the server. @kookee/react/entry adds pagination and a table of contents, and @kookee/react/entry/client adds reactions and a table of contents that follows the reader. See Build a blog with the React components.

Was this article helpful?