KitRocket

Blog + SEO Module

MDX blog with automatic sitemap, OG images, RSS feed, and structured data.

Pro tier only. This module is included in the Pro plan.

Overview

The Blog + SEO module gives you a complete content marketing setup:

  • MDX blog — write posts in Markdown with React components
  • Sitemap — auto-generated at /sitemap.xml
  • OG images — dynamic Open Graph images for social sharing
  • RSS feed — auto-generated at /feed.xml
  • Structured data — JSON-LD for search engines
  • SEO metadata — per-page title, description, and keywords

Blog content lives in content/blog/ and blog routes live in src/app/(marketing)/blog/.

Configuration

Blog content directory

Posts are MDX files in content/blog/:

content/blog/
├── hello-world.mdx
├── building-with-ai.mdx
└── shipping-fast.mdx

Each post has frontmatter:

---
title: Hello World
description: Our first blog post — what we're building and why.
date: 2026-01-15
author: Your Name
image: /blog/hello-world.png
tags: ["announcement", "launch"]
---

Welcome to our blog. We built this SaaS because...

SEO metadata

Global metadata is set in the root layout:

import type { Metadata } from "next";

export const metadata: Metadata = {
  title: {
    default: "Your SaaS — Tagline here",
    template: "%s | Your SaaS",
  },
  description: "A one-sentence description of your product.",
  openGraph: {
    siteName: "Your SaaS",
    url: "https://yourdomain.com",
  },
  twitter: {
    card: "summary_large_image",
  },
};

Per-page metadata is set in each page file using generateMetadata.

Usage

Write a blog post

Create a new MDX file in content/blog/:

---
title: My New Post
description: A short summary for SEO and social cards.
date: 2026-03-15
author: Your Name
image: /blog/my-new-post.png
tags: ["tutorial"]
---

## Introduction

Start writing your post here. You can use standard Markdown.

### Code examples

```typescript
const greeting = "Hello from the blog!";

React components

You can embed React components in MDX:

<Callout type="info">
  This is a callout component rendered inside a blog post.
</Callout>

Posts are automatically available at /blog/my-new-post based on the filename.

Sitemap generation

The sitemap is auto-generated at build time:

import { getAllPosts } from "@/lib/blog";

export default async function sitemap() {
  const posts = await getAllPosts();

  return [
    { url: "https://yourdomain.com", lastModified: new Date() },
    { url: "https://yourdomain.com/pricing", lastModified: new Date() },
    { url: "https://yourdomain.com/blog", lastModified: new Date() },
    ...posts.map((post) => ({
      url: `https://yourdomain.com/blog/${post.slug}`,
      lastModified: new Date(post.date),
    })),
  ];
}

Dynamic OG images

Each blog post gets a dynamic Open Graph image generated at request time:

import { ImageResponse } from "next/og";
import { getPost } from "@/lib/blog";

export default async function OGImage({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug);

  return new ImageResponse(
    <div style={{ display: "flex", flexDirection: "column", padding: 48 }}>
      <h1 style={{ fontSize: 48 }}>{post.title}</h1>
      <p style={{ fontSize: 24, color: "#666" }}>{post.description}</p>
    </div>,
    { width: 1200, height: 630 }
  );
}

RSS feed

An RSS feed is generated at /feed.xml:

import { getAllPosts } from "@/lib/blog";

export async function GET() {
  const posts = await getAllPosts();
  // Generate RSS XML from posts
  return new Response(rssXml, {
    headers: { "Content-Type": "application/xml" },
  });
}

Customization

Change blog layout

Edit the blog list page and post layout:

  • src/app/(marketing)/blog/page.tsx — blog index with post cards
  • src/app/(marketing)/blog/[slug]/page.tsx — individual post layout

Add categories

  1. Add a category field to post frontmatter
  2. Create a category filter on the blog index page
  3. Optionally add /blog/category/[category] routes

Custom OG image template

Edit src/app/blog/[slug]/opengraph-image.tsx to change fonts, colors, layout, or add your logo.

Structured data

JSON-LD is added automatically for blog posts:

export default function BlogPost({ post }) {
  const jsonLd = {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    headline: post.title,
    description: post.description,
    datePublished: post.date,
    author: { "@type": "Person", name: post.author },
  };

  return (
    <>
      <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
      <article>{/* post content */}</article>
    </>
  );
}

Removing this module

  1. Delete content/blog/ directory
  2. Delete src/app/(marketing)/blog/ directory
  3. Delete src/app/sitemap.ts (or remove blog entries from it)
  4. Delete src/app/feed.xml/ directory
  5. Remove blog-related MDX dependencies:
pnpm remove next-mdx-remote gray-matter

On this page