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
Route (page.tsx) → renders, no logic
Server Action → auth, authorization, validation
Service → business rules
Repository → database queriesEach 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.
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.
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.