Skip to content
matthewcm.dev

    ↑ ↓ move · ↵ open · tab scopeopen graph →

    Front-end Rendering Strategies and Architecture

    MVC, SPA, BFF, SSG, ISR, SSR, Server Components, modular monoliths and micro-frontends: what each is good for, what it costs, and how hydration fits in.

    Growingtended planted 3 min read
    On this page

    Pick rendering per page: static where possible, server-rendered where it’s personalised, client-rendered where it’s highly interactive. Then structure the code as a domain-driven modular monolith until team boundaries, not technology, justify micro-frontends.

    The options and their trade-offs

    ApproachWhat it isGood forCosts
    Static pagesPlain HTML and CSSMarketing, docsNo interactivity
    MVC (server-rendered)The server renders views from models via controllersSmall teams; dynamic pages with complex server logicThe UI scales poorly; framework lock-in
    SPAClient state and client routingDashboards, SaaS, logic that lives in the front endToo much JS, slow first load, weak SEO
    BFF (backend for frontend)A backend per client type (desktop vs mobile), often a GraphQL serverAggregating APIs, faster UI iterationAnother service to own; tight coupling; a single point of failure
    SSGPages generated at build time and served from a CDNContent that isn’t interactiveRebuild to change anything
    ISRSSG that regenerates a page after it expiresContent that changes occasionallyA staleness window
    SSRHTML rendered per request, then hydratedPersonalisation, SEO with fresh dataServer cost and hydration cost
    RSCA split tree: some components only ever render on the serverPerformance-sensitive appsA harder mental model

    Hydration

    1. The browser renders the server-generated HTML: First Contentful Paint.
    2. It downloads, parses and executes the JS bundle.
    3. It hydrates the HTML, attaching event handlers: now it’s interactive.

    The gap between 1 and 3 is the “looks ready but isn’t” window. React Server Components shrink it: only the interactive client components ship JS, so bundles are smaller and there’s less to hydrate. Next.js implements RSC; Nuxt and Astro chase the same “ship less JS, use islands” idea. (This site is built on that idea: see Resumability, and Why This Site Chose Astro.)

    Structuring the code

    • Modular monolith. One deployable with hard boundaries between domain modules. Shared UI and logic (design system, CSS, hooks, validation) and shared services (auth, data fetching) sit underneath. Bulletproof React is a feature-folder version of this.
    • Micro-frontends. Each domain becomes an independently deployed app, often with its own vertical slice of backend. Architecturally it’s close to a modular monolith, just split at deploy time. It lets large, independent teams ship separately, at the price of much more complexity: shared state, versioning, consistent UX. It’s an organisational decision more than a technical one, and overkill for small teams.
    • Hexagonal or onion architecture matters less on the front end. Frameworks are opinionated and bring their own structure, UIs are usually thin, and extra layers add overhead. It pays off for long-lived apps, or if you genuinely expect to swap frameworks.

    How I’d explain a sensible default

    Serve static pages with SSG and use SSR where you need personalisation. Keep the repo a modular monolith with domain-driven boundaries, so that if the team grows you can move towards micro-frontends when there’s a real need, such as shipping parts of the product independently.

    In short

    • Hydration: server HTML (FCP), then download and run JS, then hydrate (interactive).
    • SSG is build time, ISR is build time plus timed regeneration, SSR is every request.
    • Micro-frontends are justified by many independent teams that need to deploy separately. The org shape drives it.
    • #architecture
    • #rendering
    • #ssr
    • #ssg
    • #react-server-components
    • #micro-frontends

    ↪ Links to (2)