Next.js Integration
Next.js Integration
The cleanest way to use the SDK in Next.js App Router is from Server Components: the API key stays on the server, the body HTML renders without a client bundle, and you get fetch caching for free.
Keep the key in env
Use a server-only variable (no NEXT_PUBLIC_ prefix) so the key is never shipped to the browser:
RANKHIKER_API_KEY=rh_export_...
Server code does not strictly need the www baseUrl, but it is the safest default, so the examples set it.
Shared client
// lib/rankhiker.ts
import { createClient } from '@rankhiker/sdk';
export const rh = createClient({
apiKey: process.env.RANKHIKER_API_KEY!,
baseUrl: 'https://rankhiker.com',
requestInit: { next: { revalidate: 300 } }, // ISR: refresh every 5 minutes
});
To opt a client out of caching entirely, use requestInit: { cache: 'no-store' } instead.
Blog index: app/blog/page.tsx
import Link from 'next/link';
import { rh } from '@/lib/rankhiker';
export default async function BlogPage() {
const posts = await rh.listArticles({ limit: 20 });
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={/blog/${post.slug}}>{post.title}</Link>
</li>
))}
</ul>
);
}
Article page: app/blog/[slug]/page.tsx
getArticleBySlug returns null on a 404, which pairs naturally with Next.js notFound().
import { notFound } from 'next/navigation';
import type { Metadata } from 'next';
import { rh } from '@/lib/rankhiker';
export async function generateStaticParams() {
const posts = await rh.listArticles({ limit: 1000 });
return posts.map((post) => ({ slug: post.slug }));
}
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}): Promise<Metadata> {
const { slug } = await params;
const post = await rh.getArticleBySlug(slug);
if (!post) return {};
return {
title: post.title,
description: post.metaDescription || post.excerpt,
openGraph: post.featuredImage ? { images: [post.featuredImage] } : undefined,
};
}
export default async function ArticlePage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const post = await rh.getArticleBySlug(slug);
if (!post) notFound();
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
);
}
generateStaticParams pre-renders each article at build time, and generateMetadata fills in per-article SEO tags. The requestInit: { next: { revalidate } } on the shared client controls how often the data refreshes.
When to use the React client adapter
Server Components cannot use hooks. Reach for @rankhiker/sdk/react (the provider, useArticles, useArticle, , ) when you need client-side behavior: live filtering, infinite scroll, or rendering inside an existing client component. Those pieces are 'use client', so they run in the browser and you should pass a NEXT_PUBLIC_ key (the default apex baseUrl works in the browser as-is). For static or server-rendered blogs, the Server Component approach above is simpler and keeps the key private.
Was this article helpful?