Skip to main content
This is the source of truth for integrating a RankBuddy-powered blog into a Next.js App Router app. AI coding agents should follow every required section below and substitute project-specific values from the user’s prompt (BLOG_PATH, SITE_URL, PROJECT_NAME).

SDK on npm

@rankbuddy/sdk — headless client, ISR helpers, React HTML renderer.

Article fields

Full reference for RankBuddyArticle and RankBuddyCluster.

What you are building

1

Routes

  • {BLOG_PATH} — article index (listing + topic strip)
  • {BLOG_PATH}/[slug] — article detail
  • {BLOG_PATH}/topic/[clusterSlug] — topic hub (recommended)
  • {BLOG_PATH}/layout.tsx — passthrough layout for cache invalidation
2

Data

Server Components fetch published content via @rankbuddy/sdk ISR helpers (not raw fetch to RankBuddy).
3

SEO

Per-page generateMetadata using article seo.* fields, JSON-LD BlogPosting, and robots when seo.noIndex.
4

Styling

Clean Tailwind layout: readable prose for HTML body, card grid on index, violet accent #7C3AED, responsive and accessible.
Replace {BLOG_PATH} with the user’s path (e.g. /blog). Replace {SITE_URL} with their production origin (e.g. https://acme.com).

1. Install dependencies

npm install @rankbuddy/sdk@^0.3.5
Requires Next.js ≥ 14.2 and Node 18+. React is optional unless you use @rankbuddy/sdk/react.

2. Environment variables

Add to .env.local and your hosting provider:
RANKBUDDY_API_KEY=rb_...
Server-only. Never prefix with NEXT_PUBLIC_. Never import the client in Client Components with a secret key.

3. SDK client (server)

Create src/lib/rankbuddy.ts:
import { createRankBuddyClient } from "@rankbuddy/sdk";

function getRankBuddyApiKey() {
  const apiKey = process.env.RANKBUDDY_API_KEY;
  if (!apiKey) {
    throw new Error("RANKBUDDY_API_KEY is required to render RankBuddy blog pages.");
  }
  return apiKey;
}

const siteUrl = process.env.NEXT_PUBLIC_SITE_URL ?? "https://example.com";
const blogPath = process.env.RANKBUDDY_BLOG_PATH ?? "/blog";

export const rankBuddy = createRankBuddyClient({
  apiKey: getRankBuddyApiKey(),
  siteUrl,
  blogPath,
});
Set NEXT_PUBLIC_SITE_URL and RANKBUDDY_BLOG_PATH to match the user’s project, or hardcode values from the onboarding prompt.

4. Route map (App Router)

For {BLOG_PATH} = /blog:
src/app/blog/
  layout.tsx
  page.tsx
  [slug]/page.tsx
  topic/[clusterSlug]/page.tsx
Use a route group if needed (src/app/(marketing)/blog/...) — URLs stay /blog/....

Blog layout

src/app/blog/layout.tsx:
export default function BlogLayout({ children }: { children: React.ReactNode }) {
  return children;
}

5. Use SDK ISR helpers (required)

On Next.js, do not call rankBuddy.articles.getBySlug directly in Server Components. Use cached helpers so revalidateTag works:
PageSDK helperCache tag
IndexgetArticlesList, getClustersListblogListingCacheTag()
ArticlegetArticleBySlugblogArticleCacheTag(slug)
Topic hubgetClusterBySlug, getClusterArticlesblogTopicListingCacheTag(clusterSlug)
Related blockgetRelatedArticlesblogRelatedCacheTag(slug) + blogRelatedAllCacheTag()
generateStaticParamsgetArticlesList, getClusterSlugslisting tag
import {
  getArticleBySlug,
  getArticlesList,
  getClustersList,
  getClusterArticles,
  getClusterBySlug,
  getRelatedArticles,
  getClusterSlugs,
} from "@rankbuddy/sdk";
import { rankBuddy } from "@/lib/rankbuddy";

const post = await getArticleBySlug(rankBuddy, slug);
const posts = await getArticlesList(rankBuddy, { limit: 24 });
See article fields for every property to render.

6. Blog index ({BLOG_PATH}/page.tsx)

Required behavior:
  • export const revalidate = 3600 for first launch (see Caching); use false only with a revalidation strategy.
  • Fetch getArticlesList(rankBuddy, { limit: 24 }) and getClustersList(rankBuddy) in parallel.
  • Render an empty state when there are no posts.
  • Each card shows: coverImage, title, excerpt || description, publishedAt, readingTime, link to {BLOG_PATH}/{slug}.
  • Topic strip links to {BLOG_PATH}/topic/{cluster.slug}.
  • Static generateMetadata for the index (title, description, canonical, Open Graph).
Minimal styling: max-width container, responsive grid or stacked cards, subtle borders, hover states, sufficient color contrast.

7. Article page ({BLOG_PATH}/[slug]/page.tsx)

Required behavior:
  • generateStaticParams — paginate getArticlesList (e.g. limit: 100) to prebuild slugs; wrap in try/catch so missing API key at build time does not fail CI.
  • generateMetadata — map from article fields:
export async function generateMetadata({ params }): Promise<Metadata> {
  const { slug } = await params;
  const post = await getArticleBySlug(rankBuddy, slug);
  if (!post) return {};

  const title = post.seo?.title ?? post.title;
  const description = post.seo?.description ?? post.description;
  const image = post.seo?.ogImage ?? post.coverImage;
  const canonical = post.seo?.canonicalUrl ?? `${siteUrl}${blogPath}/${post.slug}`;

  return {
    title,
    description,
    keywords: post.seo?.keywords,
    alternates: { canonical },
    openGraph: {
      title,
      description,
      type: "article",
      url: canonical,
      images: image ? [{ url: image }] : undefined,
      publishedTime: post.publishedAt,
      modifiedTime: post.updatedAt,
    },
    twitter: {
      card: "summary_large_image",
      title,
      description,
      images: image ? [image] : undefined,
    },
    robots: post.seo?.noIndex ? { index: false, follow: false } : undefined,
  };
}
Alternatively: await rankBuddy.seo.generateMetadata(slug) returns a Next-compatible metadata object. Page UI must include:
ElementSource
<h1>post.title
Cover heropost.coverImage
BylinepublishedAt, readingTime
Topic linkpost.primaryCluster → topic page
Tagspost.tags or post.seo.keywords
Bodypost.content.html (see below)
Related postscluster articles or getRelatedArticles(rankBuddy, slug)
Back linkto {BLOG_PATH}
Article body — choose one:
// Option A (recommended): sanitized React helper
import { RenderContent } from "@rankbuddy/sdk/react";
<RenderContent content={post.content} />

// Option B: Tailwind Typography prose + trusted server HTML
<div
  className="prose prose-slate max-w-none dark:prose-invert"
  dangerouslySetInnerHTML={{ __html: post.content.html }}
/>
Hide duplicate <h1> in HTML if the page already renders post.title (e.g. [&>h1:first-child]:hidden). JSON-LD (recommended): BlogPosting + BreadcrumbList scripts with headline, description, image, datePublished, dateModified, url. Call notFound() when getArticleBySlug returns null.

8. Topic hub ({BLOG_PATH}/topic/[clusterSlug]/page.tsx)

Required behavior:
  • generateStaticParams from getClusterSlugs(rankBuddy).
  • getClusterBySlug(rankBuddy, clusterSlug)notFound() if null.
  • getClusterArticles(rankBuddy, clusterSlug, { limit: 24 }) for the grid.
  • Metadata from cluster.name, cluster.notes, cluster.pillarKeyword, cluster.keywords.
  • Same card component as the index.

9. Caching

export const revalidate = 3600; // refresh at most once per hour
Published articles appear on your site within the revalidation window without extra infrastructure.

Production (on-demand)

export const revalidate = false;
Pair with revalidateTag + revalidatePath after publishes. Tags from @rankbuddy/sdk:
  • blogArticleCacheTag(slug)
  • blogListingCacheTag()
  • blogRelatedAllCacheTag()
  • blogRelatedCacheTag(slug)
  • blogTopicListingCacheTag(clusterSlug)
import { revalidatePath, revalidateTag } from "next/cache";
import {
  blogArticleCacheTag,
  blogListingCacheTag,
  blogRelatedAllCacheTag,
} from "@rankbuddy/sdk";

revalidateTag(blogArticleCacheTag(slug));
revalidateTag(blogListingCacheTag());
revalidateTag(blogRelatedAllCacheTag());
revalidatePath("/blog", "layout");
revalidatePath("/sitemap.xml");

10. Base styling spec

Agents should ship production-ready pages without a design system dependency:
  • Font: system sans or project default
  • Accent: use the primary brand color from the onboarding prompt (Primary brand color: #RRGGBB). If none was detected from the site, RankBuddy defaults to #0F172A (Tailwind slate-900)
  • Index: card layout with 16:9 cover thumbnails, title, 2-line excerpt
  • Article: max-width ~48rem prose column, optional sidebar
  • Dark mode: support if the host app uses dark: classes
  • Images: alt from title, lazy loading, object-cover on cards
  • Accessibility: semantic <article>, <time dateTime>, focus states on links

11. Agent checklist

Before finishing, verify:
All three routes exist under the user’s {BLOG_PATH}.
RANKBUDDY_API_KEY is read only on the server.
SDK ISR helpers used instead of raw client list/get calls.
Every RankBuddyArticle SEO field in article fields is mapped in generateMetadata.
seo.noIndex sets robots correctly.
generateStaticParams tolerates missing API key at build time.
Empty index state explains that posts come from RankBuddy after publish.
User can follow Publish your first article after deploy.

Next step

Deploy, then publish from RankBuddy — see Publish your first article.