The architecture decision a startup founder makes in week one, and rewrites in month eight

Why a client-only React app is the fastest thing to build and the most expensive thing to fix, how Next.js's App Router separates what must be findable from what only needs to be usable, and the code at the boundary between the two.

by ·

A startup's first technical decision is almost never about the product: it is about how the interface gets built, and it usually gets made by whoever is in the biggest hurry to see something on screen. A React app rendered entirely on the client — no server involved in drawing the page — is the fastest thing to get moving: you write a component, it appears, you iterate. It is also the choice that makes the pricing page invisible to Google until somebody notices, usually when the first investor asks “why don't you show up when someone searches your own product name”.

The problem is not technical in the narrow sense: it is that the fix arrives late, and arriving late is what makes it expensive in a way an earlier decision would not have been. It is not a matter of adding a plugin: pages that need to be found and screens that only need to be used require two different ways of being built, and mixing them into one architecture picked for week-one speed costs a rewrite by month eight.

This piece is the decision that avoids the rewrite: how Next.js's App Router keeps, in the same project, both the pages a search engine must be able to read and the interactive dashboard it will never need to care about, the real code at that boundary, and the bill for finding out late.

1 · Why “it works in my browser” is not enough for a search engine

A client-only app sends the browser an almost-empty page and a JavaScript file that, once run, builds everything else. For a person with a good connection the difference is barely noticeable. For a crawler that has to decide in a fraction of a second whether a page deserves indexing, that empty page is often all it sees — and “maybe” is not the answer that brings organic traffic.

The moment the bill arrives

It does not arrive at launch: it arrives when marketing asks why the pricing page does not show up for the product's own name, or when an investor notices that the blog — written, published, good — brings no traffic. By then the product code and the marketing code are already tangled, and untangling them is the rewrite, not a fix.

2 · One project, two ways of drawing the page

The mistake is not choosing React: it is treating every page the same way. Marketing pages — landing, pricing, blog — need to already exist, written, by the time whoever reads them arrives, human or crawler. The dashboard behind the login needs exactly the opposite: immediate interactivity, and a crawler will never care because it will never get in. Next.js's App Router lets you choose per page, in the same project, without two separate codebases to maintain.

Client-onlyServer-onlyApp Router · per page
Pricing page, indexed by GoogleRisky: depends on how long the crawler waitsYes, by constructionYes — Server Component, pre-rendered
Dashboard with an interactive editorYes, by constructionExpensive: every interaction needs a round trip to the serverYes — Client Component, hydrated only where needed
Weight downloaded by a landing-page visitorThe whole app bundle, dashboard includedMinimal, but little interactivity anywhereOnly that page's markup plus the components it actually uses
Two codebases to maintainNo, but one approach compromises the other caseNo, the same trade-off in reverseNo — one project, the choice is per file

The row that matters is the last one in the first column: you do not have to choose between development speed and search-engine visibility. The boundary is drawn per page, with one line at the top of a file — not with a parallel architecture you maintain forever.

3 · The boundary, in code

A pricing page is a Server Component by default: no special directive, data arrives through a normal `fetch` executed on the server, and what the browser receives is already complete HTML — a crawler does not have to wait for, or run, anything.

app/pricing/page.tsx

// No "use client" directive: this component runs ON THE SERVER,
// once per build (or per request, with revalidate set to zero).
export default async function PricingPage() {
  const plans = await fetch("https://api.example.com/plans", {
    next: { revalidate: 3600 }, // regenerated at most once an hour
  }).then(r => r.json())

  return (
    <main>
      <h1>Pricing</h1>
      {plans.map((p: { name: string; price: number }) => (
        <div key={p.name}>
          <h2>{p.name}</h2>
          <p>{p.price} € / month</p>
          {/* Interactive island INSIDE a static page: only this piece
              hydrates JavaScript in the browser, the rest stays HTML. */}
          <SubscribeButton plan={p.name} />
        </div>
      ))}
    </main>
  )
}

The crawler sees title, prices and structure without running a single line of JavaScript, because there is none inside this component — it is already HTML by the time it leaves the server.

app/pricing/SubscribeButton.tsx

"use client" // The boundary: from here down, JavaScript in the browser.

import { useState } from "react"

export default function SubscribeButton({ plan }: { plan: string }) {
  const [loading, setLoading] = useState(false)

  async function subscribe() {
    setLoading(true)
    await fetch("/api/checkout", { method: "POST", body: JSON.stringify({ plan }) })
    // ...redirect to checkout
  }

  return (
    <button onClick={subscribe} disabled={loading}>
      {loading ? "One moment…" : "Choose this plan"}
    </button>
  )
}

"use client" is the one line separating the two strategies. Everything above this boundary in the component tree stays static HTML; everything below it becomes JavaScript hydrated in the browser — a button, not the whole page.

4 · The bill for fixing it late

Rewriting the rendering architecture eight months after launch is never “adding SSR”: it is a cascade, because every later choice assumes the first one. Routing, how data gets fetched, the boundary between public and authenticated pages were all written assuming everything ran in the browser.

€ 38,000 – 62,000 — typical cost of a rendering rewrite on a product with 40-60 pages, six months after launch

The figure includes time lost re-testing features that already worked — not just new code.

Half an hour on your architecture, before you write a line of it

If you are about to start a new product, bring the list of pages you're imagining: which need to be found, which only need to be used. In half an hour we draw the boundary before it becomes a coding habit that is hard to undo.

If you already have a client-only app that works and nobody is asking you for organic traffic yet, I will tell you: it does not need rewriting today. The right moment is before the first marketing page gets written on top of the wrong architecture, not after sixty pages already have been.

The service behind this article

Technical SEO and AI search optimization

If this was useful, carry on from here

FAQ

Why is a client-only React app invisible to search engines?

Because the browser receives an almost-empty page and a JavaScript file that builds the content AFTER the first paint. A crawler that does not wait long enough — or that scores the page at a disadvantage regardless — sees little or nothing, and the meta tags for social previews, which must exist before anything runs, stay generic.

What are Next.js Server Components and when do you use them?

They are components that run on the server and produce already-complete HTML, with no JavaScript shipped to the browser for that part of the page. They are used for anything that needs to be found — landing pages, pricing, blog — while the "use client" directive marks the boundary to interactive components (forms, editors, dashboards) that need to run in the browser.

Can you mix server-side and client-side rendering in the same Next.js project?

Yes, and that is exactly the App Router's advantage: the choice is made per file, not per project. A page can be almost entirely static with a single interactive island — a button, a form — hydrating JavaScript, while the rest stays plain HTML.

How much does it cost to rewrite the rendering architecture after launch?

On a product with 40-60 pages, typically €38,000-62,000, six months after launch — and it is not just new code: it includes time re-testing features that already worked, and organic traffic lost during the weeks pages stayed poorly indexable, which does not come back by pushing the fix forward in time.

Nadia Maar