
Building a Dynamic Featured Posts Component in Next.js 15 with Prisma
Introduction: The Importance of "Featured Content"
In the digital age, attention is the new currency. When a visitor lands on Wisemix Media, you only have a few seconds to capture their interest. A "Featured Posts" section is not just a list of links; it is a curated gateway to your best content.
In this guide, we will dissect a professional-grade FeaturedPosts component built using the latest Next.js 15 features and Prisma ORM. We will explain why specific technologies were chosen and how the code ensures peak performance.
1. The Tech Stack: Why This Combination?
Next.js Server Components (RSC)
The component is defined as an async function. In Next.js, this means it is a Server Component.
Why? Unlike traditional React, this code runs on the server. The database query happens before the page reaches the user's browser. This results in zero "loading spinners" and a much faster First Contentful Paint (FCP).
Prisma ORM
We use prisma.post.findMany() to interact with our PostgreSQL database.
Why? Prisma provides Type-Safety. If we try to access a field that doesn't exist in our PostgreSQL schema, the code will throw an error during development, not after the site is live.
Tailwind CSS
For styling, we utilize utility classes like grid-cols-1 md:grid-cols-2 xl:grid-cols-4.
Why? It allows for a Mobile-First responsive design. The layout automatically adjusts from a single column on iPhones to four columns on large desktop monitors.
2. Logical Deep Dive: Understanding the Query
Let's break down the logic inside the Prisma fetch:
JavaScript..... before featured posts make menu.jsx
const posts = await prisma.post.findMany({
where: {
featured: true,
published: true,
...(excludeIds.length > 0 && {
id: { notIn: excludeIds },
}),
},
include: { category: true },
orderBy: { createdAt: "desc" },
take: 8,
});
how to make dynamic header using isr
The Exclusion Logic (excludeIds)
One of the most professional touches in this code is the excludeIds prop.
The Problem: If a user is already reading a specific blog post, you don't want that same post to appear again in the "Featured" section at the bottom of the page.
The Solution: By passing the current post's ID into
excludeIds, we use the{ notIn: excludeIds }filter to ensure every suggestion is fresh and new.
How Many Posts?
The take: 8 parameter limits the query. In this design, we fetch up to 8 posts.
On a desktop, this creates two clean rows of four cards.
Limiting the number of posts is crucial for Core Web Vitals. Fetching 100 posts would increase the "Total Blocking Time" (TBT), but 8 is the "sweet spot" for engagement without sacrificing speed.
3. Performance Optimization: Next/Image
The component uses the next/image component with specific optimizations:
fillAttribute: This allows the image to occupy the entire parent container (h-[210px]), making it easy to create uniform card sizes regardless of the original image's dimensions.priority={index < 2}: This is a genius move for SEO. It tells the browser to load the first two images immediately (High Priority) because they are likely "above the fold," while lazy-loading the rest to save data.object-cover: This CSS property ensures that images aren't stretched or squeezed, maintaining a professional look.
4. User Experience & UI Design
The Visual Feedback
The code includes group-hover:scale-105. When a user hovers over a post card, the image slightly zooms in. This micro-interaction makes the website feel "alive" and encourages clicks.
Clean Typography
Using line-clamp-2 on the title ensures that even if a blog post has a very long title, the cards stay the same height. This prevents "layout shift," which is a major factor in Google’s ranking algorithm.
5. How to Implement This in Your Project
To use this component, you simply drop it into your page like this:
JavaScript
// On a single blog page
<FeaturedPosts excludeIds={[currentPost.id]} />
// On the homepage
<FeaturedPosts />
Step-by-Step Installation:
DB Schema: Ensure your Prisma schema has
featured(Boolean) andpublished(Boolean) fields.Lib Folder: Ensure your
prismaclient is exported from@/lib/prisma.Placeholder: Always have a placeholder image (like
/images/blog/placeholder.svg) to prevent broken layouts if an image fails to load.
6. Conclusion: Why This Code Wins
This FeaturedPosts component is a perfect example of modern web standards. It is:
Fast: Thanks to Server-side fetching.
Smart: Thanks to the exclusion logic.
Beautiful: Thanks to Tailwind's responsive grid.
SEO Ready: Thanks to optimized images and semantic HTML.
By implementing this architecture, Wisemix Media ensures that users stay longer on the site, reducing the bounce rate and increasing the overall domain authority.
this is featuredpots complete code of my website wisemixmedia
import Image from "next/image";
import Link from "next/link";
import prisma from "@/lib/prisma";
export default async function FeaturedPosts({ excludeIds = [] }) {
const posts = await prisma.post.findMany({
where: {
featured: true,
published: true,
// If your DB uses siteId, make sure it's included here:
// siteId: process.env.SITE_ID,
...(excludeIds.length > 0 && {
id: { notIn: excludeIds },
}),
},
include: { category: true },
orderBy: { createdAt: "desc" },
take: 8,
});
// If no posts are found, the component won't render
if (!posts || posts.length === 0) {
return null;
}
return (
<section className="py-12 bg-white">
<div className="container mx-auto px-4 text-center">
<span className="text-blue-600 font-semibold text-sm uppercase tracking-wide">
More Featured
</span>
<h2 className="text-3xl font-bold mt-2 mb-4 text-gray-800">
Additional Featured Stories
</h2>
</div>
<div className="container mx-auto px-4 mt-10 grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6">
{posts.map((post, index) => {
const imageSrc = post.mainImage || "/images/blog/placeholder.svg";
return (
<Link key={post.id} href={`/blog/${post.slug}`}>
<article className="relative h-[340px] rounded-xl overflow-hidden shadow-lg bg-gray-50 group flex flex-col">
<div className="relative w-full h-[210px] bg-gray-200">
<Image
src={imageSrc}
alt={post.title}
fill
priority={index < 2}
className="object-cover transition-transform duration-300 group-hover:scale-105"
/>
</div>
<div className="p-4 flex flex-col justify-between flex-grow">
<span className="text-xs bg-blue-600 text-white px-3 py-1 rounded-full w-fit">
{post.category?.name || "Uncategorized"}
</span>
<h3 className="mt-3 text-sm font-bold text-gray-900 line-clamp-2">
{post.title}
</h3>
</div>
</article>
</Link>
);
})}
</div>
</section>
);
}




