Server Components, One Year Later
There is no shortage of articles about Playwright. Most of them cover the happy path. This one is about what happened after the happy path ended.
The first symptom was subtle. Nothing failed outright, but form validation kept drifting in the wrong direction, and every fix seemed to make the next one harder.
The problem
The setup was ordinary: Playwright at the edges, React doing most of the heavy lifting, and a handful of scripts nobody wanted to own. It worked well until usage roughly doubled over a single quarter.
Most of the complexity was accidental. Over the years we'd layered three different approaches to form validation on top of each other, and each one made sense at the time.
The fix
We started by writing down the one rule we actually cared about, then deleted every bit of code that didn't serve it. It's not glamorous, but it works.
Stripped of the project-specific bits, it looks like this:
.card-list {
container-type: inline-size;
}
@container (min-width: 36rem) {
.card {
display: grid;
grid-template-columns: 12rem 1fr;
gap: 1.5rem;
}
}
Nothing about this is clever, and that's the point. Anyone on the team can read it in a minute and know exactly what it does.
A few things that helped along the way:
- Agree on what "done" means before the first pull request.
- Delete dead code aggressively; it's the cheapest refactor there is.
- Write the migration guide before writing the migration.
Trade-offs
None of this is free. The new approach is a bit more verbose, and newcomers occasionally ask why we don't just use React for everything. That's a fair question with a boring answer: we tried, and it made form validation harder to reason about.
The biggest cost was social, not technical. Changing how people think about form validation takes much longer than changing the code.
What's next
If you take one thing away from this, let it be this: form validation is a design decision, not a cleanup task. Make it early and make it on purpose.