Every new project starts the same way for me: before a single component gets built, I decide how the layout system is going to work. Not the visual design — the structural rules. What's the max content width? Where do breakpoints actually change behavior instead of just resizing text? Which parts of the page are server-rendered by default, and which ones genuinely need to ship JavaScript to the client? Getting these decisions right early saves a lot of rework later, because responsive bugs are almost always layout-architecture bugs wearing a CSS costume.
Start with the container, not the component
The first thing I set up in any Next.js project is a small set of layout primitives — a container with a max width and consistent horizontal padding, a handful of spacing tokens, and a type scale defined with clamp() so headings scale fluidly between breakpoints instead of jumping at fixed points. This sounds basic, but skipping it is the single biggest reason component-level responsive code turns into a mess. If every component has to reinvent its own container logic, you end up with inconsistent gutters across the site and CSS that's harder to reason about than it needs to be.
Most "responsive design" problems are actually layout-system problems that never got solved at the container level.
Component structure: server by default
With the App Router, my default assumption for any new component is that it's a Server Component unless it has a specific reason not to be — state, event handlers, browser-only APIs. This isn't a performance micro-optimization I bolt on later; it changes how I structure the component tree from the start. I try to push "client" boundaries as low and as narrow as possible, so an interactive dropdown doesn't force its entire parent section to ship as client-side JavaScript.
// Only this small piece needs the client boundary —
// everything around it stays a Server Component.
"use client";
import { useState } from "react";
export function PricingToggle() {
const [annual, setAnnual] = useState(true);
return (
<button onClick={() => setAnnual(!annual)}>
{annual ? "Annual" : "Monthly"}
</button>
);
}
Responsive patterns I actually use
I lean heavily on a small set of patterns rather than writing bespoke media queries for every component:
- Fluid type with
clamp()for headings, so text scales smoothly instead of stepping at breakpoints. - CSS Grid with
auto-fitandminmax()for card grids, which handles most "how many columns fit" logic without a single media query. - Container-level breakpoints defined once and reused, rather than scattered pixel values across components.
- Mobile-first ordering — I write the small-screen layout first and add complexity as the viewport grows, never the reverse.
A grid pattern I reuse constantly
.card-grid {
display: grid;
gap: 1.5rem;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
}
That single rule handles one column on mobile, two on tablet, three or four on desktop — all without a media query, and it degrades gracefully at any width in between.
Testing across real breakpoints
Before I call a page done, I check it at a specific set of widths: 320px, 375px, 390px, 430px, a tablet width around 768px, and a couple of desktop widths. Chrome's device toolbar is fine for a first pass, but I always spend a few minutes on an actual phone before shipping — emulators tend to hide font-rendering and tap-target issues that only show up on real hardware.
| Breakpoint | What I'm checking |
|---|---|
| 320–375px | Text wrapping, tap target size, no horizontal scroll |
| 390–430px | Most common phone widths — this is where most users are |
| 768px | Tablet layout shifts, nav collapse behavior |
| 1024px+ | Multi-column layouts, hover states, max-width limits |
The part that's easy to skip
None of this matters if the site ships too much JavaScript to feel fast on a mid-range phone. Every client component I add gets weighed against a simple question: does this genuinely need to run in the browser, or am I adding it out of habit? Keeping that discipline is, honestly, more responsible for "feeling" responsive than any specific CSS technique.