React SDK

Hooks

Headless React hooks for fetching posts, categories, tags, and archives.

For complete control over the UI, use the React hooks to fetch data and build your own components. Import them from @vlozi/blog/react.

usePosts

Fetches a paginated list of blog posts with optional filtering, searching, and sorting.

const {
  data,        // PaginatedResponse<Post> | null
  loading,     // boolean
  error,       // Error | null
  refetch,     // () => Promise<void>
  page,        // current page number
  totalPages,  // total number of pages
  hasNextPage, // boolean
  hasPrevPage, // boolean
} = usePosts({
  page: 1,
  limit: 10,
  category: "tutorials",   // filter by category slug
  tag: "react",            // filter by tag slug
  search: "getting started", // search title & excerpt
  sort: "publishedAt",     // "publishedAt" | "title" | "createdAt"
  order: "desc",           // "asc" | "desc"
});

Parameters

All parameters are optional:

Param Type Default Description
page number 1 Page number for pagination.
limit number 10 Items per page (max 100).
category string Filter by category slug.
tag string Filter by tag slug.
search string Search title and excerpt (case-insensitive).
sort "publishedAt" | "title" | "createdAt" "publishedAt" Field to sort by.
order "asc" | "desc" "desc" Sort direction.

usePost

Fetches a single post by its slug, including full HTML content. Useful for detail pages.

const { data: post, loading, error } = usePost("my-first-post");
 
// post.title, post.content (HTML), post.category, post.tags, etc.

useCategories

Fetches all categories with their published post counts. Categories are returned in a flat array (not paginated).

const { data: categories, loading, error, refetch } = useCategories();
 
// categories = [{ name: "Tutorials", slug: "tutorials", postCount: 12 }, ...]

useTags

Fetches all tags with their published post counts. Tags are returned in a flat array (not paginated).

const { data: tags, loading, error, refetch } = useTags();
 
// tags = [{ name: "React", slug: "react", postCount: 8 }, ...]

useArchive

Fetches every published post, grouped by year and month. Paginates through the post list client-side — suitable for blogs up to ~1000 posts.

const { data, loading, error, refetch } = useArchive({ maxPosts: 1000 });
 
// data = {
//   groups: [
//     { year: 2026, month: 4, monthName: "April", posts: [...], count: 5 },
//     { year: 2026, month: 3, monthName: "March", posts: [...], count: 8 },
//     ...
//   ],
//   totalPosts: 137,
// }

Parameters

Param Type Default Description
maxPosts number 1000 Upper bound on posts to include across all pages.

useRelatedPosts

Finds posts related to a given slug by category/tag overlap scoring. Perfect for a "You might also like…" block at the bottom of a post page.

const { data: related, loading, error } = useRelatedPosts(post.slug, {
  limit: 3,   // how many related posts to return
  pool: 100,  // how many recent posts to score against
});
 
// related = Post[] — already sorted by relevance.

Parameters

Param Type Default Description
slug string required Slug of the current post. Scoring is done relative to this post's category + tags.
options.limit number 3 Max related posts to return.
options.pool number 100 Pool of recent posts to score against.

useNeighbors

Returns the previous (older) and next (newer) posts adjacent to a given slug in publication order. Either side may be null when the current post is the oldest or newest.

const { data, loading } = useNeighbors(post.slug, { pool: 500 });
 
// data = {
//   previous: { slug, title, publishedAt, ... } | null,
//   next:     { slug, title, publishedAt, ... } | null,
// }

Parameters

Param Type Default Description
slug string required Slug of the current post.
options.pool number 500 How many recent posts to fetch when searching for neighbors.

useVlozi (advanced)

Returns the underlying VloziClient from context. Use this when the built-in hooks don't cover your use case — e.g. calling a client method imperatively, prefetching on hover, or wiring the client into your own data layer.

import { useVlozi } from "@vlozi/blog/react";
 
function PrefetchOnHover({ slug, children }: { slug: string; children: ReactNode }) {
  const client = useVlozi();
  return (
    <div onMouseEnter={() => client.blog.get(slug)}>
      {children}
    </div>
  );
}

Must be called inside a <VloziProvider> subtree — throws a clear error otherwise.

Combined example: blog with filters

A practical example combining multiple hooks to build a filterable blog page:

"use client";
 
import { useState } from "react";
import { usePosts, useCategories, useTags } from "@vlozi/blog/react";
 
export default function BlogPage() {
  const [category, setCategory] = useState<string>();
  const [tag, setTag] = useState<string>();
  const [search, setSearch] = useState("");
 
  const { data: categories } = useCategories();
  const { data: tags } = useTags();
  const { data, loading } = usePosts({ category, tag, search: search || undefined });
 
  return (
    <div>
      <input
        placeholder="Search posts…"
        value={search}
        onChange={(e) => setSearch(e.target.value)}
      />
 
      {/* Category pills */}
      {categories?.map((cat) => (
        <button key={cat.slug} onClick={() => setCategory(cat.slug)}>
          {cat.name} ({cat.postCount})
        </button>
      ))}
 
      {/* Tag pills */}
      {tags?.map((t) => (
        <button key={t.slug} onClick={() => setTag(t.slug)}>
          #{t.name}
        </button>
      ))}
 
      {/* Post list */}
      {loading ? <p>Loading…</p> : (
        data?.data.map((post) => (
          <article key={post.slug}>
            <h2>{post.title}</h2>
            {post.category && <span>{post.category.name}</span>}
            {post.tags?.map((t) => <span key={t.slug}>#{t.name}</span>)}
            <p>{post.excerpt}</p>
          </article>
        ))
      )}
    </div>
  );
}

Headless usage

These hooks are "headless" — they don't render any markup. They simply provide the data and state management, giving you the freedom to design the UI exactly how you want it.

Blog · React SDKEdit on GitHub