≡ Menu

Building a High-Performance Full-Stack Blog with Next.js

In the modern web ecosystem, Next.js has emerged as the go-to framework for developers who want to build fast, SEO-friendly, and scalable applications.

Whether you are a seasoned developer or just starting out, building a blog is a rite of passage that touches on all the core pillars of web development: data fetching, dynamic routing, and server-side logic.

In this tutorial, we’ll walk through creating a full-stack blog from scratch using Next.js 15, Prisma for database management, and Tailwind CSS for a sleek, modern design.

By the end of this guide, you will have a working application where you can view a list of posts, read individual articles, and even publish new content directly to your database.


1. Project Setup

Initialize your project with the latest features.

npx create-next-app@latest my-awesome-blog

Selection Guide:

  • TypeScript: Yes (Better for catching bugs in your post data).

  • Tailwind CSS: Yes (For styling).

  • App Router: Yes (The modern standard).


2. The Data Layer (Prisma)

Prisma acts as the bridge between your code and your database (PostgreSQL, MySQL, or SQLite).

  1. Install Prisma:

    npm install prisma @prisma/client
    npx prisma init
    
  2. Define the Schema: Open prisma/schema.prisma and add your blog model:

    model Post {
      id        String   @id @default(cuid())
      title     String
      slug      String   @unique
      content   String
      excerpt   String
      createdAt DateTime @default(now())
    }
    
  3. Push the Schema: Update your database with npx prisma db push.


3. Building the Blog Feed (Server Component)

In Next.js, we fetch data directly inside our components. This is fast because the data stays on the server and only the HTML is sent to the user.

File: src/app/page.tsx

import Link from 'next/link';
import { prisma } from '@/lib/prisma';

export default async function BlogHome() {
  const posts = await prisma.post.findMany({
    orderBy: { createdAt: 'desc' },
  });

  return (
    <main className="max-w-4xl mx-auto p-8">
      <h1 className="text-5xl font-extrabold mb-12">The Dev Blog</h1>
      <div className="space-y-8">
        {posts.map((post) => (
          <article key={post.id} className="group border-b pb-8">
            <Link href={`/blog/${post.slug}`}>
              <h2 className="text-2xl font-bold group-hover:text-blue-500 transition">
                {post.title}
              </h2>
              <p className="text-gray-600 mt-2">{post.excerpt}</p>
              <span className="text-sm text-gray-400">{post.createdAt.toDateString()}</span>
            </Link>
          </article>
        ))}
      </div>
    </main>
  );
}

4. The Individual Post Page (Dynamic Routing)

We use the folder syntax [slug] to handle any blog post URL (e.g., /blog/my-first-post).

File: src/app/blog/[slug]/page.tsx

import { prisma } from '@/lib/prisma';
import { notFound } from 'next/navigation';

export default async function PostPage({ params }: { params: { slug: string } }) {
  const post = await prisma.post.findUnique({
    where: { slug: params.slug },
  });

  if (!post) notFound();

  return (
    <article className="max-w-3xl mx-auto py-16 px-4">
      <h1 className="text-4xl font-bold mb-4">{post.title}</h1>
      <p className="text-gray-400 mb-8 italic">{post.createdAt.toDateString()}</p>
      {/* The 'prose' class handles all the markdown-style formatting */}
      <div className="prose lg:prose-xl">
        {post.content}
      </div>
    </article>
  );
}

5. Adding Content (Server Actions)

Instead of building a separate API, we use Server Actions to handle form submissions securely on the server.

File: src/app/admin/new/page.tsx

import { prisma } from '@/lib/prisma';
import { redirect } from 'next/navigation';

export default function NewPost() {
  async function handleSubmit(formData: FormData) {
    'use server';

    const title = formData.get('title') as string;
    const content = formData.get('content') as string;
    const slug = title.toLowerCase().replace(/ /g, '-');

    await prisma.post.create({
      data: {
        title,
        content,
        slug,
        excerpt: content.substring(0, 150) + '...',
      },
    });

    redirect('/');
  }

  return (
    <form action={handleSubmit} className="flex flex-col gap-4 max-w-xl mx-auto p-10">
      <input name="title" placeholder="Post Title" className="border p-2 rounded" required />
      <textarea name="content" placeholder="Write your post here..." className="border p-2 h-40 rounded" required />
      <button type="submit" className="bg-blue-600 text-white p-2 rounded hover:bg-blue-700">
        Publish Post
      </button>
    </form>
  );
}

Conclusion

Congratulations! You’ve just built a fully functional, full-stack blog application.

By leveraging Next.js Server Components, you’ve ensured that your blog is optimized for search engines and loads lightning-fast for your readers.

You’ve also learned how to use Server Actions to simplify your data mutations and Prisma to keep your database interactions clean and type-safe.

From here, you could take this project even further by adding user authentication with NextAuth, integrating a Markdown editor for more complex formatting, or implementing a comment section.

The foundation you’ve built today is the perfect springboard for any advanced web project.

Happy coding!

Useful links below:

Let me & my team build you a money making website/blog for your business https://bit.ly/tnrwebsite_service

Get Bluehost hosting for as little as $1.99/month (save 75%)…https://bit.ly/3C1fZd2

Best email marketing automation solution on the market! http://www.aweber.com/?373860

Build high converting sales funnels with a few simple clicks of your mouse! https://bit.ly/484YV29

Join my Patreon for one-on-one coaching and help with your coding…https://www.patreon.com/c/TyronneRatcliff

Buy me a coffee ☕️https://buymeacoffee.com/tyronneratcliff

{ 0 comments… add one }

Leave a Comment