A demo proves that a starter runs. One small end-to-end feature shows what it actually takes to extend it.
A Next.js SaaS boilerplate can look finished before you have written any product code. Sign-up works, the dashboard loads, and billing is connected. The README explains how to run the app. The demo looks more like a product than a blank repository.
Those are good signs, but they answer only one question: can you run the starter? They tell you much less about what it will be like to add the feature your product actually needs.
Feature lists are a poor proxy for that. Two starters may both include authentication, billing, teams, email, and an admin area, yet feel very different to extend. One may have clear conventions that guide new work. Another may contain several working examples that do not quite fit together.
Before choosing a Next.js SaaS boilerplate, add one small feature that is not already in the demo. Pick something that touches the UI, server-side logic, persistence, authorization, validation, and tests. It does not need to be impressive. It needs to show how the pieces fit together—and where you are left guessing.
Pick a feature that resembles your product
Choose something small enough to implement without turning the evaluation into a side project, but substantial enough to cross the boundaries your application will use.
For a B2B product, you might add saved reports to a workspace. A member can create a report with a name and a set of filters, see it in a list, rename it, and delete it. The reports belong to a workspace, so one customer must never be able to read another customer's data.
Make the permission model explicit before you start. For this example, suppose all workspace members can view reports, while only the creator or a workspace admin can rename or delete one. A different product may need different rules; what matters is that the test has an unambiguous expected behavior.
A feature this ordinary still raises the questions that matter in real SaaS work:
- Where does the page belong, and which existing UI patterns should it follow?
- Where is input validated?
- How does the server establish the current user and workspace?
- Where are membership, ownership, and role checks enforced?
- How are schema changes and migrations handled?
- What does the app show when there are no reports, a request fails, or the user lacks permission?
- Which test proves that one workspace cannot access another workspace's data?
Choose something else if saved reports are too similar to a feature the starter already includes. Pick a small piece of real product work that forces the repository's conventions to work together.
Define the behavior before you start coding
Don't start with a vague instruction like “build saved reports” and judge the result afterward. Whether you write the code yourself or use an AI coding agent, important decisions can otherwise stay implicit: who is allowed to do what, how failures should work, and where the logic belongs.
Write down the expected behavior first. For the saved-reports example, the criteria might be:
- An authenticated workspace member can create and view reports belonging to that workspace.
- Only the report's creator or a workspace admin can rename or delete it.
- Every operation checks workspace membership and the required permissions on the server; hiding a button is not authorization.
- Input is validated at the server boundary, and relevant data constraints are enforced where appropriate.
- Schema changes follow the project's documented migration process.
- The interface handles loading, empty, success, and error states.
- Tests cover normal operations, invalid input, and attempts to access or modify another workspace's data.
This is not a full product specification. It is enough to make the test repeatable. Use the same criteria—and the same prompt—when comparing repositories.
Before coding, inspect one or two similar features in the repository. If you are using an agent, ask it to identify the patterns it plans to follow, the files likely to change, and any unresolved questions. Treat its explanation as a hypothesis, not proof; check the code and the eventual diff.
Watch the implementation, not just the final screen
A feature can look fine in the browser and still violate the architecture or permissions you meant to test. Pay attention to how the change fits into the codebase, not just how the final screen looks.
Does the code have an obvious home?
Not every concern needs its own abstraction. The common path should, however, be easy to find. When adding a workspace feature, you should be able to find how similar features handle routing, data access, validation, and server-side operations.
See whether the implementation follows those patterns or introduces a parallel way to solve the same problem. A new helper may be justified; another inconsistent implementation may mean the conventions are unclear. Review the diff and surrounding code, not just a summary of what changed.
Is authorization enforced where the data is accessed?
Multi-tenant behavior is a useful test because the UI is only one part of the security boundary. Hiding a Delete button from the wrong user does not protect an operation if the server accepts an arbitrary record ID.
Trace the request to the data-access layer. Find where the current user and workspace are established, then verify that the operation checks workspace membership and the permission required for that report. Then inspect the negative tests. A test showing only that an allowed request succeeds leaves a central part of the requirement unproven.
Do data changes follow the normal workflow?
Adding a table or field should not depend on unexplained manual steps. Look at how the project defines its schema, creates and applies migrations, and prepares data for local development and tests. Where relevant, check whether a clean environment can be set up using documented commands. If there is no clear path, it may be difficult to keep development, test, and production databases aligned.
Are the important states part of the feature?
The happy path is usually the easiest part to get working. What happens when a report has no name, the database operation fails, the workspace no longer exists, or the session expires? The interface does not need elaborate custom treatment for every case, but its behavior should be deliberate and consistent with the rest of the application.
Do the tests show what is protected?
A passing build is necessary, but it does not prove the feature is safe or correct. Look for tests that express the contract: a member can read allowed data, a different workspace cannot, unauthorized changes are rejected, invalid input fails, and an unsuccessful operation is not presented as successful. Tests should make important rules understandable to the next developer, not merely satisfy a coverage threshold.
Measure friction, not the number of files
After implementing the feature, record what happened. Which project-specific decisions did you have to guess? Did you find a documented pattern or infer one from unrelated files? Was existing validation and authorization code reused? How often did you have to correct the same mistake? Could you run the relevant tests using the documented setup, or did you need to discover extra services and undocumented steps?
Do not score the starter by the raw number of changed files. A sound vertical slice may touch a schema, a server-side operation, a page, and several tests. A short diff can still be wrong if it bypasses the permission model. A larger diff can be reasonable if it follows the project's established layers.
For each candidate, note which conventions were clear, where you had to guess, what the tests covered or missed, and how much setup work was needed. Run the same exercise on every starter you're seriously considering. You won't get a universal ranking, but you will have evidence tied to the work you expect to do.
A good starter makes its conventions visible
This matters whether you write the code yourself or use an AI coding agent. A repository should make it possible to see where business logic belongs, how authentication and workspace access are enforced, how migrations run, and which patterns new work is expected to follow.
I ran into the problem while adding promo-code support and extending referrals in my own product. An AI coding agent would sometimes duplicate logic that already existed, put a module in the wrong part of the codebase, or add imports that went against the intended dependency hierarchy. I would fix the structure and refactor the code, then a later session would bring some of the same problems back. The frustrating part wasn't one bad change; it was having to do the same cleanup again.
I realized that instructions and examples were only part of the answer. Some rules about module boundaries and import direction were implicit, and nothing checked them consistently. That experience pushed me toward making important architecture rules explicit and verifiable, rather than relying on each new session to preserve them.
Clear instructions and consistent examples still matter. Tests matter too, but ordinary feature tests may not catch an import that violates the intended dependency direction or a module placed in the wrong layer. When evaluating a starter, look for conventions that are easy to discover and, where it matters, checks that can flag these violations before they accumulate. A long AGENTS.md file cannot compensate for inconsistent code.
Run the experiment in a disposable branch. You are evaluating how a change fits into the project, not adding experimental code to the product you intend to ship. Keep the prompt and acceptance criteria so you can repeat the exercise on another starter—or after an update.
Do not expect a boilerplate to make every decision for you
There are limits to this exercise. A starter cannot know your domain, and no set of conventions replaces engineering judgment. Some projects make stronger decisions about authentication, data access, hosting, or UI because coherent defaults are more useful than an abstraction for every possible choice.
That can be a good trade-off. The goal is not to make every dependency interchangeable or hide every implementation detail behind another layer. It is to understand how the starter expects new work to fit into the system—and whether those expectations suit the product you plan to build.
If the starter already implements a feature you need, inspect that implementation instead of rebuilding it just to complete the exercise. If your chosen feature falls outside the starter's intended scope, that is useful information too. Evaluate a boilerplate against the work you are likely to do, not an imaginary product that needs everything.
I build Codapult, a Next.js SaaS boilerplate and starter kit for MVPs, team-based B2B products, and AI-powered applications. I have a commercial interest in this space, so treat Codapult as one candidate to evaluate—not as an independent verdict. Apply the same test to it and to any other starter: choose a realistic feature, trace the boundaries, and see whether the code gives you a clear path forward.
A feature list tells you what a starter claims to include. A small, realistic change gives you a better sense of what working in that codebase will be like. Try the kind of change you expect to make next before committing to it.
Top comments (0)