When a Greek radio station asked me to rebuild their editorial platform, the brief had two hard constraints: no CMS subscription, and page loads under 200ms.
MDX files with Next.js static generation hit both targets.
The content model
Every article is an .mdx file with frontmatter:
title: "Μίκης Θεοδωράκης — 100 Χρόνια"
category: "Μουσική"
subcategory: "Μουσικοί"
date: "2026-02-09"
author: "Λάμπης Καϊτεφτζίδης"
readTime: 6
excerpt: "Εκατό χρόνια από τη γέννηση..."
coverImage: "/images/theodorakis.jpg"
featured: true
gray-matter parses the frontmatter at build time. generateStaticParams pre-renders every article page. Result: a static site with full editorial control, zero database, and Vercel's CDN serving every page.
Category pages without a database
The category filter is just an array .filter() over the article metadata:
export async function generateStaticParams() {
const categories = [...new Set(getAllArticles().map(a => a.category))]
return categories.map(category => ({ category }))
}
That's it. No SQL, no API call. Build time scales linearly with article count — 200 articles builds in under 3 seconds.
The trade-off
Content updates require a re-deploy. For a radio station publishing 3–5 articles per week, that's fine — a Vercel webhook on GitHub push makes it invisible. For a publication pushing breaking news every hour, you'd want ISR or a proper CMS.
Phase 2 of this project will wire in Sanity for the editorial team and keep MDX for static pages (About, Categories). The MDX content functions are already abstracted behind a getArticles() interface — swapping the data source is a single file change.
Accessibility
The site is named after the Greek word for "blind" — WCAG AA was non-negotiable. The main callouts:
min-heighton all interactive elements (44px touch targets)aria-labelon all icon-only buttons- Colour contrast checked with
axe-corein CI - No motion that can't be paused (
prefers-reduced-motionon every animation)
Accessibility isn't a feature — it's table stakes.