Recently, I introduced testing into a new Next.js project.
One question always comes up at the beginning:
How much testing should we add?
Too much testing slows down development.
Too little testing eventually creates regressions.
For this project, we wanted something practical:
reasonable confidence without slowing down shipping.
This is the approach we ended up taking.
We Started With the Testing Trophy
For this project, we adopted the Testing Trophy approach.
Compared to the traditional Testing Pyramid, Testing Trophy emphasizes:
more integration tests and less E2E.
This felt much closer to how modern React applications — especially in a Next.js environment — tend to fail.
In many cases, frontend bugs don't happen in isolated functions.
They happen when:
UI
+ state
+ user interaction
come together.
Especially in React / Next.js applications, where components, state, rendering, and interaction are tightly coupled.
For an early-stage product, we wanted to balance:
confidence
vs
maintenance cost
So we decided to prioritize:
Unit Test
+ Integration Test
+ Accessibility Test
while intentionally postponing E2E.
What We Chose
Our testing stack looks like this:
| Type | Tool | Purpose |
|---|---|---|
| Unit Test | Vitest | Pure logic |
| Integration Test | React Testing Library + Vitest | UI behavior |
| Accessibility Test | Playwright + axe-core | Accessibility validation |
We intentionally skipped E2E tests for now.
Why?
Because in early-stage projects:
- UI changes quickly
- Requirements change often
- Maintenance costs grow fast
At this stage, the cost simply felt higher than the benefit.
We'll likely introduce E2E later:
- Authentication
- Payment flow
- Business-critical features
But not yet.
Directory Structure Matters Too
One thing that helped us keep testing practical was our directory structure.
We intentionally separate:
shared modules
vs
feature-specific modules
at the directory level.
Example:
src/
├── shared/
│ ├── components/
│ ├── hooks/
│ └── utils/
│
└── features/
├── auth/
└── search/
The rule is simple:
We prioritize testing shared modules.
Because shared modules are reused across the application, regressions there tend to have wider impact.
This naturally helps keep the testing scope intentionally small.
Without a clear rule, frontend teams often end up testing too much.
For example, when components are organized purely by UI granularity (such as Atomic Design), it can become difficult to decide:
What actually deserves a dedicated test?
By separating modules based on responsibility and reuse, test decisions become much clearer.
Of course, exceptions still exist.
Even if something is not shared, we may still test it when:
logic is complex
business impact is high
regression risk is high
In our current project, we didn't have many cases where this felt necessary.
Another benefit was team alignment.
Since our team previously didn't have a strong frontend testing culture, having explicit rules made it much easier to share:
what should be tested — and what shouldn't
CI Setup
We kept CI intentionally simple:
- typecheck
- lint
- test
- accessibility test
- build
The goal isn't perfect coverage.
It's to:
catch meaningful regressions early
while keeping development fast.
Final Thoughts
I don't think early-stage Next.js projects need massive testing setups.
But I also don't think:
"We'll add tests later"
works very well.
For us, the sweet spot was:
Unit Test
+ Integration Test
+ Accessibility Test
inspired by the Testing Trophy approach.
Simple enough to maintain.
Practical enough to prevent regressions.
How are you approaching testing in early-stage frontend projects?
Top comments (0)