How I structure a Next.js SaaS project in 2025
I have made enough SaaS projects to know that a clean folder setup does not magically solve your problems, but a messy one can slow you down more than you expect. When I started building my earlier products I kept changing my folder structure every few weeks. At some point I realised I was spending more time organising the code than shipping features.
This post is not a perfect guide. It is simply the structure that lets me move fast without feeling lost. It comes from building things like TryScribe, Synote and smaller internal tools for clients.
Why this structure works for me
Next.js gives a lot of freedom. That freedom is great until you mix logic across pages, scatter components everywhere, and forget where a single function lives. Over time I wanted a setup where I could look at the project for five seconds and understand where everything sits. Nothing fancy. Just clarity.
The main idea behind my structure
I split the app into three simple areas:
what the user sees, what the app uses, and what the app depends on.
This keeps my files discoverable and makes it easy to onboard even my future self.
The project layout
Below is the exact structure I use most of the time.
Now I will explain how each part behaves.
actions
This holds my server actions. I try to keep anything that touches the database or external services inside this folder. Keeping the server code separate helps me follow the flow of the app without mixing logic everywhere.
app
This holds my routes. Each folder is a page or a group of pages. I try to keep server code out of here and let pages stay thin. If a page gets heavy I create a folder inside it called sections to separate parts of the UI.
components
Shared UI building blocks. Buttons, cards, modals, form fields, small reusable elements. My rule is simple. If it appears more than twice, it moves here.
constants
A simple folder for static values that do not change. Here I place things like routes, links, feature list, static arrays, app level strings, config defaults. This prevents magic values from leaking across the app.
hooks
Custom hooks that make my code clean. I keep them tiny. One hook should solve one clear behaviour. Examples are theme handling, form states, scroll behaviour or local preference states.
lib
This is where helpers, small utilities, formatters, actions and external clients live. If I am connecting to a database, an API, or a mail service, the client lives here.
styles
Global CSS files or themes. I rarely touch this folder once the base layout is ready.
types
Shared TypeScript definitions. I prefer keeping all product level types in one place. It keeps me from creating duplicates and makes it easier to refactor later.
The small decisions that make a big difference
I used to keep everything inside the app folder. It made sense at first but the project became noisy. Moving my UI pieces out of app gave me more breathing room. It also made the mental map of the project cleaner.
I also keep my actions small. One action should do one clear thing. If I am mixing behaviour I split it without thinking too much. This helps prevent the trap of having giant files that nobody wants to touch later.
A quick story about why this structure matters
During an early version of TryScribe I spent two hours searching for a single function that controlled a small part of the scoring logic. It was sitting inside a random page file. That day I moved all server code into a dedicated folder. It saved me so much time on later iterations.
Sometimes structure is not about looking neat. It is about not wasting time on problems that should not exist.
What you should take from this
Do not aim for the perfect structure. Aim for a structure that makes you feel calm when you open the project. Keep your server logic together. Keep your UI pieces together. Put names that make sense to you. The goal is not elegance. The goal is speed and clarity.
If you want to see a real world version of this structure, you can check PyNext Starter on my GitHub. It follows the same pattern and will give you a clean starting point.
If you want a breakdown of how I structure the backend or how I connect these pieces together for a real product, tell me and I will write that next.