I spent last Tuesday arguing with a designer about a dropdown menu. Not the colors, not the border radius—those were fine. The problem was that the design assumed we could load 5,000 items into the DOM without the browser crying for mercy. “But it looks clean,” they said. Sure. It looked great in the mockup. In the browser, on a mid-range Android device? It was a disaster.
This is the disconnect I see everywhere in 2026. We treat web design and web engineering as two different countries, when in reality, if your design isn’t functional, it’s not a design. It’s a drawing.
Well, I’ve been obsessing lately over bridging this gap. Not just “handing off” designs better, but fundamentally changing how we build scalable applications using tools like TypeScript and Next.js. Because, frankly, I’m tired of fixing “pixel-perfect” UIs that fall apart the second real data hits them.
The “Clean Code” of Design
We talk a lot about clean code—functions that do one thing, separation of concerns, DRY principles. But we rarely apply that rigor to the visual layer until it’s too late. To me, functional design means the code structure is the design strategy.
I recently audited a project running Next.js 16.0.4 where the component library was a tangled mess of props. We had a Button component that took 34 different optional props. Thirty-four. You needed a PhD to render a “Submit” button. That’s not flexible; that’s fragile.
My approach now? Strict constraints. I use TypeScript not just to catch bugs, but to enforce design decisions. And if a button shouldn’t have an icon and a loading state simultaneously, I don’t just write a comment. I make it a type error.
// This is my design documentation now
type ButtonProps =
| { variant: 'primary'; icon?: never; loading?: boolean }
| { variant: 'icon-only'; icon: ReactNode; loading?: never };
When I implemented this pattern last month, our dev velocity didn’t just inch up—it skyrocketed. We stopped having conversations like “Can I put a spinner here?” because the compiler literally said “No.” It forces the design system to be logical, not just aesthetic.
Performance is a Design Feature
And there’s this weird idea that performance is something you sprinkle on at the end, like salt. “Make it fast” is a ticket in the backlog. But you can’t optimize a fundamental architectural flaw.
I’m building a lot of internal tools lately—lightweight, problem-solving stuff—and the constraint I set for myself is brutal: If it doesn’t load instantly on a slow 4G connection, the design is wrong. Not “the code is slow,” the design is wrong.
For example, we had a dashboard showing complex analytics. The initial design called for everything to be visible “above the fold.” In Next.js, that meant blocking the entire render until six different heavy database queries finished. The Time to First Byte (TTFB) was sitting at a painful 1.8 seconds.
I pushed back. We redesigned the UI to embrace streaming. Now, the shell loads instantly (70ms on my local M3 env), and the heavy charts pop in as the data resolves. The design had to change to accommodate the physics of the network. We used React Suspense boundaries as actual design elements—creating skeleton states that looked intentional, not broken.
Scalability vs. The “One-Off” Trap
The biggest enemy of scalable web apps isn’t bad tech; it’s the “just this once” mindset. But I’ve seen it happen. I inherited a codebase in late 2025 that was so brittle, changing a font size in the footer broke the layout on the checkout page. I wish I was joking.
I’m finding that building lightweight, single-purpose tools helps enforce discipline. Instead of a monolithic “Utils” folder, I create small, functional modules. It keeps the bundle size down—I managed to shave 45KB off our main bundle just by ripping out a “do everything” date library and replacing it with three specific functions I actually needed.
The Tooling Reality Check
Let’s talk about the stack, because tools shape how we think. I’ve settled heavily into TypeScript and Next.js, not because they are “industry standard,” but because they handle the friction between design and logic better than anything else right now.
But they aren’t magic. I ran into a nasty issue with TypeScript 5.7.3 recently where my generic component types were getting inferred incorrectly, causing the IDE to suggest props that didn’t exist. It took me four hours to realize I was fighting a circular dependency in my type definitions.
What Actually Works
If you’re trying to build efficient web apps today, stop obsessing over the latest CSS framework or state management library. Focus on the data flow.
- Type your data first. Before you draw a single rectangle in Figma, know what your data looks like. If the data is messy, the UI will be messy. As the Pydantic documentation states, “The easiest way to write correct code is to make illegal states unrepresentable.”
- Design for failure. What happens when the API errors? What happens when the list is empty? What happens when the user has a 300-character name? If you haven’t designed for these, you haven’t finished designing.
- Keep it lightweight. We have browsers that can run 3D games, yet we struggle to render a blog post under 5MB. It’s embarrassing. I audit my dependencies weekly. If a package hasn’t been used in two sprints, it gets cut.
I’m currently refactoring a massive client portal, moving it from a legacy SPA to a modern Next.js architecture. The goal isn’t just “modernization”—it’s about creating a system where the right way to build something is also the easiest way. When clean code meets functional design, you stop fighting the browser and start actually shipping.
It’s messy work. You’ll argue with designers. You’ll fight with the compiler. But when you finally load a page with thousands of interactive elements and it feels smooth as butter? That’s the only metric that matters.

