Docs
React Components
Documentation

React Components

Pre-built React components for rendering blog posts and listings.

React Components

The @autoblogwriter/sdk/react entry point provides ready-to-use React components for rendering blog content. All components work with both server and client components.

import { BlogFaq, BlogPost, BlogPostList, Markdown, RelatedPosts, Branding, AutoBlogPost, AutoBlogPostList } from "@autoblogwriter/sdk/react";

<BlogPostList>

Renders a list of blog post cards with titles, dates, and excerpts.

Basic Usage

import Link from "next/link";
import { fetchBlogPosts } from "@autoblogwriter/sdk/next";
import { BlogPostList } from "@autoblogwriter/sdk/react";
 
export default async function BlogPage() {
  const { posts } = await fetchBlogPosts();
  return <BlogPostList posts={posts} linkComponent={Link} />;
}

Props

PropTypeDefaultDescription
postsBlogPost[]Array of posts to display
titlestring"Latest posts"Section heading. Pass empty string to hide.
routePrefixstring"/blog"URL prefix for post links (e.g., /blog/my-post)
linkComponentReact.ComponentType<a>Link component (use Next.js Link for client navigation)
classNamestring"ba-listing"CSS class on the wrapper <section>
theme"light" | "dark"Applies a theme via data-ba-theme on the wrapper.
layout"card" | "list""card"Layout style for the list.
postsPerPagenumberPage size for controlled pagination.
pagenumber1Current page (1-based) for controlled pagination.
onPageChange(page: number) => voidHandler invoked by pagination buttons.
paginationHref(page: number) => stringRender pagination links instead of buttons (server-safe).
showPaginationbooleantrue (when postsPerPage is set)Whether to render pagination UI.
imageComponentReact.ComponentTypeCustom image component for card images (e.g. Next.js Image).
imagePropsRecord<string, unknown>Extra props forwarded to the card image component.
renderCard(post, href) => ReactNodeCustom card renderer (overrides default card)
brandingBrandingInfoinferred from postsExplicit branding tier info. Inferred from posts[0].branding if not set.
showBrandingbooleantrueShow/hide the "Powered by" footer. On free plans this is forced true.
brandingLinkComponentReact.ComponentTypelinkComponentLink component used specifically for the branding link.

Pass Next.js Link for client-side navigation:

import Link from "next/link";
<BlogPostList posts={posts} linkComponent={Link} />

Custom Route Prefix

<BlogPostList posts={posts} routePrefix="/articles" />
// Links will be /articles/my-post instead of /blog/my-post

Theme

<BlogPostList posts={posts} theme="light" />
<BlogPostList posts={posts} theme="dark" />

Card vs List Layout

<BlogPostList posts={posts} layout="card" />
<BlogPostList posts={posts} layout="list" />

Image Component

Pass a custom image component (e.g., Next.js Image) for optimized card images:

import Image from "next/image";
 
<BlogPostList
  posts={posts}
  imageComponent={Image}
  imageProps={{ width: 1200, height: 675 }}
/>

Controlled Pagination (Client-Side)

Use onPageChange for client-side pagination with React state:

"use client";
import { useState } from "react";
import { BlogPostList } from "@autoblogwriter/sdk/react";
 
export function BlogList({ posts }) {
  const [page, setPage] = useState(1);
 
  return (
    <BlogPostList
      posts={posts}
      postsPerPage={6}
      page={page}
      onPageChange={setPage}
    />
  );
}

Server-Friendly Pagination

Use paginationHref for server-side rendering without client state:

import Link from "next/link";
import { fetchBlogPosts } from "@autoblogwriter/sdk/next";
import { BlogPostList } from "@autoblogwriter/sdk/react";
 
type Props = {
  searchParams?: { page?: string };
};
 
export default async function BlogPage({ searchParams }: Props) {
  const { posts } = await fetchBlogPosts();
  const page = Math.max(1, Number(searchParams?.page ?? "1"));
 
  return (
    <BlogPostList
      posts={posts}
      linkComponent={Link}
      layout="card"
      postsPerPage={6}
      page={page}
      paginationHref={(nextPage) => `/blog?page=${nextPage}`}
    />
  );
}

Custom Card Rendering

Override the default card layout with renderCard:

<BlogPostList
  posts={posts}
  linkComponent={Link}
  renderCard={(post, href) => (
    <Link href={href} className="custom-card">
      <h3>{post.title}</h3>
      <p>{post.excerpt}</p>
      {post.metadata?.readingTimeMinutes && (
        <span>{post.metadata.readingTimeMinutes} min read</span>
      )}
    </Link>
  )}
