What production-ready actually means
The gap between an approved design and a shippable frontend is mostly made of states nobody drew. That gap is where projects lose their last three weeks.
"Production-ready" is used as though it means finished. It does not. It means the interface still behaves when the conditions stop being ideal — and the conditions in a design file are always ideal.
The gap between a design that is approved and a frontend that can be shipped is mostly made of states nobody drew. That gap is where projects lose their last three weeks.
The states nobody drew
A mockup shows one state: the good one. Real data has more, and every one of them needs a decision by someone:
- Empty. A new account with no projects, no messages, no history. The most common answer is a blank panel, which reads as breakage rather than as a beginning.
- Loading. Not a spinner in the middle of the page — what specifically is pending, and does the layout hold its shape while it arrives so nothing jumps.
- Error. What the person is told, and what they can do next. "Something went wrong" is not a decision, it is the absence of one.
- Partial. Half the data arrived. Common with several sources on one screen and almost never designed.
- Too much. A name that is forty characters, a list with 900 rows, a description someone pasted an essay into.
- Stale. The screen was correct four minutes ago. Does it say so?
Six states per meaningful component, and that is before the interface is translated. Deciding them during the build means a developer guessing at design questions under deadline. Deciding them during design costs an afternoon.
Text that refuses to stay put
Any interface with a second language has a layout problem waiting in it. German and French run materially longer than English for the same sentence, and a button sized to its English label wraps, truncates, or pushes its neighbour off the row.
The fix is not clever. It is designing the component to survive its longest realistic label rather than its shortest, and checking that with the actual translated strings instead of placeholder text. It is a ten-minute check that reliably catches things a week before launch instead of a week after.
What exists before the JavaScript does
Between the request and the moment your app is interactive, something is on screen. On a fast laptop that gap is imperceptible. On a mid-range phone on a poor connection it is seconds.
So the questions are: what is visible in that window, does the layout shift when the real content lands, and what does a client that never runs JavaScript at all — a link preview, a feed reader, an AI crawler — receive? For a single-page app the honest answer is often an empty document, and the people relying on it rarely know.
Typed boundaries and a build that says no
Production-ready also means the codebase can be changed by someone who did not write it. That is a tooling question as much as a craft one.
TypeScript across the boundaries — API responses, form values, stored data — turns a wrong assumption into a compile error at the edge instead of a runtime failure deep inside a component. That argument is set out separately in TypeScript before you scale.
The part worth adding here is that the build should refuse things, not just report them. A check that prints a warning gets scrolled past. A check that fails the build gets fixed. The rule we apply: if a defect would be invisible in review and visible to users, the build should stop.
The defects that only exist in production
This is the category that separates "works on my machine" from production-ready, because these bugs cannot be caught locally by definition. Security headers, caching rules, redirects and host configuration do not exist in a dev server.
A concrete one from this site. Web fonts were loaded with the standard non-blocking pattern: request the stylesheet as print, then switch it to screen once it arrives, using an inline handler on the tag. Correct, widely recommended, and silently dead in production — the site's Content-Security-Policy does not cover inline event handlers, so the switch never ran. The stylesheet downloaded, stayed scoped to print, and every page rendered in system fallback fonts. Nothing errored. Nothing failed a build. It simply looked slightly wrong to everyone, and matched the design in every local environment.
There is no clever process that prevents that class of defect. There is only checking the deployed site rather than the local one, with the actual headers applied — and treating "it looks right on my machine" as the beginning of verification rather than the end of it.
What handoff should actually contain
When design and development are separate, the file is not the deliverable. What a developer needs in order to not guess:
- Every state above, for every component that has them.
- Behaviour at the breakpoints in between, not only at the three that were drawn.
- Which values are tokens — spacing, colour, type scale — and which are one-offs.
- What is interactive, what happens on hover, focus and keyboard, and what the focus order is.
- What moves, and what it does when the viewer has asked for reduced motion.
- The real longest string for every label, not the demo one.
A designer and a developer working in the same room answer these as they go, which is the main practical argument for not separating them. When they are separated, the answers have to be written down or they get invented.
How this shows up in the work
Appstruct works with React, Next.js, TypeScript and modern web technologies to design and build production-ready digital products, which in practice means the design decisions above and the build decisions above are made by the same group of people rather than passed between two.
Lowkar, a car marketplace built with React and Next.js, is the case study where that is most visible: dealer listing flows and mobile search are exactly the kind of surfaces where empty, partial and too-much states are the ordinary case rather than the exception.
If you have a design that is approved and a build that keeps finding new edges, tell us what you are building.
