Massblogger

REST API Documentation

Integrate MassBlogger with any website using our REST API.

Quick Start - Copy & Paste

Copy these two files to your Next.js project. Replace your_api_key with your API key from website settings.

1. Blog Overview Page

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>
  );
}

2. Single Post Page

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.

Category & Tag Pages

Create dedicated pages for categories and tags to improve SEO and user experience. You have two options:

Query Parameters (Simple)

/blog?category=Tech

Use the blog overview page code above. Filter posts based on URL parameters.

Dedicated Pages (SEO-friendly)

/blog/category/tech

Create separate page files for cleaner URLs and better SEO.

Category Page

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>
  );
}

Tag Page

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>
  );
}

Making Categories & Tags Clickable

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>

Filtering & Sorting

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));

Recommended URL Structure

/blogAll posts
/blog/[slug]Single post
/blog/category/[slug]Posts in category
/blog/tag/[slug]Posts with tag

Authentication

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 Reference

GET/api/blog

Returns all published posts for your website.

Query Parameters

apiKeyRequiredYour API key

Example Response

[
  {
    "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
  }
]
GET/api/blog?slug=POST_SLUG

Returns a single post by slug, including the full content.

Query Parameters

apiKeyRequiredYour API key
slugRequiredThe post slug

Example Response

{
  "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
}
GET/api/taxonomies

Returns all categories and tags configured for your website. Use these to build category/tag pages on your frontend.

Query Parameters

apiKeyRequiredYour API key

Example Response

{
  "categories": [
    { "id": "abc123", "name": "Technology" },
    { "id": "def456", "name": "Lifestyle" }
  ],
  "tags": [
    { "id": "ghi789", "name": "React" },
    { "id": "jkl012", "name": "JavaScript" }
  ]
}

Usage Example

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'));

Response Fields

FieldTypeDescription
titlestringThe article title
slugstringURL-friendly identifier
categorystring | nullPost category
tagsstring[]Array of tag names
featuredImagestring | nullFeatured image URL
contentstringFull HTML content (single post endpoint only)
metaTitlestringSEO title for the page
metaDescriptionstringSEO description for the page
createdAtstring (ISO 8601)When the post was created
updatedAtstring (ISO 8601)When the post was last updated (show "Updated on...")
scheduleDatestring | nullScheduled publish date (hide post if in future)

Quick Notes

  • Styling:The content field is raw HTML. Style it with @tailwindcss/typography (prose classes) or custom CSS.
  • Images:For Next.js Image component with external URLs, add remotePatterns to your next.config.
  • Cache:You may need to clear your cache or redeploy your site to see new articles.
  • Scheduled Posts:Filter out posts where scheduleDate is in the future.
  • Updated Date:Show "Updated on..." when updatedAt > createdAt.
  • Internal Links:Use /api/internal-links to auto-link keywords in your content.

Need help? Contact us at support@massblogger.com