Integrations

Next.js — App Router

Recommended for Next.js 13.4+ projects using the app/ directory.

Estimated setup time: 8 minutes

1. Install the Adapter

npm install @blogree/nextjs-adapter

2. Environment Variables

Add these to your .env.local. Find the values in Blogree → Sites → your site → Settings.

BLOGREE_API_KEY=bk_live_xxxxxxxxxxxxxxxxxxxx BLOGREE_API_URL=https://api.blogree.com BLOGREE_WEBHOOK_SECRET=whs_xxxxxxxxxxxxxxxxxxxx

3. Webhook Handler

Create app/api/blogree/route.ts:

import { createWebhookHandler } from '@blogree/nextjs-adapter'; import { revalidatePath } from 'next/cache'; export const POST = createWebhookHandler( { apiKey: process.env.BLOGREE_API_KEY!, apiUrl: process.env.BLOGREE_API_URL!, webhookSecret: process.env.BLOGREE_WEBHOOK_SECRET!, }, async (payload) => { // Revalidate your blog listing and the specific post page revalidatePath('/blog'); revalidatePath(`/blog/${payload.post.slug}`); // Optional: revalidate your homepage if it shows recent posts // revalidatePath('/'); } );

4. Fetch Posts (Blog Listing)

In app/blog/page.tsx:

import { getBlogreePosts } from '@blogree/nextjs-adapter'; import type { BlogreePost } from '@blogree/nextjs-adapter'; export default async function BlogPage() { const posts: BlogreePost[] = await getBlogreePosts({ apiKey: process.env.BLOGREE_API_KEY!, apiUrl: process.env.BLOGREE_API_URL!, limit: 20, // optional, default 50 tags: ['seo'], // optional filter by tag }); return ( <main> <h1>Blog</h1> <ul> {posts.map((post) => ( <li key={post.slug}> <a href={`/blog/${post.slug}`}>{post.title}</a> <p>{post.excerpt}</p> </li> ))} </ul> </main> ); }

5. Individual Post Page

In app/blog/[slug]/page.tsx:

import { getBlogreePost, getBlogreePosts } from '@blogree/nextjs-adapter'; import { notFound } from 'next/navigation'; import type { Metadata } from 'next'; // Generate static paths at build time export async function generateStaticParams() { const posts = await getBlogreePosts({ apiKey: process.env.BLOGREE_API_KEY!, apiUrl: process.env.BLOGREE_API_URL!, }); return posts.map((p) => ({ slug: p.slug })); } // Auto-generate SEO metadata from Blogree post meta export async function generateMetadata( { params }: { params: { slug: string } } ): Promise<Metadata> { const post = await getBlogreePost(params.slug, { apiKey: process.env.BLOGREE_API_KEY!, apiUrl: process.env.BLOGREE_API_URL!, }); if (!post) return { title: 'Post Not Found' }; return { title: post.meta.title, description: post.meta.description, openGraph: { images: [post.meta.og_image], type: 'article', publishedTime: post.published_at, }, }; } export default async function PostPage( { params }: { params: { slug: string } } ) { const post = await getBlogreePost(params.slug, { apiKey: process.env.BLOGREE_API_KEY!, apiUrl: process.env.BLOGREE_API_URL!, }); if (!post) notFound(); return ( <article> <h1>{post.title}</h1> <time>{post.published_at}</time> {/* Render HTML body — sanitize in production */} <div dangerouslySetInnerHTML={{ __html: post.body.html }} /> </article> ); }

6. Set Webhook URL in Dashboard

In Blogree → Sites → your site → Webhook URL, enter:

https://yoursite.com/api/blogree

Click Verify Webhook. Blogree sends a signed test payload and expects a 200 response. If verification passes, your integration is complete.

TypeScript Types

// All types exported from @blogree/nextjs-adapter interface BlogreePost { id: string; version: number; slug: string; title: string; excerpt: string | null; body: { html: string; markdown: string; json: object; }; meta: { title: string; description: string; og_image: string; }; tags: string[]; published_at: string; // ISO 8601 status: 'published' | 'draft' | 'scheduled'; } interface WebhookPayload { event: 'post.published' | 'post.updated' | 'post.deleted'; post: BlogreePost; site_id: string; timestamp: string; }

Troubleshooting

Webhook not receiving

Ensure your deployment platform allows incoming POST requests to /api/blogree. Check that environment variables are set in your deployment environment (not just .env.local).

HMAC signature failing

Your BLOGREE_WEBHOOK_SECRET must exactly match the value shown in the Blogree dashboard. Copy-paste it — do not retype it manually.

Posts not appearing after publish

Check that revalidatePath('/blog') is being called in your webhook handler. For static export mode, use revalidate: 60 on your blog page as a fallback.

generateStaticParams timing out

If you have many posts, add export const revalidate = 3600 to your blog page instead of generating all static params at build time.