How Should I Structure a Reusable Next.js Application Without Overengineering It?
Asked by
MellowCedar47
I'm building a reusable Next.js boilerplate and I'm trying to decide how much architecture to introduce up front. In a React and Express project, I typically use a layered structure like Web โ API โ Service โ Repository โ Database, which makes it fairly easy to replace the web client with an Android or iOS app later.
With Next.js, I'm considering using server actions as the web-facing layer: Web โ Server Action โ Service โ Repository โ Database, with Prisma handling database access. If mobile clients are added later, I could introduce an API that also calls the same services. My concern is ending up with duplicate logic in server actions and API handlers.
Would it be better to use API routes from the beginning and have both the web and mobile clients consume them? Or should server actions be the default for a web-only application, with an API added only when another client actually becomes necessary? I'm looking for a practical Next.js architecture that stays reusable without being over-engineered.
Answers
A layered structure like Web โ API โ Service โ Repository โ Database isnโt automatically over-engineering. Small, well-defined abstractions can make testing and future changes easier, especially around data access. The important part is avoiding layers that only pass arguments through without adding a useful boundary.
Reply to this answer
For a product that genuinely targets web and mobile, a shared workspace can work well: separate client and server applications, shared database and validation packages, a reusable API client, authentication utilities, and common UI components. That approach is useful when multiple clients are already part of the plan, but itโs probably more structure than a web-only project needs on day one.
Reply to this answer
Next.js doesnโt require you to choose one universal architecture. Server actions are convenient for mutations that belong closely to the web application, while route handlers or a separate API are better when external clients need a stable contract. Either way, put authorization and business logic below those entry points so the choice of transport doesnโt duplicate core behavior.
Reply to this answer
Donโt design around a hypothetical mobile client. Keep your business rules in shared service modules, and make both server actions and route handlers thin adapters around those services. If another client becomes a real requirement, add an API at that point. That keeps the initial application simple without preventing a clean expansion later.
Reply to this answer
Add your answer
Answers are reviewed before publication.
Related Discussions