Blog
Retrieve and render blog content using the Quotient SDK
Overview
The blog API lets you retrieve published blog posts, authors, and tags from your Quotient blog. All endpoints are read-only and work with both public and private API keys.
| Endpoint | API Key | Server SDK | Client SDK |
|---|---|---|---|
| Get blog post | public or private | blog.get() | blog.get() |
| List blog posts | public or private | blog.list() | blog.list() |
| List authors | public or private | blog.listAuthors() | blog.listAuthors() |
Get a Blog Post
blog.get(options)
GET /api/v0/blog/{slug}Auth: public or private key · scope:
BLOG_READ
| Param | Type | Required | Description |
|---|---|---|---|
slug | string | Yes | Blog post slug |
rawHtml | boolean | No | Return pre-rendered HTML |
const { blog } = await client.blog.get({
slug: "my-first-post",
rawHtml: true,
});
Returns:
{
blog: {
id: string;
title: string;
slug: string;
content: JSON;
dominantImageUrl?: string | null;
publishDate: Date | null;
rawHtml?: string | null;
authors: {
id: string;
name: string;
emailAddress?: string | null;
avatarUrl?: string | null;
}[];
metaDescription: string | null;
tags: {
id: string;
name: string;
description?: string | null;
}[];
};
}
List Blog Posts
blog.list(options?)
GET /api/v0/blog/listAuth: public or private key · scope:
BLOG_READ
| Param | Type | Required | Description |
|---|---|---|---|
authorIds | string[] | No | Filter by author IDs |
tagIds | string[] | No | Filter by tag IDs |
statuses | ("DRAFT" | "SCHEDULED" | "PUBLISHED")[] | No | Filter by status |
search | string | No | Search by title |
page | number | No | Page number (default 1) |
limit | number | No | Results per page (default 50) |
const { blogs, pageData } = await client.blog.list({
statuses: ["PUBLISHED"],
search: "marketing",
page: 1,
limit: 20,
});
Returns:
{
blogs: {
id: string;
title: string;
slug: string;
content: JSON;
dominantImageUrl?: string | null;
publishDate: Date | null;
authors: {
id: string;
name: string;
emailAddress?: string | null;
avatarUrl?: string | null;
}[];
metaDescription: string | null;
tags: {
id: string;
name: string;
description?: string | null;
}[];
}[];
pageData: {
page: number;
limit: number;
total: number;
isNextPageAvailable: boolean;
};
}
List Authors
blog.listAuthors(options?)
GET /api/v0/blog/authorsAuth: public or private key · scope:
BLOG_READ
| Param | Type | Required | Description |
|---|---|---|---|
search | string | No | Filter by name |
page | number | No | Page number (default 1) |
limit | number | No | Results per page (default 10) |
const { authors, pageData } = await client.blog.listAuthors({
search: "jane",
page: 1,
limit: 10,
});
Returns:
{
authors: {
id: string;
name: string;
emailAddress?: string | null;
avatarUrl?: string | null;
}[];
pageData: {
page: number;
limit: number;
total: number;
isNextPageAvailable: boolean;
};
}
Rendering
The @quotientjs/react package provides a <Blog /> component that renders
blog content into HTML. It takes the content JSON from blog.get() and
produces unstyled semantic HTML elements.
Basic Usage
import { Blog } from "@quotientjs/react";
import { QuotientServer } from "@quotientjs/server";
// app/blog/[slug]/page.tsx
export default async function BlogPost({
params,
}: {
params: { slug: string };
}) {
const client = new QuotientServer({
privateKey: process.env.QUOTIENT_PRIVATE_KEY!,
});
const { blog } = await client.blog.get({ slug: params.slug });
return <Blog content={blog.content} />;
}
Styling
The <Blog /> component renders unstyled HTML with default class names on
each element (quotient-p, quotient-h1, quotient-a, etc.). There are
three ways to style blog content:
1. Target default classes in CSS
.quotient-p { line-height: 1.75; }
.quotient-h2 { margin-top: 2rem; }
.quotient-a { color: blue; text-decoration: underline; }
2. Override classes via elementClassName
<Blog
content={blog.content}
elementClassName={{
p: "text-base leading-7",
h2: "text-2xl font-bold mt-8",
a: "text-blue-600 underline",
}}
/>
3. Tailwind Typography plugin
<Blog content={blog.content} className="prose prose-lg" />
Available Class Targets
| Element | Default Class |
|---|---|
<strong> | quotient-strong |
<em> | quotient-em |
<u> | quotient-u |
<a> | quotient-a |
<p> | quotient-p |
<h1> – <h6> | quotient-h1 – quotient-h6 |
<ul> | quotient-ul |
<ol> | quotient-ol |
<li> | quotient-li |
<img> | quotient-image |
| assets | quotient-asset |
Client-Side Fetching
Within client components inside a <QuotientProvider>, use the useBlogs
hook for client-side blog fetching:
// app/blog/blog-list.tsx
"use client";
import { BlogStatus, useBlogs } from "@quotientjs/react";
export default function BlogList({
page,
limit,
statuses,
}: {
page: number;
limit: number;
statuses: BlogStatus[];
}) {
const { data, isLoading } = useBlogs({
page,
limit,
statuses,
});
const { blogs, pageData } = data;
return (
{/* Render blog list according to your own specs */}
);
}
// app/blog/page.tsx
"use client";
import { QuotientProvider, BlogStatus } from "@quotientjs/react";
const PAGE_SIZE = 50;
export default function Blogs() {
const [page, setPage] = useState<number>(1);
const [statuses, setStatuses] = useState<BlogStatus[]>([]);
return (
<QuotientProvider clientOptions={clientOptions}>
<BlogList page={page} limit={PAGE_SIZE} statuses={statuses} />
</QuotientProvider>
);
}
You can also fetch blogs using the client directly via useQuotient:
// app/blog/blog-list.tsx
import { QuotientProvider, BlogStatus, useQuotient } from "@quotientjs/react";
import { useEffect, useState } from "react";
export default function BlogList({
page,
limit,
statuses,
}: {
page: number;
limit: number;
statuses: BlogStatus[];
}) {
const { client } = useQuotient();
const [blogs, setBlogs] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [hasNextPage, setHasNextPage] = useState(false);
useEffect(() => {
async function fetchBlogs() {
try {
setIsLoading(true);
setError(null);
const response = await client.blog.list({
page,
limit,
statuses,
});
setBlogs(response.blogs);
setHasNextPage(response.pageData.isNextPageAvailable);
} catch (err) {
setError(err instanceof Error ? err.message : "An error occurred");
} finally {
setIsLoading(false);
}
}
fetchBlogs();
}, [page, client]);
return (
{/* Render blog list according to your own specs */}
);
}
// app/blog/page.tsx
"use client";
import { QuotientProvider } from "@quotientjs/react";
const PAGE_SIZE = 50;
export default function Blogs() {
const [page, setPage] = useState<number>(1);
const [statuses, setStatuses] = useState<BlogStatus[]>([]);
return (
<QuotientProvider clientOptions={clientOptions}>
<BlogList page={page} limit={PAGE_SIZE} statuses={statuses} />
</QuotientProvider>
);
}