/>

Default HTML Structure

<section class="ba-listing">
  <h1 class="ba-listing-title">Latest posts</h1>
  <div class="ba-posts">
    <article class="ba-post-card">
      <h2 class="ba-post-card-title">
        <a href="/blog/my-post" class="ba-post-card-link">My Post</a>
      </h2>
      <p class="ba-post-card-meta">
        <time datetime="2025-01-15T10:00:00.000Z">January 15, 2025</time>
      </p>
      <div class="ba-post-card-excerpt">
        <!-- Rendered excerpt markdown -->
      </div>
    </article>
    <!-- More cards... -->
  </div>
</section>

<BlogPost>

Renders a full blog post article with title, date, reading time, and markdown content.

Basic Usage

import { fetchBlogPost } from "@autoblogwriter/sdk/next";
import { BlogPost } from "@autoblogwriter/sdk/react";
 
export default async function PostPage({ params }: Props) {
  const { slug } = await params;
  const post = await fetchBlogPost(slug);
  return <BlogPost post={post} />;
}

Props

PropTypeDefaultDescription
postBlogPostThe post to render
showTitlebooleantrueShow the post title as an <h1>
showDatebooleantrueShow publication date
showFaqbooleantrueRender FAQ after content when post.faq exists.
showRelatedPostsbooleantrueRender related posts when post.relatedPosts exists.
classNamestring"ba-post"CSS class on the wrapper <article>
theme"light" | "dark"Applies a theme via data-ba-theme on the wrapper.
showHeroImagebooleanfalseRender post.images?.hero?.url under the title when available.
heroImageAltstringpost.titleOptional alt text for the hero image.
linkComponentReact.ComponentTypeCustom link component for related posts (e.g. Next.js Link).
imageComponentReact.ComponentTypeCustom image component for the hero image (e.g. Next.js Image).
imagePropsRecord<string, unknown>Extra props forwarded to the hero image component.
renderContent(content: string) => ReactNodeCustom content renderer (overrides default Markdown)
renderFaq(faq, post) => ReactNodeOverride FAQ rendering, or return null to hide it.
renderRelatedPosts(posts, post) => ReactNodeOverride related posts rendering, or return null to hide it.
brandingBrandingInfofrom APIExplicit branding tier info. Overrides post.branding.
showBrandingbooleantrueShow/hide the "Powered by" footer. On free plans this is forced true.
brandingLinkComponentReact.ComponentTypelinkComponentLink component used specifically for the branding link.

Hero Image

Display a hero image from post.images.hero:

<BlogPost post={post} showHeroImage />

With a custom image component (e.g., Next.js Image):

import Image from "next/image";
 
<BlogPost
  post={post}
  showHeroImage
  imageComponent={Image}
  imageProps={{ width: 1200, height: 630 }}
/>

Theme

<BlogPost post={post} theme="light" />
<BlogPost post={post} theme="dark" />

Hiding Title and Date

<BlogPost post={post} showTitle={false} showDate={false} />

Custom FAQ Rendering

Override the default FAQ section:

<BlogPost
  post={post}
  renderFaq={(faq, post) => (
    <div className="my-faq">
      <h2>FAQ about {post.title}</h2>
      {faq.items.map((item, i) => (
        <details key={i}>
          <summary>{item.question}</summary>
          <p>{item.answer}</p>
        </details>
      ))}
    </div>
  )}
/>

Override the default related posts section:

<BlogPost
  post={post}
  renderRelatedPosts={(posts, post) => (
    <aside>
      <h3>More like "{post.title}"</h3>
      <ul>
        {posts.map((p) => (
          <li key={p.id}><a href={`/blog/${p.slug}`}>{p.title}</a></li>
        ))}
      </ul>
    </aside>
  )}
/>
import Link from "next/link";
import Image from "next/image";
 
<BlogPost
  post={post}
  showHeroImage
  linkComponent={Link}
  imageComponent={Image}
  imageProps={{ width: 1200, height: 630 }}
/>

Custom Content Rendering

Replace the built-in markdown renderer with your own (e.g., for syntax highlighting with react-markdown or next-mdx-remote):

import ReactMarkdown from "react-markdown";
 
<BlogPost
  post={post}
  renderContent={(content) => (
    <ReactMarkdown className="prose">{content}</ReactMarkdown>
  )}
