Architecting a Zero-Database Headless CMS in Next.js 15: GitHub REST API Commit Bridge and On-Demand ISR
For software engineers managing a personal portfolio or technical blog, choosing a Content Management System (CMS) typically involves trade-offs between speed, cost, and complexity:
- Traditional Databases (PostgreSQL, MySQL, MongoDB): Introduce cold-start latencies on serverless edge networks, connection pooling overhead, and continuous hosting bills.
- Third-Party Headless CMS Services (Sanity, Strapi, Contentful): Introduce third-party API dependencies, strict tier limits, vendor lock-in, and unnecessary client-side bundle weight.
- Local Markdown Files (
content/posts/*.md): Fast, version-controlled, and cost-free, but historically limited by one fundamental constraint: on serverless environments like Vercel, the filesystem is read-only at runtime, preventing in-browser publishing without manual git operations.
In this article, I break down the architectural design of a Zero-Database Headless CMS built directly inside Next.js 15 App Router using a GitHub REST API Commit Bridge and On-Demand Incremental Static Regeneration (ISR).
1. The Core Architectural Problem
When running a Next.js application on a serverless platform, each route handler executes inside an ephemeral Lambda container with a read-only filesystem.
In local development (npm run dev), writing a file with Node.js fs.writeFileSync immediately reflects on disk. In serverless production, however, direct disk writes fail or vanish as soon as the lambda finishes execution.
Consequently, publishing from a traditional web interface cannot modify the deployed code directly. Content remains frozen until someone manually runs git commit and git push from a local machine.
To eliminate this manual bottleneck without provisioning an expensive database, we designed a serverless bridge that treats the Git repository as the primary database.
2. Solution Architecture
The system orchestrates a 4-step pipeline that functions cleanly across both mobile and desktop screens:
Step 1: Admin Management UI (/admin)
The author writes and previews articles in Markdown, specifying title, cover image, category, and tags in an isolated admin workspace.
Step 2: Serverless Validation (/api/admin/blog)
Submitting the form triggers a Next.js App Router API route that validates metadata and sanitizes the slug ([^a-z0-9_-]) against directory traversal.
Step 3: Dual-Mode Synchronization (Local & GitHub REST API)
- In Local Development: Content is written directly to
content/posts/<slug>.md. - In Production: The API connects to GitHub's Git Data REST endpoints, creating an authenticated blob, tree, and commit directly onto the
mainbranch.
Step 4: Instant Edge Invalidation (On-Demand ISR)
Upon commit creation, the handler invokes revalidatePath('/blog') and revalidatePath('/blog/[slug]'). The edge CDN purges stale caches, serving fresh static HTML worldwide within milliseconds.
3. Implementing the GitHub Commit Bridge
Here is the TypeScript implementation of the GitHub REST API commit service:
interface GitHubCommitFile {
path: string;
content: string;
}
export async function commitFilesToGitHub(
files: GitHubCommitFile[],
commitMessage: string
): Promise<{ success: boolean; message: string; commitSha?: string }> {
const token = process.env.GITHUB_TOKEN;
const owner = process.env.GITHUB_REPO_OWNER || 'sandrotonal';
const repo = process.env.GITHUB_REPO_NAME || 'gucluyumheqoder';
const branch = process.env.GITHUB_BRANCH || 'main';
if (!token) {
return { success: false, message: 'Local mode: GITHUB_TOKEN not configured.' };
}
const headers = {
Authorization: `Bearer ${token}`,
Accept: 'application/vnd.github.v3+json',
'Content-Type': 'application/json',
'User-Agent': 'gucluyumhe-cms',
};
// 1. Fetch latest commit SHA on target branch
const refRes = await fetch(
`https://api.github.com/repos/${owner}/${repo}/git/ref/heads/${branch}`,
{ headers }
);
const refData = await refRes.json();
const latestCommitSha = refData.object.sha;
// 2. Retrieve base tree SHA
const commitRes = await fetch(
`https://api.github.com/repos/${owner}/${repo}/git/commits/${latestCommitSha}`,
{ headers }
);
const commitData = await commitRes.json();
const baseTreeSha = commitData.tree.sha;
// 3. Map files to tree entries
const treeEntries = files.map((file) => ({
path: file.path.replace(/\\/g, '/').replace(/^\//, ''),
mode: '100644',
type: 'blob',
content: file.content,
}));
const treeRes = await fetch(
`https://api.github.com/repos/${owner}/${repo}/git/trees`,
{
method: 'POST',
headers,
body: JSON.stringify({ base_tree: baseTreeSha, tree: treeEntries }),
}
);
const treeData = await treeRes.json();
// 4. Create new commit object
const newCommitRes = await fetch(
`https://api.github.com/repos/${owner}/${repo}/git/commits`,
{
method: 'POST',
headers,
body: JSON.stringify({
message: commitMessage,
tree: treeData.sha,
parents: [latestCommitSha],
}),
}
);
const newCommitData = await newCommitRes.json();
// 5. Update branch reference to new commit
await fetch(
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`,
{
method: 'PATCH',
headers,
body: JSON.stringify({ sha: newCommitData.sha, force: false }),
}
);
return { success: true, message: 'Commit successful.', commitSha: newCommitData.sha };
}
4. Medium RSS Feed Integration
To streamline dual-publishing, the admin workspace connects directly to the Medium RSS feed (@gucluyumhe):
- Cleans incoming XML content, stripping tracking pixels and formatting raw text into clean Markdown.
- A single "Import to Blog" action pre-populates title, slug, excerpt, tags, and canonical attribution directly into the editor for instant publishing.
5. Architectural Benefits
- Zero Client Overhead: Administrative packages are completely isolated to
/admin, ensuring visitor-facing pages remain lean and fast. - Static Generation by Default (SSG): All blog posts are pre-rendered into static HTML during build time and served via edge CDN.
- Security & Versioning: Eliminates open database ports and SQL injection vectors. Every modification is an immutable Git commit with full historical auditability.
- Zero Ongoing Cost: Operates entirely within standard GitHub and Vercel free/hobby tiers without requiring managed database subscriptions.
6. Summary
Treating your Git repository as the single source of truth provides an optimal blend of speed, security, and developer ergonomics. By combining Next.js 15 App Router, On-Demand ISR, and GitHub REST API, you can achieve a professional headless CMS workflow with zero database maintenance overhead.
