DEV Community

the bomber
the bomber

Posted on

Building a Lean Blog CMS in Next.js 14 and Prisma with ISR

Why Build a Custom CMS?

Hi, I am Kaleb. I run Dream Futures Tech out of Pompano Beach, Florida. When I was rebuilding my own business website, I needed a straightforward blog publishing workflow. I did not want a heavy headless CMS subscription or a bloated monolith database setup. I already run a small automation server at home, and I host my main site on standard infrastructure. What I wanted was simple: write drafts, hit publish, and have the static pages update instantly without rebuilding the entire site from scratch every time.

To solve this, I built a lightweight blog content management system directly into my Next.js 14 application using Prisma and SQLite. It keeps things contained, fast, and entirely under my control. In my judgment, writing your own lightweight CMS for a simple business blog takes less time than configuring a third-party service once you factor in authentication and API keys.

The Prisma Schema for Posts and Drafts

Everything starts with the database schema. I use Prisma because the type safety prevents entire classes of silly bugs when moving data from form inputs to the database. Here is the core model I use for my blog posts:

model Post {
  id        String   @id @default(cuid())
  title     String
  slug      String   @unique
  content   String
  published Boolean  @default(false)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}
Enter fullscreen mode Exit fullscreen mode

The published boolean is the gatekeeper. When I query the database for public-facing routes, I strictly filter by published: true. In the admin dashboard route, I fetch all records so I can view, edit, and delete my working drafts.

Handling Drafts in the UI

Separating drafts from published posts on the frontend is straightforward with Next.js App Router. My public route uses a simple Prisma query:

// app/blog/page.tsx
import { db } from '@/lib/db';

export default async function BlogIndex() {
  const posts = await db.post.findMany({
    where: { published: true },
    orderBy: { createdAt: 'desc' },
  });

  return (
    <div>
      {posts.map(post => (
        <article key={post.id}>
          <h2>{post.slug}</h2>
          <p>{post.title}</p>
        </article>
      ))}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

For the admin side, I built a protected route that lists both drafts and published entries, allowing me to iterate on formatting and copy before pushing it live to the world.

Incremental Static Regeneration and RevalidatePath

Next.js 14 provides powerful caching mechanisms, but stale static pages can be frustrating when you are trying to publish a new article. I wanted the performance of static generation combined with instant updates upon publishing.

I used Incremental Static Regeneration (ISR) combined with Next.js Server Actions to handle this. When I update a post's status to published in the admin panel, the Server Action updates the database and immediately calls revalidatePath.

Here is what that Server Action looks like in practice:

// app/admin/actions.ts
'use server';

import { db } from '@/lib/db';
import { revalidatePath } from 'next/cache';

export async function togglePublish(id: string, currentStatus: boolean) {
  await db.post.update({
    where: { id },
    data: { published: !currentStatus },
  });

  // Invalidate the public blog index and the specific post route
  revalidatePath('/blog');
  revalidatePath(`/blog/[slug]`, 'page');
}
Enter fullscreen mode Exit fullscreen mode

When togglePublish runs, Next.js clears the cache for those specific paths in the background. The next time a visitor requests the blog index or the specific article, they get the freshly generated static page without waiting for a full manual deployment.

Keeping It Maintainable

Building this internal tool directly into my Next.js codebase saved me from managing external API tokens, webhooks, and third-party dashboard limits. SQLite handles the data cleanly on my hosting provider, and the codebase remains small enough that I can audit every line myself. If you are managing a small business site and want total control over your publishing pipeline without adding unnecessary infrastructure overhead, combining Next.js App Router, Prisma, and revalidatePath is a reliable path forward.

I build sites and automations for small businesses at dreamfuturestech.com.

Top comments (0)