/>

Default HTML Structure

<article class="ba-post">
  <h1 class="ba-post-title">My Post Title</h1>
  <p class="ba-post-meta">
    <time datetime="2025-01-15T10:00:00.000Z">January 15, 2025</time>
    <span class="ba-post-meta-sep"> · </span>
    <span class="ba-post-reading-time">5 min read</span>
  </p>
  <div class="ba-markdown">
    <!-- Rendered markdown HTML -->
  </div>
</article>

<BlogFaq>

Renders an FAQ section from post FAQ data.

Basic Usage

import { BlogFaq } from "@autoblogwriter/sdk/react";
 
<BlogFaq faq={post.faq!} />

Props

PropTypeDefaultDescription
faqBlogFaq | BlogFaqItem[]FAQ data to render.
titlestring"Frequently Asked Questions"Section heading text.
classNamestring"ba-faq"CSS class on the FAQ section.

Custom Title

<BlogFaq faq={post.faq!} title="Common Questions" />

With BlogPost

The <BlogPost> component automatically renders <BlogFaq> when post.faq exists. Control it with showFaq:

<BlogPost post={post} showFaq={false} />

Renders a related-posts list from post.relatedPosts.

Basic Usage

import { RelatedPosts } from "@autoblogwriter/sdk/react";
 
<RelatedPosts posts={post.relatedPosts ?? []} />

Props

PropTypeDefaultDescription
postsRelatedPostSummary[]Related post summaries to render.
titlestring"Related Posts"Section heading text.
routePrefixstring"/blog"URL prefix for links ({routePrefix}/{slug}).
linkComponentReact.ComponentType<a>Custom link component (e.g. Next.js Link).
classNamestring"ba-related"CSS class on the section wrapper.
renderItem(post, href) => ReactNodeOverride default item rendering.

Custom Title and Route Prefix

<RelatedPosts
  posts={post.relatedPosts ?? []}
  title="Keep reading"
  routePrefix="/articles"
/>
import Link from "next/link";
 
<RelatedPosts posts={post.relatedPosts ?? []} linkComponent={Link} />

Custom Item Rendering

<RelatedPosts
  posts={post.relatedPosts ?? []}
  renderItem={(post, href) => (
    <a href={href} className="my-related-link">
      <strong>{post.title}</strong>
      {post.excerpt && <p>{post.excerpt}</p>}
    </a>
  )}
/>

With BlogPost

The <BlogPost> component automatically renders <RelatedPosts> when post.relatedPosts exists. Control it with showRelatedPosts:

<BlogPost post={post} showRelatedPosts={false} />

<Markdown>

Renders a markdown string to HTML using the built-in renderer.

Basic Usage

import { Markdown } from "@autoblogwriter/sdk/react";
 
<Markdown source="# Hello World\n\nThis is **bold** text." />

Props

PropTypeDefaultDescription
sourcestring | nullMarkdown string to render. Returns null if empty.
classNamestring"ba-markdown"CSS class on the wrapper <div>

Custom Class

<Markdown source={post.content} className="prose dark:prose-invert" />

How It Works

