Blog API: Next.js Example
Blog API: Next.js Example
Full API reference: https://api.mailoo.app/docs/v1
This page shows how to integrate Mailoo's public Blog API into a Next.js app. The reference implementation is the blog in this repository at en/blog: list via BFF, article by slug via BFF, no API key on the client.
Prerequisites
- A Mailoo project with a Blog integration and at least one published article
- The project UID, integration ID, and an API key (dashboard → project → API Keys)
The exact values for MAILOO_BLOG_PROJECT_UID and MAILOO_BLOG_INTEGRATION_ID are shown on the integration settings page in the dashboard (Connection (External API) block). Use an API key with Blog (external read) scope.
Environment (server-only)
Add to .env.local:
MAILOO_BLOG_API=<same value as API_BASE_URL>
MAILOO_BLOG_API_KEY=your-api-key-here
MAILOO_BLOG_PROJECT_UID=<from dashboard integration page>
MAILOO_BLOG_INTEGRATION_ID=<from dashboard integration page>
Use server-side only; do not use NEXT_PUBLIC_* for API URL or key.
List Page (e.g. app/blog/page.tsx)
Using BFF routes (same app exposes /api/v1/blog):
export default async function BlogPage() {
const res = await fetch('/api/v1/blog?limit=20', { next: { revalidate: 60 } })
if (!res.ok) return <div>Failed to load blog</div>
const json = await res.json()
const posts = json.data || []
return (
<div>
<h1>Blog</h1>
<ul>
{posts.map((p: { id: string; title: string; slug: string }) => (
<li key={p.id}>
<a href={`/blog/${p.slug}`}>{p.title}</a>
</li>
))}
</ul>
</div>
)
}
Optional: Categories (e.g. for filters)
To show category filters or links, fetch categories from the BFF:
const res = await fetch('/api/v1/blog/categories', { next: { revalidate: 60 } })
const json = await res.json()
const categories = json.data || [] // [{ id, slug, name }, ...]
Use categoryId in the list query when filtering: /api/v1/blog?categoryId=... (see blog-headless-cms{.interpreted-text role="doc"}).
Article Page (e.g. app/blog/[slug]/page.tsx)
Use BFF route /api/v1/blog/slug/[slug] or call the API with X-API-Key server-side. Return 404 if not found. For localized article content, pass the current locale in the query (e.g. ?locale=en); see blog-headless-cms{.interpreted-text role="doc"}.
import { notFound } from 'next/navigation'
export default async function BlogPostPage({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
const res = await fetch(`/api/v1/blog/slug/${encodeURIComponent(slug)}`, {
next: { revalidate: 60 },
})
if (!res.ok) notFound()
const json = await res.json()
const post = json.data
if (!post?.id) notFound()
return (
<article>
<h1>{post.title}</h1>
<p>{post.excerpt}</p>
<div
dangerouslySetInnerHTML={{ __html: post.htmlContent || post.content }}
/>
</article>
)
}
Response shape and errors
For full response fields and error codes (e.g. 404 for unknown slug), see the API reference.