Test Blog Post
Starter template for writing out a blog post using MDX/JSX and Next.js.
Abdullah Muhammad
Published on August 17, 2025 • 5 min read
Understand Dynamic MDX with Supabase
MDX opens the door to combining Markdown with JSX, making your blog or documentation highly flexible. But what if you don’t want to keep your MDX files in the file system? That’s where Supabase comes in.
In this article, we explore how to store and render MDX content dynamically from Supabase, using next-mdx-remote
in a Next.js App Router project.
🧠 Why Store MDX in Supabase?
Storing MDX in Supabase enables:
- Dynamic blog posts
- Easier authoring through a CMS or dashboard
- Version control and user-generated content
- Centralized content API
Instead of placing .mdx
files in your repo, you fetch them from Supabase and render them as needed.
Working with Images in MDX
The following is an image utilizing the Next.js Image component and the built-in HTML element for working with figcaptions:
🛠️ Storing MDX in Supabase
Create a table named articles
in Supabase with columns like:
id
: UUIDslug
:text
title
:text
content
:text
(your raw MDX string)created_at
:timestamp
Here’s a sample GitHub Gist for you to view:
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { verify } from 'jsonwebtoken';
// Simple middleware to protect specific routes
export function middleware(request: NextRequest) {
// Get token from cookies
const token = request.cookies.get('token')?.value;
// If no token exists, direct the user to the main page.
// Send a response redirect
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
try {
// Verify JWT
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
verify(token, JWT_SECRET);
// If the token is valid, transfer control of request to the appropriate route handler function
return NextResponse.next();
}
catch (error) {
// Token is invalid, redirect to login
return NextResponse.redirect(new URL('/login', request.url));
}
}
// Only run middleware on protected routes
export const config = {
matcher: ['/dashboard/:path*', '/profile/:path*', '/api/protected/:path*'],
};
And here, is a sample code block featuring how to work with SQL
INSERT INTO articles (id, slug, title, content) VALUES (
uuid_generate_v4(),
'dynamic-mdx-with-supabase',
'Understand Dynamic MDX with Supabase',
'# Hello from Supabase\n\nThis MDX content is stored in the cloud!',
now()
);