The <Markdown> component uses the built-in renderMarkdownToHtml() function which:

  • Converts headings (# H1 through ###### H6)
  • Renders bold, italic, and inline code
  • Converts links [text](url) with target="_blank" and rel="noreferrer"
  • Converts images ![alt](src) with proper attributes
  • Wraps code blocks in <pre><code>
  • Escapes HTML to prevent XSS attacks
  • Has zero dependencies

If you need more advanced markdown features (tables, footnotes, syntax highlighting), use renderContent on <BlogPost> with a library like react-markdown.

Standalone Markdown Renderer

The renderMarkdownToHtml() function is also available directly:

import { renderMarkdownToHtml } from "@autoblogwriter/sdk";
 
const html = renderMarkdownToHtml("# Hello\n\nWorld");
// "<h1>Hello</h1>\n<p>World</p>"

<Branding>

A standalone "Powered by AutoBlogWriter" attribution component. This is rendered automatically inside <BlogPost> and <BlogPostList> when branding is required, but you can also use it directly.

Basic Usage

import { Branding } from "@autoblogwriter/sdk/react";
 
<Branding />

Props

PropTypeDefaultDescription
classNamestring"ba-branding"CSS class on the wrapper element.
linkComponentReact.ComponentType<a>Custom link component for the attribution link.
hrefstringAutoBlogWriter site URLOverride the attribution link target.
labelstring"AutoBlogWriter"Override the brand name text.
showLabelbooleantrueShow the "Powered by" label alongside the brand name.

Example

import Link from "next/link";
import { Branding } from "@autoblogwriter/sdk/react";
 
<Branding linkComponent={Link} className="my-branding" />

Free plan note: On free-tier workspaces, branding is required and cannot be hidden. Attempting to set showBranding={false} on <BlogPost> or <BlogPostList> will have no effect if the API response indicates brandingRequired: true. Upgrade to Pro+ to remove attribution.


<AutoBlogPost>

An async React Server Component that fetches and renders a single blog post in one JSX call. Requires Next.js App Router (or any environment that supports async components).

Basic Usage

import { AutoBlogPost } from "@autoblogwriter/sdk/react";
 
export default async function PostPage({ params }: Props) {
  const { slug } = await params;
  return <AutoBlogPost slug={slug} />;
}

Props

PropTypeDefaultDescription
slugstring(Required) The post slug to fetch.
postBlogPostOptional pre-fetched post. If provided, skips the fetch.
fetchPost(slug: string) => Promise<BlogPost | null>Custom fetch function. Overrides the default client.
clientAutoBlogWriterClientenv clientCustom client instance to use for fetching.
...BlogPostPropsAll props from <BlogPost> are supported.

Usage Examples

Basic:

import { AutoBlogPost } from "@autoblogwriter/sdk/react";
 
export default async function PostPage({ params }: Props) {
  const { slug } = await params;
  return <AutoBlogPost slug={slug} />;
}

With a custom client:

import { createAutoBlogWriterClient } from "@autoblogwriter/sdk";
import { AutoBlogPost } from "@autoblogwriter/sdk/react";
 
const client = createAutoBlogWriterClient({ apiUrl: "...", apiKey: "..." });
 
export default async function PostPage({ params }: Props) {
  const { slug } = await params;
  return <AutoBlogPost slug={slug} client={client} showHeroImage />;
}

With a custom fetcher:

import { AutoBlogPost } from "@autoblogwriter/sdk/react";
 
async function myFetchPost(slug: string) {
  // your own data-fetching logic
}
 
export default async function PostPage({ params }: Props) {
  const { slug } = await params;
  return <AutoBlogPost slug={slug} fetchPost={myFetchPost} />;
}

With a pre-fetched post (e.g., from parallel data loading):

import { fetchBlogPost } from "@autoblogwriter/sdk/next";
import { AutoBlogPost } from "@autoblogwriter/sdk/react";
 
export default async function PostPage({ params }: Props) {
  const { slug } = await params;
  const post = await fetchBlogPost(slug);
  return <AutoBlogPost slug={slug} post={post} showHeroImage />;
}

<AutoBlogPostList>

An async React Server Component that fetches and renders a list of blog posts in one JSX call.

Basic Usage

import { AutoBlogPostList } from "@autoblogwriter/sdk/react";
 
export default async function BlogPage() {
  return <AutoBlogPostList />;
}

Props

PropTypeDefaultDescription
postsBlogPost[]Optional pre-fetched posts. If provided, skips the fetch.
fetchPosts() => Promise<PostsResponse>Custom fetch function. Overrides the default client.
clientAutoBlogWriterClientenv clientCustom client instance to use for fetching.
limitnumberMaximum number of posts to fetch.
cursorstringPagination cursor for fetching a specific page.
categorystringFilter posts by category slug.
...BlogPostListPropsAll props from <BlogPostList> are supported.

Usage Examples

Basic:

import { AutoBlogPostList } from "@autoblogwriter/sdk/react";
 
export default async function BlogPage() {
  return <AutoBlogPostList />;
}

With limit and category filter:

import { AutoBlogPostList } from "@autoblogwriter/sdk/react";
 
export default async function BlogPage() {
  return (
    <AutoBlogPostList
      limit={6}
      category="engineering"
      layout="card"
    />
  );
}

With pre-fetched posts:

import { fetchBlogPosts } from "@autoblogwriter/sdk/next";
import { AutoBlogPostList } from "@autoblogwriter/sdk/react";
 
export default async function BlogPage() {
  const { posts } = await fetchBlogPosts({ limit: 20 });
  return <AutoBlogPostList posts={posts} layout="list" />;
}

Next Steps

  • Styling — Customize the look of these components with CSS.
  • Next.js Helpers — Fetch data for the components.