Publishing integrations

Next.js blog

For sites with no CMS at all. Instead of publishing into someone else’s database, your app reads the articles from ours at build time and revalidates on a schedule.

Install#

npm install rankli-nextjs-blog

Next.js 14 or 15 with the App Router. React 18 or 19.

Create the integration and get a key#

  1. In Rankli, open Integrations → New integration → Next.js blog.
  2. Name it after the site it serves.
  3. Copy the API key from the confirmation. It is shown once.
  4. Put it in your environment:
.env.local
RANKLI_BLOG_API_KEY=rk_blog_your_key_here

This key is read on the server only — the package never exposes it to the browser, and it must not be prefixed NEXT_PUBLIC_. Add it to your hosting provider’s environment variables as well as your local .env.local.

Add the routes#

Four files, all of them thin wrappers around the package:

app/blog/page.tsx
import { BlogIndex } from 'rankli-nextjs-blog';

export const revalidate = 86400; // once a day

export default function Page() {
  return <BlogIndex title="Blog" />;
}
app/blog/[slug]/page.tsx
import { BlogPost, generateBlogParams, blogMetadata } from 'rankli-nextjs-blog';

export const revalidate = 86400;
export const generateStaticParams = generateBlogParams;
export const generateMetadata = blogMetadata;

export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params;
  return <BlogPost slug={slug} />;
}

Two more are optional: app/blog/tag/[slug]/page.tsx for tag archives and app/blog/sitemap.xml/route.ts for a blog-only sitemap. Both are one-liners over the same exports, and both are in the package’s starter folder.

Styling it#

The components render semantic HTML with no styles of their own and no CSS import, so they inherit your site. Every element carries a rk- class if you want to target them:

.rk-post h2 { font-size: 1.5rem; margin-top: 2.5rem; }
.rk-post img { border-radius: 12px; }
.rk-index-card { border: 1px solid #eee; }

If you would rather build your own markup, getPosts() and getPost(slug) return the data and nothing else.

Caching and freshness#

With revalidate = 86400 your pages are static and refresh once a day, which means one request to us per page per day and a blog that survives us being down.

To publish faster than that, add a revalidation route and give Rankli its URL as a webhook target — the article then appears within seconds of being written:

app/api/revalidate/route.ts
import { revalidatePath } from 'next/cache';

export async function POST(req: Request) {
  if (req.headers.get('authorization') !== `Bearer ${process.env.RANKLI_REVALIDATE_TOKEN}`) {
    return new Response('no', { status: 401 });
  }
  revalidatePath('/blog');
  revalidatePath('/blog/[slug]', 'page');
  return Response.json({ revalidated: true });
}

When it does not work#

The index is empty

No articles have been published to this integration yet. An article publishes to the connections attached to its product; check the product’s integrations list.

401 at build time

The environment variable is missing where the build runs. Vercel and Netlify need it set in the project settings as well as in your local file.

Changes do not appear

revalidate is doing its job. Either wait for the window, redeploy, or add the revalidation route above.