
Short version: Friendly URLs make links easier to read for humans and better for SEO.
I designed a small database structure with Department β Category β Product, and used it to create hierarchical URLs like:
https://e-commer.com/market/fruits/apples/red-apple
In this post, Iβll explain why this matters, how I implemented it with Prisma and Next.js, SEO benefits, and some practical tips you can use in your own project.
π Tip: always use hyphens (-) instead of underscores (_). Google treats hyphens as spaces, which makes your URLs more readable.
My structure follows this logic:
Department groups main areas, Category can have subcategories (hierarchy), and Product belongs to a single category.
model Department { id Int @id @default(autoincrement()) name String @unique slug String @unique categories Category[] } model Category { id Int @id @default(autoincrement()) name String slug String @unique parentId Int? parent Category? @relation("CategoryHierarchy", fields: [parentId], references: [id]) children Category[] @relation("CategoryHierarchy") departmentId Int department Department @relation(fields: [departmentId], references: [id]) products Product[] } model Product { id Int @id @default(autoincrement()) name String slug String @unique price Decimal @db.Decimal(10, 2) description String imageUrl String imageAlt String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt published Boolean @default(true) categoryId Int category Category @relation(fields: [categoryId], references: [id]) @@index([name]) @@index([description]) @@index([slug]) @@index([price]) @@map("products") }
parentId).Thatβs how I can generate URLs like:
/maket/fruits/apples/red-apple
| Page Type | Example URL |
|---|---|
| Department | /maket/ |
| Category | /market/fruits/ |
| Subcategory | /market/fruits/apples |
| Product | /market/fruits/apples/red-apple |
Rule: keep URLs short, clean, and descriptive.
To support hierarchical URLs, I use a catch-all route:
app/[...slug]/page.tsx
This captures any level of depth, like ["market", "fruits", "apples", "red-apple"].
Inside page.tsx, I get params.slug and use it to fetch the right data from the database.
["market", "fruits", "apples", "red-apple"]).const segments = params.slug;
const last = segments[segments.length - 1];
const product = await prisma.product.findFirst({
where: {
slug: last,
published: true,
},
include: { category: { include: { parent: true } } }
});
@unique).slug fields.fullPath field like "fruits/apples/red-apple" for faster lookups.fullPath automatically if a parent slug changes.<link rel="canonical" ...> on each page.Breadcrumbs still help users navigate and provide context for search engines.
Even if Google doesnβt always show them on mobile, they are still a good UX element.
export async function generatedBreadcrumbs(
slugs: string[]
): Promise<{ name: string; href: string }[]> {
const breadCrumbs = [{ name: 'Home', href: '/' }]
let currentPath = ''
for (let i = 0; i < slugs.length; i++) {
currentPath += `/${slugs[i]}`
const slug = slugs[i]
let name = slug
const product = await getProductBySlug(slug)
try {
if (product) name = product.name
const category = await getCategoryBySlug(slug)
if (category) name = category.name
const department = await getDepartmentBySlug(slug)
if (department) name = department.name
} catch (error) {
console.error(`Error Loading breadcrumb for ${slug}:`, error)
name = (slug)
}
breadCrumbs.({ name, : currentPath })
}
breadCrumbs
}
export function BreadCrumbs({ links }: BreadCrumbsProps) {
return (
<nav className='w-full flex justify-start' aria-label='Breadcrumb'>
<ol className='flex flex-warp items-center gap-2 py-4 text-sm'>
{links.map(({ name, href }, index) => {
const isLast = index == links.length - 1
return (
<li key={href} className='flex items-center gap-2'>
{index > 0 && (
<span className='text-gray-400 select-none' aria-hidden>
/
</span>
)}
{isLast ? (
<span className='text-gray-700 font-semibold'>{name}</span>
) : (
<Link
href={href}
className='text-blue-500 hover:text-blue-600 hover:underline transition-colors'
>
{name}
</Link>
)}
</li>
)
})}
</>
)
}
export default async function Page({ params }: { params: { slug: string[] }}) {
const segments = params.slug;
const last = segments[segments.length - 1];
const product = await findProductByPath(segments);
if (product) return <ProductPage product={product} />;
const category = await findCategoryByPath(segments);
if (category) return <CategoryPage category={category} />;
return notFound();
}
Friendly URLs may look like a small detail, but they make a big impact on SEO and user experience.
With Prisma and Next.js, itβs easy to build a professional and scalable structure for your online store.