Integrate MassBlogger with any website using our REST API.
Copy these two files to your Next.js project. Replace your_api_key with your API key from website settings.
Shows all posts with category/tag filtering. Supports /blog, /blog?category=Tech, and /blog?tag=React.
// app/blog/page.js (or app/blog/category/[category]/page.js, app/blog/tag/[tag]/page.js)
import Link from 'next/link';
const API_URL = 'https://www.massblogger.com';
const API_KEY = 'your_api_key'; // Get this from your website settings
async function getBlogData() {
const [postsRes, taxonomiesRes] = await Promise.all([
fetch(`${API_URL}/api/blog?apiKey=${API_KEY}`, { next: { revalidate: 60 } }),
fetch(`${API_URL}/api/taxonomies?apiKey=${API_KEY}`, { next: { revalidate: 3600 } }),
]);
const posts = await postsRes.json();
const taxonomies = await taxonomiesRes.json();
// Filter out scheduled posts
const now = new Date();
const visiblePosts = posts.filter(post =>
!post.scheduleDate || new Date(post.scheduleDate) <= now
);
return { posts: visiblePosts, taxonomies };
}
export default async function BlogPage({ searchParams }) {
const { posts, taxonomies } = await getBlogData();
const category = searchParams?.category;
const tag = searchParams?.tag;
// Filter posts by category or tag if provided
let filteredPosts = posts;
if (category) {
filteredPosts = posts.filter(p => p.category === category);
} else if (tag) {
filteredPosts = posts.filter(p => p.tags?.includes(tag));
}
return (
<div className="max-w-4xl mx-auto px-4 py-12">
<h1 className="text-3xl font-bold mb-8">
{category ? `Category: ${category}` : tag ? `Tag: ${tag}` : 'Blog'}
</h1>
{/* Category/Tag Navigation */}
<div className="flex flex-wrap gap-2 mb-8">
<Link href="/blog" className="px-3 py-1 bg-gray-100 rounded-full text-sm hover:bg-gray-200">
All
</Link>
{taxonomies.categories?.map(cat => (
<Link
key={cat.id}
href={`/blog?category=${encodeURIComponent(cat.name)}`}
className={`px-3 py-1 rounded-full text-sm ${category === cat.name ? 'bg-blue-500 text-white' : 'bg-gray-100 hover:bg-gray-200'}`}
>
{cat.name}
</Link>
))}
</div>
{/* Posts Grid */}
<div className="grid gap-8 md:grid-cols-2">
{filteredPosts.map(post => (
<Link key={post.slug} href={`/blog/${post.slug}`} className="group">
{post.featuredImage && (
<img src={post.featuredImage} alt={post.title} className="w-full h-48 object-cover rounded-lg mb-4" />
)}
<p className="text-sm text-blue-600 mb-1">{post.category}</p>
<h2 className="text-xl font-semibold group-hover:text-blue-600">{post.title}</h2>
<p className="text-gray-600 mt-2">{post.metaDescription}</p>
<div className="flex gap-2 mt-3">
{post.tags?.map(tag => (
<span key={tag} className="text-xs bg-gray-100 px-2 py-1 rounded">{tag}</span>
))}
</div>
</Link>
))}
</div>
</div>
);
}Displays a single post with SEO metadata, internal links automatically applied, and category/tag links.
// app/blog/[slug]/page.js
const API_URL = 'https://www.massblogger.com';
const API_KEY = 'your_api_key'; // Get this from your website settings
async function getPost(slug) {
const [postRes, linksRes] = await Promise.all([
fetch(`${API_URL}/api/blog?apiKey=${API_KEY}&slug=${slug}`, { next: { revalidate: 60 } }),
fetch(`${API_URL}/api/internal-links?apiKey=${API_KEY}`, { next: { revalidate: 3600 } }),
]);
const post = await postRes.json();
const links = await linksRes.json();
// Apply internal links to content
let content = post.content || '';
if (Array.isArray(links)) {
links.forEach(link => {
const regex = new RegExp(`\\b(${link.keyword})\\b`, 'gi');
content = content.replace(regex, `<a href="${link.url}" class="text-blue-600 hover:underline">$1</a>`);
});
}
return { ...post, content };
}
export async function generateMetadata({ params }) {
const post = await getPost(params.slug);
return {
title: post.metaTitle || post.title,
description: post.metaDescription,
};
}
export default async function PostPage({ params }) {
const post = await getPost(params.slug);
if (!post || post.error) {
return <div className="text-center py-20">Post not found</div>;
}
const showUpdated = post.updatedAt && new Date(post.updatedAt) > new Date(post.createdAt);
return (
<article className="max-w-3xl mx-auto px-4 py-12">
{/* Category & Tags */}
<div className="flex items-center gap-3 mb-4">
{post.category && (
<a href={`/blog?category=${encodeURIComponent(post.category)}`} className="text-blue-600 text-sm font-medium">
{post.category}
</a>
)}
{post.tags?.map(tag => (
<a key={tag} href={`/blog?tag=${encodeURIComponent(tag)}`} className="text-xs bg-gray-100 px-2 py-1 rounded">
{tag}
</a>
))}
</div>
{/* Title */}
<h1 className="text-4xl font-bold mb-4">{post.title}</h1>
{/* Date */}
<p className="text-gray-500 mb-8">
{new Date(post.createdAt).toLocaleDateString()}
{showUpdated && <span> · Updated {new Date(post.updatedAt).toLocaleDateString()}</span>}
</p>
{/* Featured Image */}
{post.featuredImage && (
<img src={post.featuredImage} alt={post.title} className="w-full rounded-xl mb-8" />
)}
{/* Content - use Tailwind Typography plugin for styling */}
<div
className="prose prose-lg max-w-none"
dangerouslySetInnerHTML={{ __html: post.content }}
/>
</article>
);
}Tip: Install @tailwindcss/typography for automatic content styling with the prose class.
Create dedicated pages for categories and tags to improve SEO and user experience. You have two options:
/blog?category=TechUse the blog overview page code above. Filter posts based on URL parameters.
/blog/category/techCreate separate page files for cleaner URLs and better SEO.
Create app/blog/category/[slug]/page.js for SEO-friendly category URLs.
// app/blog/category/[slug]/page.js
import Link from 'next/link';
const API_URL = 'https://www.massblogger.com';
const API_KEY = 'your_api_key';
export async function generateStaticParams() {
const res = await fetch(`${API_URL}/api/taxonomies?apiKey=${API_KEY}`);
const { categories } = await res.json();
return categories?.map(cat => ({
slug: cat.name.toLowerCase().replace(/\s+/g, '-'),
})) || [];
}
export async function generateMetadata({ params }) {
const { slug } = await params;
const name = slug.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
return {
title: `${name} - Blog`,
description: `Browse all posts in ${name}`,
};
}
export default async function CategoryPage({ params }) {
const { slug } = await params;
const postsRes = await fetch(`${API_URL}/api/blog?apiKey=${API_KEY}`, { next: { revalidate: 60 } });
const posts = await postsRes.json();
const filteredPosts = posts.filter(p =>
p.category?.toLowerCase().replace(/\s+/g, '-') === slug
);
const categoryName = slug.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
return (
<div className="max-w-4xl mx-auto px-4 py-12">
<h1 className="text-3xl font-bold mb-8">Category: {categoryName}</h1>
<div className="grid gap-8 md:grid-cols-2">
{filteredPosts.map(post => (
<Link key={post.slug} href={`/blog/${post.slug}`} className="group">
<h2 className="text-xl font-semibold group-hover:text-blue-600">{post.title}</h2>
<p className="text-gray-600 mt-2">{post.metaDescription}</p>
</Link>
))}
</div>
</div>
);
}Create app/blog/tag/[slug]/page.js for SEO-friendly tag URLs.
// app/blog/tag/[slug]/page.js
import Link from 'next/link';
const API_URL = 'https://www.massblogger.com';
const API_KEY = 'your_api_key';
export async function generateStaticParams() {
const res = await fetch(`${API_URL}/api/taxonomies?apiKey=${API_KEY}`);
const { tags } = await res.json();
return tags?.map(tag => ({
slug: tag.name.toLowerCase().replace(/\s+/g, '-'),
})) || [];
}
export default async function TagPage({ params }) {
const { slug } = await params;
const postsRes = await fetch(`${API_URL}/api/blog?apiKey=${API_KEY}`, { next: { revalidate: 60 } });
const posts = await postsRes.json();
const filteredPosts = posts.filter(p =>
p.tags?.some(tag => tag.toLowerCase().replace(/\s+/g, '-') === slug)
);
const tagName = slug.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
return (
<div className="max-w-4xl mx-auto px-4 py-12">
<h1 className="text-3xl font-bold mb-8">Tag: {tagName}</h1>
<div className="grid gap-8 md:grid-cols-2">
{filteredPosts.map(post => (
<Link key={post.slug} href={`/blog/${post.slug}`} className="group">
<h2 className="text-xl font-semibold group-hover:text-blue-600">{post.title}</h2>
<p className="text-gray-600 mt-2">{post.metaDescription}</p>
</Link>
))}
</div>
</div>
);
}In your single post page, link categories and tags so users can browse related content:
{/* Category link */}
{post.category && (
<a
href={`/blog/category/${post.category.toLowerCase().replace(/\s+/g, '-')}`}
className="text-blue-600 text-sm font-medium hover:underline"
>
{post.category}
</a>
)}
{/* Tag links */}
<div className="flex gap-2">
{post.tags?.map(tag => (
<a
key={tag}
href={`/blog/tag/${tag.toLowerCase().replace(/\s+/g, '-')}`}
className="text-xs bg-gray-100 px-2 py-1 rounded hover:bg-gray-200"
>
{tag}
</a>
))}
</div>Filter and sort posts for category/tag pages:
// Filter by category
const categoryPosts = posts.filter(p => p.category === 'Technology');
// Filter by tag
const tagPosts = posts.filter(p => p.tags?.includes('React'));
// Sort by date (newest first)
const sorted = posts.sort((a, b) =>
new Date(b.createdAt) - new Date(a.createdAt)
);
// Sort alphabetically
const alphabetical = posts.sort((a, b) =>
a.title.localeCompare(b.title)
);
// Combine filter + sort
const result = posts
.filter(p => p.category === 'Technology')
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));| /blog | All posts |
| /blog/[slug] | Single post |
| /blog/category/[slug] | Posts in category |
| /blog/tag/[slug] | Posts with tag |
Show relevant content to readers based on category, tags, or recency. Add these sections to your single post page.
Show posts from the same category as the current article.
// Get posts in the same category (excluding current post)
function getRelatedByCategory(posts, currentPost, limit = 3) {
return posts
.filter(p => p.category === currentPost.category && p.slug !== currentPost.slug)
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
.slice(0, limit);
}
// Usage
const relatedPosts = getRelatedByCategory(allPosts, currentPost);Show posts that share the most tags with the current article.
// Get posts with overlapping tags, sorted by relevance
function getRelatedByTags(posts, currentPost, limit = 3) {
const currentTags = currentPost.tags || [];
if (currentTags.length === 0) return [];
return posts
.filter(p => p.slug !== currentPost.slug)
.map(p => ({
...p,
sharedTags: (p.tags || []).filter(tag => currentTags.includes(tag)).length,
}))
.filter(p => p.sharedTags > 0)
.sort((a, b) => b.sharedTags - a.sharedTags)
.slice(0, limit);
}Show the most recently published posts.
// Get the most recently created posts
function getRecentPosts(posts, currentPost, limit = 5) {
return posts
.filter(p => p.slug !== currentPost?.slug)
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
.slice(0, limit);
}Show posts that have been recently updated (great for evergreen content).
// Get recently updated posts
function getRecentlyUpdated(posts, currentPost, limit = 5) {
return posts
.filter(p => p.slug !== currentPost?.slug && p.updatedAt)
.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt))
.slice(0, limit);
}Combine category, tags, and recency for the most relevant recommendations.
// Combine multiple strategies for best relevance
function getRelevantSources(posts, currentPost, limit = 4) {
const currentTags = currentPost.tags || [];
// Score each post based on relevance
const scored = posts
.filter(p => p.slug !== currentPost.slug)
.map(p => {
let score = 0;
// Same category = +3 points
if (p.category === currentPost.category) score += 3;
// Each shared tag = +2 points
const sharedTags = (p.tags || []).filter(tag => currentTags.includes(tag)).length;
score += sharedTags * 2;
// Recently updated = +1 point
const daysSinceUpdate = (Date.now() - new Date(p.updatedAt || p.createdAt)) / (1000 * 60 * 60 * 24);
if (daysSinceUpdate < 30) score += 1;
return { ...p, relevanceScore: score };
})
.filter(p => p.relevanceScore > 0)
.sort((a, b) => b.relevanceScore - a.relevanceScore)
.slice(0, limit);
return scored;
}Fetch all posts alongside the current post to enable related content:
async function getPostData(slug) {
const [postRes, allPostsRes] = await Promise.all([
fetch(`${API_URL}/api/blog?apiKey=${API_KEY}&slug=${slug}`),
fetch(`${API_URL}/api/blog?apiKey=${API_KEY}`),
]);
const post = await postRes.json();
const allPosts = await allPostsRes.json();
return {
post,
allPosts: Array.isArray(allPosts) ? allPosts : [],
};
}All API requests require an API key. You can find your API key in your website settings after adding a Next.js or REST API website.
GET https://www.massblogger.com/api/blog?apiKey=YOUR_API_KEY/api/blogReturns all published posts for your website.
| apiKey | Required | Your API key |
[
{
"title": "My First Post",
"slug": "my-first-post",
"category": "Technology",
"tags": ["React", "JavaScript"],
"featuredImage": "https://example.com/image.jpg",
"metaTitle": "My First Post | Blog",
"metaDescription": "An introduction to...",
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-16T14:20:00.000Z",
"scheduleDate": null
}
]/api/blog?slug=POST_SLUGReturns a single post by slug, including the full content.
| apiKey | Required | Your API key |
| slug | Required | The post slug |
{
"title": "My First Post",
"slug": "my-first-post",
"category": "Technology",
"tags": ["React", "JavaScript"],
"featuredImage": "https://example.com/image.jpg",
"metaTitle": "My First Post | Blog",
"metaDescription": "An introduction to...",
"content": "<p>Your full HTML content here...</p>",
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-16T14:20:00.000Z",
"scheduleDate": null
}/api/internal-linksReturns all internal links configured for your account. Use these to automatically add internal links to your articles based on keywords.
| apiKey | Required | Your API key |
[
{ "keyword": "contact us", "url": "/contact" },
{ "keyword": "pricing", "url": "/pricing" },
{ "keyword": "learn more", "url": "/about" }
]You can use these links to automatically replace keywords in your article content with links:
// Fetch internal links
const links = await fetch(
`${API_URL}/api/internal-links?apiKey=${API_KEY}`
).then(res => res.json());
// Replace keywords in content with links
let content = post.content;
links.forEach(link => {
const regex = new RegExp(`\\b${link.keyword}\\b`, 'gi');
content = content.replace(regex,
`<a href="${link.url}">${link.keyword}</a>`
);
});/api/taxonomiesReturns all categories and tags configured for your website. Use these to build category/tag pages on your frontend.
| apiKey | Required | Your API key |
{
"categories": [
{ "id": "abc123", "name": "Technology" },
{ "id": "def456", "name": "Lifestyle" }
],
"tags": [
{ "id": "ghi789", "name": "React" },
{ "id": "jkl012", "name": "JavaScript" }
]
}Use taxonomies to filter posts and build category/tag pages:
// Fetch taxonomies
const { categories, tags } = await fetch(
`${API_URL}/api/taxonomies?apiKey=${API_KEY}`
).then(res => res.json());
// Filter posts by category
const techPosts = posts.filter(p => p.category === 'Technology');
// Filter posts by tag
const reactPosts = posts.filter(p => p.tags.includes('React'));| Field | Type | Description |
|---|---|---|
| title | string | The article title |
| slug | string | URL-friendly identifier |
| category | string | null | Post category |
| tags | string[] | Array of tag names |
| featuredImage | string | null | Featured image URL |
| content | string | Full HTML content (single post endpoint only) |
| metaTitle | string | SEO title for the page |
| metaDescription | string | SEO description for the page |
| createdAt | string (ISO 8601) | When the post was created |
| updatedAt | string (ISO 8601) | When the post was last updated (show "Updated on...") |
| scheduleDate | string | null | Scheduled publish date (hide post if in future) |
content field is raw HTML. Style it with @tailwindcss/typography (prose classes) or custom CSS.remotePatterns to your next.config.scheduleDate is in the future.updatedAt > createdAt./api/internal-links to auto-link keywords in your content.Need help? Contact us at support@massblogger.com