Next.js earns its place when two demands land on the same product: it has to feel like an application and it has to be readable by a crawler. Getting both from a client-only app means bolting on a rendering layer later, which is always the more expensive order to do it in.
What we use it for
Marketplaces, dashboards behind a marketing front, subscription products, and anything where the first screen must arrive complete rather than assemble itself after a spinner.
Rendering decided per route
Not every page wants the same treatment. Pricing and landing pages are static. A logged-in dashboard renders on the server per request. A product listing sits in between, revalidating on a schedule that matches how often stock actually moves. We write that decision down per route, because a codebase where nobody can say why a page is dynamic will drift into all-dynamic within a year.
Caching is the hard part
Most Next.js incidents are cache incidents: a mutation that did not invalidate, a personalised fragment cached for everyone, a build that went stale. We keep the cache surface small and explicit, and we test invalidation the way we test any other behaviour.
Where it meets the marketing side
Metadata, canonical URLs and structured data are generated from the same source as the content, so a new page cannot ship without them. Core Web Vitals are measured in the pipeline against a budget rather than checked once at launch. That link between the build and the demand work is the point of having engineering and marketing in one team.
Honest limits
Next.js adds a server to own and a framework upgrade cadence to keep up with. If the site is content with a form on it, say so early and we will build it in Astro for a fraction of the running cost. The components themselves are ordinary React, so the skills transfer either way.