ThanhDev
  • Articles
  • Categories
  • Tags
  • About

ThanhDev

Frontend engineering notes on React, Next.js, TypeScript and software architecture.

  • Articles
  • Categories
  • Tags
  • About
  • GitHub
  • LinkedIn
  • Website
  • Email

© 2026 ThanhDev. Built with Next.js, Prisma and PostgreSQL.

  1. Home
  2. Articles
  3. Frontend Architecture: Layers That Earn Their Keep
Architecture

Frontend Architecture: Layers That Earn Their Keep

Repositories, services and actions are useful right up to the point where they become ceremony. Where I draw the line.

T

ThanhDev

Jul 18, 2026•2 min read•401 views

Every architecture article shows the same diagram. Few of them say when to skip a layer, which is the harder and more useful question.

The layers I actually use

text
Route (page.tsx)      → renders, no logic
Server Action         → auth, authorization, validation
Service               → business rules
Repository            → database queries

Each has one job, and each job is one you can name in a sentence.

Repository owns queries and nothing else. It does not know what a session is.

ts
export function findPublishedPosts(params: ListParams) {
  return prisma.post.findMany({
    where: { status: "PUBLISHED" },
    orderBy: { publishedAt: "desc" },
    take: params.pageSize,
    skip: (params.page - 1) * params.pageSize,
  });
}

Service owns rules — the things that would still be true if you swapped the database.

ts
export async function publishPost(id: string) {
  const post = await postRepository.findById(id);
  if (!post) throw new NotFoundError();
  if (!post.content.trim()) throw new ValidationError("Cannot publish an empty post");
 
  return postRepository.update(id, {
    status: "PUBLISHED",
    publishedAt: post.publishedAt ?? new Date(),
  });
}

Action owns the trust boundary. Authentication, authorization, input validation — in that order, before anything else runs.

When to collapse a layer

A repository that only ever wraps a single Prisma call and is used by exactly one service is not an abstraction. It is a second name for the same thing.

I collapse when all of these hold:

  • One caller.
  • No branching, no rules, no transformation.
  • No realistic second implementation.

Reading a category list is a good example. There is no rule to enforce, so the service calls Prisma directly and the layer never exists.

The test that actually matters

Not "is this clean architecture", but: when the requirement changes, how many files do I open?

If adding a field to a post means touching six files that each pass it through unchanged, the layers are costing more than they return. If a rule changes and you can find its single home in ten seconds, they are earning their keep.

Architecture is not a shape you copy. It is a bet about which things will change together.

  • #TypeScript
  • #Prisma
  • #Next.js
Share
PreviousMeasuring and Fixing Core Web VitalsNextTanStack Query or Server Components?

Related articles

  • Architecture

    Designing a Prisma Schema That Scales

    Relations, indexes and cascade rules are decisions you make once and live with for years. Here is how I approach them.

    • #Prisma
    • #PostgreSQL
    • #TypeScript
    Aug 15, 2026•2 min read•793 views
  • Architecture

    Measuring and Fixing Core Web Vitals

    LCP, CLS and INP explained through the fixes that actually move them, not the definitions you can already look up.

    • #Performance
    • #Next.js
    Jul 4, 2026•2 min read•594 views
  • Architecture

    PostgreSQL Indexes Every Developer Should Know

    B-tree, partial, composite and GIN — what each one is for, and how to tell whether the planner is using yours.

    • #PostgreSQL
    • #Performance
    Aug 8, 2026•2 min read•914 views